Skip to content

Commit 2da9006

Browse files
authored
[PWGCF] New unified data model for femto analysis (#12376)
1 parent b56a61d commit 2da9006

33 files changed

+6778
-9
lines changed

PWGCF/Femto/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2019-2025 CERN and copyright holders of ALICE O2.
1+
# Copyright 2019-2024 CERN and copyright holders of ALICE O2.
22
# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
33
# All rights not expressly granted are reserved.
44
#
@@ -9,6 +9,8 @@
99
# granted to it by virtue of its status as an Intergovernmental Organization
1010
# or submit itself to any jurisdiction.
1111

12-
#add_subdirectory(DataModel)
12+
add_subdirectory(Core)
1313
add_subdirectory(TableProducer)
14+
add_subdirectory(Tasks)
1415

16+
add_subdirectory(FemtoNuclei)

PWGCF/Femto/Core/CMakeLists.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright 2019-2024 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.

PWGCF/Femto/Core/baseSelection.h

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
// Copyright 2019-2025 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 baseSelection.h
13+
/// \brief Defines the BaseSelection class for managing and evaluating multiple selections over multiple observables.
14+
/// \author Anton Riedel, TU München, anton.riedel@tum.de
15+
16+
#ifndef PWGCF_FEMTO_CORE_BASESELECTION_H_
17+
#define PWGCF_FEMTO_CORE_BASESELECTION_H_
18+
19+
#include "PWGCF/Femto/Core/selectionContainer.h"
20+
21+
#include "fairlogger/Logger.h"
22+
23+
#include <climits>
24+
#include <cstddef>
25+
#include <cstdint>
26+
#include <iomanip>
27+
#include <ios>
28+
#include <sstream>
29+
#include <string>
30+
#include <unordered_map>
31+
#include <vector>
32+
33+
namespace o2::analysis::femto
34+
{
35+
36+
/// \class BaseSelection
37+
/// \brief Template class for managing selection criteria across multiple observables.
38+
///
39+
/// This class manages an array of SelectionContainer objects, each corresponding to a specific observable.
40+
/// It evaluates which selections are fulfilled, assembles a final bitmask, and tracks required vs. optional cuts.
41+
///
42+
/// \tparam T Type of observable values (mostly floats).
43+
/// \tparam BitmaskType Type used for internal bitmask operations (e.g., uint32_t, uint64_t).
44+
/// \tparam NumObservables Total number of observables handled.
45+
template <typename T, typename BitmaskType, size_t NumObservables>
46+
class BaseSelection
47+
{
48+
public:
49+
/// \brief Default constructor.
50+
BaseSelection() {}
51+
52+
/// \brief Destructor
53+
virtual ~BaseSelection() = default;
54+
55+
/// \brief Add a static-value based selection for a specific observable.
56+
/// \param selectionValues Vector of threshold values.
57+
/// \param observableIndex Index of the observable.
58+
/// \param limitType Type of limit (from limits::LimitType).
59+
/// \param skipMostPermissiveBit Whether to skip the loosest threshold in the bitmask.
60+
/// \param isMinimalCut Whether this cut is mandatory or optional.
61+
void addSelection(std::vector<T> const& selectionValues, int observableIndex, limits::LimitType limitType, bool skipMostPermissiveBit, bool isMinimalCut)
62+
{
63+
if (static_cast<size_t>(observableIndex) >= NumObservables) {
64+
LOG(fatal) << "Observable is not valid. Observable (index) has to be smaller than " << NumObservables;
65+
}
66+
if (skipMostPermissiveBit) {
67+
mNSelections += selectionValues.size() - 1;
68+
} else {
69+
mNSelections += selectionValues.size();
70+
}
71+
if (mNSelections >= sizeof(BitmaskType) * CHAR_BIT) {
72+
LOG(fatal) << "Too many selections. At most " << sizeof(BitmaskType) * CHAR_BIT << " are supported";
73+
}
74+
// check if any cut is optional
75+
if (!isMinimalCut) {
76+
mHasOptionalSelection = true;
77+
}
78+
mSelectionContainers.at(observableIndex) = SelectionContainer<T, BitmaskType>(selectionValues, limitType, skipMostPermissiveBit, isMinimalCut);
79+
}
80+
81+
/// \brief Add a function-based selection for a specific observable.
82+
/// \param baseName Base name for TF1 functions.
83+
/// \param lowerLimit Lower bound for the TF1 domain.
84+
/// \param upperLimit Upper bound for the TF1 domain.
85+
/// \param selectionValues Function definitions as strings.
86+
/// \param observableIndex Index of the observable.
87+
/// \param limitType Type of limit.
88+
/// \param skipMostPermissiveBit Whether to skip the loosest threshold in the bitmask.
89+
/// \param isMinimalCut Whether this cut is mandatory or optional.
90+
void addSelection(std::string const& baseName,
91+
T lowerLimit,
92+
T upperLimit,
93+
std::vector<std::string> const& selectionValues,
94+
int observableIndex,
95+
limits::LimitType limitType,
96+
bool skipMostPermissiveBit,
97+
bool isMinimalCut)
98+
{
99+
if (static_cast<size_t>(observableIndex) >= NumObservables) {
100+
LOG(fatal) << "Observable is not valid. Observable (index) has to be smaller than " << NumObservables;
101+
}
102+
if (skipMostPermissiveBit) {
103+
mNSelections += selectionValues.size() - 1;
104+
} else {
105+
mNSelections += selectionValues.size();
106+
}
107+
if (mNSelections >= sizeof(BitmaskType) * CHAR_BIT) {
108+
LOG(fatal) << "Too many selections. At most " << sizeof(BitmaskType) * CHAR_BIT << " are supported";
109+
}
110+
mSelectionContainers.at(observableIndex) = SelectionContainer<T, BitmaskType>(baseName, lowerLimit, upperLimit, selectionValues, limitType, skipMostPermissiveBit, isMinimalCut);
111+
}
112+
113+
/// \brief Update the limits of a function-based selection for a specific observable.
114+
/// \param observable Index of the observable.
115+
/// \param value Value at which to evaluate the selection functions.
116+
void updateLimits(int observable, T value) { mSelectionContainers.at(observable).updateLimits(value); }
117+
118+
/// \brief Reset the internal bitmask and evaluation flags before evaluating a new event.
119+
void reset()
120+
{
121+
mFinalBitmask.reset();
122+
mPassesMinimalSelections = true;
123+
// will be true if no optional cut as been defined and
124+
// will be set to false if we have optional cuts (but will be set to true in the case at least one optional cut succeeds)
125+
mPassesOptionalSelections = !mHasOptionalSelection;
126+
}
127+
128+
/// \brief Evaluate a single observable against its configured selections.
129+
/// \param observableIndex Index of the observable.
130+
/// \param value Value of the observable.
131+
void evaluateObservable(int observableIndex, T value)
132+
{
133+
// if there are no selections configured, bail out
134+
if (mSelectionContainers.at(observableIndex).isEmpty()) {
135+
return;
136+
}
137+
// if any previous observable did not pass minimal selections, there is no point in setting bitmask for other observables
138+
// minimal selection for each observable is computed after adding it
139+
if (mPassesMinimalSelections == false) {
140+
return;
141+
}
142+
// set bitmask for given observable
143+
mSelectionContainers.at(observableIndex).evaluate(value);
144+
// check if minimal selction for this observable holds
145+
if (mSelectionContainers.at(observableIndex).passesAsMinimalCut() == false) {
146+
mPassesMinimalSelections = false;
147+
}
148+
// check if any optional selection holds
149+
if (mSelectionContainers.at(observableIndex).passesAsOptionalCut() == true) {
150+
mPassesOptionalSelections = true;
151+
}
152+
}
153+
154+
/// \brief Check if all required (minimal) and optional cuts are passed.
155+
/// \return True if all required and at least one optional cut (if present) is passed.
156+
bool passesAllRequiredSelections() const
157+
{
158+
return mPassesMinimalSelections && mPassesOptionalSelections;
159+
}
160+
161+
/// \brief Check if the optional selection for a specific observable is passed.
162+
/// \param observableIndex Index of the observable.
163+
/// \return True if at least one optional selection is fulfilled.
164+
bool passesOptionalSelection(int observableIndex) const
165+
{
166+
return mSelectionContainers.at(observableIndex).passesAsOptionalCut();
167+
}
168+
169+
/// \brief Assemble the global selection bitmask from individual observable selections.
170+
void assembleBitmask()
171+
{
172+
// if minimal selections are not passed, just set bitmask to 0
173+
if (mPassesMinimalSelections == false) {
174+
mFinalBitmask.reset();
175+
return;
176+
}
177+
178+
// to assemble bitmask, convert all bitmask into integers
179+
// shift the current one and add the new bits
180+
for (auto const& selectionContainer : mSelectionContainers) {
181+
// if there are no selections for a certain observable, skip
182+
if (selectionContainer.isEmpty()) {
183+
continue;
184+
}
185+
// Shift the result to make space and add the new value
186+
mFinalBitmask = (mFinalBitmask << selectionContainer.getShift()) | selectionContainer.getBitmask();
187+
}
188+
}
189+
190+
/// \brief Retrieve the assembled bitmask as an integer value.
191+
/// \return The combined selection bitmask.
192+
BitmaskType getBitmask() const { return static_cast<BitmaskType>(mFinalBitmask.to_ullong()); }
193+
194+
/// \brief Retrieve the assembled bitmask as an integer value.
195+
/// \return The combined selection bitmask.
196+
BitmaskType getBitmask(int observableIndex) const { return static_cast<BitmaskType>(mSelectionContainers.at(observableIndex).getBitmask().to_ullong()); }
197+
198+
/// \brief Retrieve the assembled bitmask as an integer value.
199+
/// \return The combined selection bitmask.
200+
template <typename R>
201+
void setBitmask(int observableIndex, R bitmask)
202+
{
203+
mSelectionContainers.at(observableIndex).setBitmask(bitmask);
204+
}
205+
206+
/// \brief Print detailed information about all configured selections.
207+
/// \tparam MapType Type used in the observable name map (usually an enum or int).
208+
/// \param objectName Name of the current object (e.g. particle species).
209+
/// \param observableNames Map from observable index to human-readable names.
210+
template <typename MapType>
211+
void printSelections(const std::string& objectName, const std::unordered_map<MapType, std::string>& observableNames) const
212+
{
213+
LOG(info) << "Printing Configuration of " << objectName;
214+
215+
size_t globalBitIndex = 0; // Tracks bit position across all containers
216+
217+
for (size_t idx = mSelectionContainers.size(); idx-- > 0;) {
218+
const auto& container = mSelectionContainers[idx];
219+
if (container.isEmpty()) {
220+
continue;
221+
}
222+
223+
const MapType key = static_cast<MapType>(idx);
224+
const std::string& name = observableNames.count(key) ? observableNames.at(key) : "[Unknown]";
225+
226+
LOG(info) << "Observable: " << name << " (index " << idx << ")";
227+
LOG(info) << " Limit type : " << container.getLimitTypeAsString();
228+
LOG(info) << " Minimal cut : " << (container.isMinimalCut() ? "yes" : "no");
229+
LOG(info) << " Skip most permissive : " << (container.skipMostPermissiveBit() ? "yes" : "no");
230+
LOG(info) << " Bitmask shift : " << container.getShift();
231+
LOG(info) << " Selections : ";
232+
233+
const auto& values = container.getSelectionValues();
234+
const auto& functions = container.getSelectionFunction();
235+
const bool useFunctions = !functions.empty();
236+
const size_t numSelections = useFunctions ? functions.size() : values.size();
237+
const bool skipMostPermissive = container.skipMostPermissiveBit();
238+
239+
int valWidth = 20;
240+
int bitWidth = 30;
241+
242+
for (size_t j = 0; j < numSelections; ++j) {
243+
std::stringstream line;
244+
245+
// Selection string (either value or function)
246+
const std::string& sel = useFunctions ? std::string(functions[j].GetFormula()->GetExpFormula().Data()) : std::to_string(values[j]);
247+
line << " " << std::left << std::setw(valWidth) << sel;
248+
249+
// Bitmask
250+
if (skipMostPermissive && j == 0) {
251+
line << std::setw(bitWidth) << "-> loosest minimal selection, no bit saved";
252+
} else {
253+
const uint64_t bitmask = uint64_t{1} << globalBitIndex++;
254+
line << std::setw(bitWidth) << ("-> bitmask: " + std::to_string(bitmask));
255+
}
256+
257+
LOG(info) << line.str();
258+
}
259+
260+
LOG(info) << ""; // blank line between observables
261+
}
262+
LOG(info) << "Printing done";
263+
}
264+
265+
protected:
266+
std::array<SelectionContainer<T, BitmaskType>, NumObservables> mSelectionContainers = {}; ///< Array containing all selections
267+
std::bitset<sizeof(BitmaskType) * CHAR_BIT> mFinalBitmask = {}; ///< final bitmaks
268+
size_t mNSelections = 0; ///< Number of selections
269+
bool mPassesMinimalSelections = true; ///< Set to true if all minimal (mandatory) selections are passed
270+
bool mHasOptionalSelection = false; ///< Set to true if at least one selections is optional
271+
bool mPassesOptionalSelections = true; ///< Set to true if at least one optional (non-mandatory) selections is passed
272+
};
273+
} // namespace o2::analysis::femto
274+
275+
#endif // PWGCF_FEMTO_CORE_BASESELECTION_H_

0 commit comments

Comments
 (0)