Skip to content

Commit a9a7ed3

Browse files
mario-kruegerktf
authored andcommitted
HistogramRegistry: add sorting and cloning features
- added option to sort histograms alphabetically in the output file - added possibility to clone histograms or groups of histograms stored in the registry and store the copy under a different (group-)name
1 parent 6be03b4 commit a9a7ed3

File tree

5 files changed

+143
-60
lines changed

5 files changed

+143
-60
lines changed

Analysis/Tasks/trackqa.cxx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ void customize(std::vector<o2::framework::ConfigParamSpec>& workflowOptions)
4040
//****************************************************************************************
4141
struct TrackQATask {
4242

43-
HistogramRegistry histos{"Histos", true, {}, OutputObjHandlingPolicy::QAObject};
43+
HistogramRegistry histos{"Histos", {}, OutputObjHandlingPolicy::QAObject};
4444

4545
Configurable<int> selectedTracks{"select", 1, "Choice of track selection. 0 = no selection, 1 = globalTracks, 2 = globalTracksSDD"};
4646

@@ -127,7 +127,7 @@ struct TrackQATask {
127127
};
128128

129129
struct TrackCutQATask {
130-
HistogramRegistry cuts{"Cuts", true, {}, OutputObjHandlingPolicy::QAObject};
130+
HistogramRegistry cuts{"Cuts", {}, OutputObjHandlingPolicy::QAObject};
131131
TrackSelection selectedTracks = getGlobalTrackSelection();
132132
static constexpr int ncuts = static_cast<int>(TrackSelection::TrackCuts::kNCuts);
133133
void init(InitContext&)
@@ -155,7 +155,7 @@ struct TrackCutQATask {
155155
//****************************************************************************************
156156
struct TrackQATaskMC {
157157

158-
HistogramRegistry resolution{"Resolution", true, {}, OutputObjHandlingPolicy::QAObject};
158+
HistogramRegistry resolution{"Resolution", {}, OutputObjHandlingPolicy::QAObject};
159159

160160
void init(o2::framework::InitContext&){
161161

Analysis/Tutorials/src/histogramRegistry.cxx

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ struct ATask {
2525
/// Construct a registry object with direct declaration
2626
HistogramRegistry registry{
2727
"registry",
28-
true,
2928
{
3029
{"eta", "#eta", {HistType::kTH1F, {{102, -2.01, 2.01}}}}, //
3130
{"phi", "#varphi", {HistType::kTH1F, {{100, 0., 2. * M_PI}}}} //
@@ -45,7 +44,6 @@ struct BTask {
4544
/// Construct a registry object with direct declaration
4645
HistogramRegistry registry{
4746
"registry",
48-
true,
4947
{
5048
{"eta", "#eta", {HistType::kTH1F, {{102, -2.01, 2.01}}}}, //
5149
{"ptToPt", "#ptToPt", {HistType::kTH2F, {{100, -0.01, 10.01}, {100, -0.01, 10.01}}}} //
@@ -63,34 +61,35 @@ struct CTask {
6361

6462
HistogramRegistry registry{
6563
"registry",
66-
true,
6764
{
6865
{"1d", "test 1d", {HistType::kTH1I, {{100, -10.0f, 10.0f}}}}, //
6966
{"2d", "test 2d", {HistType::kTH2F, {{100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}}}}, //
7067
{"3d", "test 3d", {HistType::kTH3D, {{100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}}}}, //
7168
{"4d", "test 4d", {HistType::kTHnC, {{100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}, {100, -10.0f, 10.01f}}}}, //
7269
{"5d", "test 5d", {HistType::kTHnSparseL, {{10, -10.0f, 10.01f}, {10, -10.0f, 10.01f}, {10, -10.0f, 10.01f}, {10, -10.0f, 10.01f}, {10, -10.0f, 10.01f}}}}, //
73-
} //
70+
},
71+
OutputObjHandlingPolicy::AnalysisObject,
72+
true //
7473
};
7574

7675
void init(o2::framework::InitContext&)
7776
{
78-
registry.add({"7d", "test 7d", {HistType::kTHnC, {{3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}}}});
77+
registry.add("7d", "test 7d", {HistType::kTHnC, {{3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}}});
7978

80-
registry.add({"6d", "test 6d", {HistType::kTHnC, {{3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}}}});
79+
registry.add("6d", "test 6d", {HistType::kTHnC, {{3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}, {3, -10.0f, 10.01f}}});
8180

82-
registry.add({"1d-profile", "test 1d profile", {HistType::kTProfile, {{20, 0.0f, 10.01f}}}});
83-
registry.add({"2d-profile", "test 2d profile", {HistType::kTProfile2D, {{20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}}}});
84-
registry.add({"3d-profile", "test 3d profile", {HistType::kTProfile3D, {{20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}}}});
81+
registry.add("1d-profile", "test 1d profile", {HistType::kTProfile, {{20, 0.0f, 10.01f}}});
82+
registry.add("2d-profile", "test 2d profile", {HistType::kTProfile2D, {{20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}}});
83+
registry.add("3d-profile", "test 3d profile", {HistType::kTProfile3D, {{20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}, {20, 0.0f, 10.01f}}});
8584

86-
registry.add({"2d-weight", "test 2d weight", {HistType::kTH2C, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true});
85+
registry.add("2d-weight", "test 2d weight", {HistType::kTH2C, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true);
8786

88-
registry.add({"3d-weight", "test 3d weight", {HistType::kTH3C, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true});
87+
registry.add("3d-weight", "test 3d weight", {HistType::kTH3C, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true);
8988

90-
registry.add({"4d-weight", "test 4d weight", {HistType::kTHnC, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {100, -10.0f, 10.01f}}}, true});
89+
registry.add("4d-weight", "test 4d weight", {HistType::kTHnC, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}, {100, -10.0f, 10.01f}}}, true);
9190

92-
registry.add({"1d-profile-weight", "test 1d profile weight", {HistType::kTProfile, {{2, -10.0f, 10.01f}}}, true});
93-
registry.add({"2d-profile-weight", "test 2d profile weight", {HistType::kTProfile2D, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true});
91+
registry.add("1d-profile-weight", "test 1d profile weight", {HistType::kTProfile, {{2, -10.0f, 10.01f}}}, true);
92+
registry.add("2d-profile-weight", "test 2d profile weight", {HistType::kTProfile2D, {{2, -10.0f, 10.01f}, {2, -10.0f, 10.01f}}}, true);
9493
}
9594

9695
void process(aod::Tracks const& tracks)
@@ -126,8 +125,8 @@ struct CTask {
126125
};
127126

128127
struct DTask {
129-
HistogramRegistry spectra{"spectra", true, {}, OutputObjHandlingPolicy::AnalysisObject, true};
130-
HistogramRegistry etaStudy{"etaStudy", true, {}, OutputObjHandlingPolicy::AnalysisObject, true};
128+
HistogramRegistry spectra{"spectra", {}, OutputObjHandlingPolicy::AnalysisObject, true, true};
129+
HistogramRegistry etaStudy{"etaStudy", {}, OutputObjHandlingPolicy::AnalysisObject, true, true};
131130

132131
void init(o2::framework::InitContext&)
133132
{
@@ -158,6 +157,19 @@ struct DTask {
158157

159158
etaStudy.add("positive", "A side spectra", kTH1I, {ptAxis});
160159
etaStudy.add("negative", "C side spectra", kTH1I, {ptAxis});
160+
161+
spectra.add("before_cuts/hist1", "asdf", defaultParticleHist);
162+
spectra.add("before_cuts/hist2", "asdf", defaultParticleHist);
163+
spectra.add("before_cuts/hist3", "asdf", defaultParticleHist);
164+
spectra.add("before_cuts/hist4", "asdf", defaultParticleHist);
165+
spectra.add("before_cuts/hist5", "asdf", defaultParticleHist);
166+
167+
// clone whole category / group
168+
spectra.addClone("before_cuts/", "after_cuts/");
169+
170+
// clone single histograms
171+
spectra.addClone("sigmas", "cascades");
172+
spectra.addClone("neutral/pions", "strange/funny/particles");
161173
}
162174

163175
void process(aod::Tracks const& tracks)
@@ -175,6 +187,14 @@ struct DTask {
175187
spectra.fill("one/two/three/four/kaons", track.pt(), track.eta(), 50., 0.);
176188
spectra.fill("sigmas", track.pt(), track.eta(), 50., 0.);
177189
spectra.fill("lambdas", track.pt(), track.eta(), 50., 0.);
190+
191+
spectra.fill("before_cuts/hist2", track.pt(), track.eta(), 50., 0.);
192+
spectra.fill("before_cuts/hist2", track.pt(), track.eta(), 50., 0.);
193+
194+
spectra.fill("after_cuts/hist2", track.pt(), track.eta(), 50., 0.);
195+
196+
spectra.fill("cascades", track.pt(), track.eta(), 50., 0.);
197+
spectra.fill("strange/funny/particles", track.pt(), track.eta(), 50., 0.);
178198
}
179199
}
180200
};

Framework/Core/include/Framework/HistogramRegistry.h

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ struct HistFactory {
215215
// create histogram
216216
std::shared_ptr<T> hist{generateHist<T>(histSpec.name, histSpec.title, nAxes, nBins, lowerBounds, upperBounds)};
217217
if (!hist) {
218-
LOGF(FATAL, "The number of specified dimensions does not match the type.");
218+
LOGF(FATAL, "The number of dimensions specified for histogram %s does not match the type.", histSpec.name);
219219
return nullptr;
220220
}
221221

@@ -361,16 +361,16 @@ struct HistFiller {
361361
hist->Fill(static_cast<double>(positionAndWeight)...);
362362
} else if constexpr (validComplexFill) {
363363
// savety check for n dimensional histograms (runtime overhead)
364-
if (hist->GetNdimensions() != nDim)
365-
throw runtime_error("The number of position (and weight) arguments in fill() does not match the histogram dimensions!");
366-
364+
if (hist->GetNdimensions() != nDim) {
365+
LOGF(FATAL, "The number of arguments in fill function called for histogram %s is incompatible with histogram dimensions.", hist->GetName());
366+
}
367367
double tempArray[sizeof...(Ts)] = {static_cast<double>(positionAndWeight)...};
368368
if constexpr (useWeight)
369369
hist->Fill(tempArray, tempArray[sizeof...(Ts) - 1]);
370370
else
371371
hist->Fill(tempArray);
372372
} else {
373-
throw runtime_error("The number of position (and weight) arguments in fill() does not match the histogram dimensions!");
373+
LOGF(FATAL, "The number of arguments in fill function called for histogram %s is incompatible with histogram dimensions.", hist->GetName());
374374
}
375375
}
376376

@@ -393,12 +393,8 @@ struct HistFiller {
393393
class HistogramRegistry
394394
{
395395
public:
396-
HistogramRegistry(char const* const name_, bool enable, std::vector<HistogramSpec> histSpecs_, OutputObjHandlingPolicy policy_ = OutputObjHandlingPolicy::AnalysisObject, bool createFolder_ = false) : name(name_),
397-
policy(policy_),
398-
enabled(enable),
399-
mRegistryKey(),
400-
mRegistryValue(),
401-
mCreateFolder(createFolder_)
396+
HistogramRegistry(char const* const name_, std::vector<HistogramSpec> histSpecs_ = {}, OutputObjHandlingPolicy policy_ = OutputObjHandlingPolicy::AnalysisObject, bool sortHistos_ = false, bool createRegistryDir_ = false)
397+
: mName(name_), mPolicy(policy_), mRegistryKey(), mRegistryValue(), mSortHistos(sortHistos_), mCreateRegistryDir(createRegistryDir_)
402398
{
403399
mRegistryKey.fill(0u);
404400
for (auto& histSpec : histSpecs_) {
@@ -421,6 +417,33 @@ class HistogramRegistry
421417
insert({name_, title_, {histType_, axes_}, callSumw2_});
422418
}
423419

420+
// store a copy of an existing histogram (or group of histograms) under a different name
421+
void addClone(const std::string& source_, const std::string& target_)
422+
{
423+
// search for histograms starting with source_ substring
424+
for (auto& histVariant : mRegistryValue) {
425+
std::visit([&](const auto& sharedPtr) {
426+
if (!sharedPtr.get())
427+
return;
428+
std::string sourceName{((TNamed*)sharedPtr.get())->GetName()};
429+
if (sourceName.rfind(source_, 0) == 0) {
430+
// when cloning groups of histograms source_ and target_ must end with "/"
431+
if (sourceName.size() != source_.size() && (source_.back() != '/' || target_.back() != '/')) {
432+
return;
433+
}
434+
// when cloning a single histogram the specified target_ must not be a group name
435+
if (sourceName.size() == source_.size() && target_.back() == '/') {
436+
LOGF(FATAL, "Cannot turn histogram into folder!");
437+
}
438+
std::string targetName{target_};
439+
targetName += sourceName.substr(sourceName.find(source_) + source_.size());
440+
insertClone(targetName.data(), sharedPtr);
441+
}
442+
},
443+
histVariant);
444+
}
445+
}
446+
424447
// gets the underlying histogram pointer
425448
// we cannot automatically infer type here so it has to be explicitly specified
426449
// -> get<TH1>(), get<TH2>(), get<TH3>(), get<THn>(), get<THnSparse>(), get<TProfile>(), get<TProfile2D>(), get<TProfile3D>()
@@ -458,23 +481,23 @@ class HistogramRegistry
458481
OutputSpec const spec()
459482
{
460483
ConcreteDataMatcher matcher{"HIST", "\0", 0};
461-
strncpy(matcher.description.str, this->name.data(), 16);
462-
return OutputSpec{OutputLabel{this->name}, matcher};
484+
strncpy(matcher.description.str, mName.data(), 16);
485+
return OutputSpec{OutputLabel{mName}, matcher};
463486
}
464487

465488
OutputRef ref()
466489
{
467-
return OutputRef{std::string{this->name}, 0, o2::header::Stack{OutputObjHeader{policy, taskHash}}};
490+
return OutputRef{std::string{mName}, 0, o2::header::Stack{OutputObjHeader{mPolicy, mTaskHash}}};
468491
}
469492
void setHash(uint32_t hash)
470493
{
471-
taskHash = hash;
494+
mTaskHash = hash;
472495
}
473496

474497
TList* operator*()
475498
{
476499
TList* list = new TList();
477-
list->SetName(this->name.data());
500+
list->SetName(mName.data());
478501

479502
for (auto j = 0u; j < MAX_REGISTRY_SIZE; ++j) {
480503
TNamed* rawPtr = nullptr;
@@ -492,8 +515,29 @@ class HistogramRegistry
492515
}
493516
}
494517
}
495-
if (mCreateFolder) {
496-
// propagate wether or not to create a dedicated folder for this registry to the writer by adding this 'flag' to the list
518+
519+
// sort histograms in output file alphabetically
520+
// TODO: in cases where histograms and directories reside in same level, we could put directories always on top
521+
if (mSortHistos) {
522+
std::function<void(TList*)> sortSubLists;
523+
sortSubLists = [&](TList* list) {
524+
TIter next(list);
525+
TNamed* object = nullptr;
526+
while ((object = (TNamed*)next())) {
527+
if (object->InheritsFrom(TList::Class())) {
528+
TList* subList = (TList*)object;
529+
subList->Sort();
530+
sortSubLists(subList);
531+
}
532+
}
533+
};
534+
list->Sort();
535+
sortSubLists(list);
536+
}
537+
538+
// create dedicated directory containing all of the registrys histograms
539+
if (mCreateRegistryDir) {
540+
// propagate this to the writer by adding a 'flag' to the output list
497541
list->AddLast(new TNamed("createFolder", ""));
498542
}
499543
return list;
@@ -515,7 +559,7 @@ class HistogramRegistry
515559
return;
516560
}
517561
}
518-
throw runtime_error("No matching histogram found in HistogramRegistry!");
562+
LOGF(FATAL, "No histogram called %s found in HistogramRegistry!", name);
519563
}
520564

521565
template <typename... Ts>
@@ -552,7 +596,7 @@ class HistogramRegistry
552596
return;
553597
}
554598
}
555-
throw runtime_error("No matching histogram found in HistogramRegistry!");
599+
LOGF(FATAL, "No histogram called %s found in HistogramRegistry!", name);
556600
}
557601

558602
/// lookup distance counter for benchmarking
@@ -573,12 +617,31 @@ class HistogramRegistry
573617
return;
574618
}
575619
}
576-
throw runtime_error("Internal array of HistogramRegistry is full.");
620+
LOGF(FATAL, "Internal array of HistogramRegistry %s is full.", mName);
621+
}
622+
623+
// clone an existing histogram and insert it into the registry
624+
template <typename T>
625+
void insertClone(const char* name, const std::shared_ptr<T>& originalHist)
626+
{
627+
const uint32_t id = compile_time_hash(name);
628+
uint32_t i = imask(id);
629+
for (auto j = 0u; j < MAX_REGISTRY_SIZE; ++j) {
630+
TObject* rawPtr = nullptr;
631+
std::visit([&](const auto& sharedPtr) { rawPtr = sharedPtr.get(); }, mRegistryValue[imask(j + i)]);
632+
if (!rawPtr) {
633+
mRegistryKey[imask(j + i)] = id;
634+
mRegistryValue[imask(j + i)] = std::shared_ptr<T>(static_cast<T*>(originalHist->Clone(name)));
635+
lookup += j;
636+
return;
637+
}
638+
}
639+
LOGF(FATAL, "Internal array of HistogramRegistry %s is full.", mName);
577640
}
578641

579642
inline constexpr uint32_t imask(uint32_t i) const
580643
{
581-
return i & mask;
644+
return i & MASK;
582645
}
583646

584647
// helper function to create resp. find the subList defined by path
@@ -615,16 +678,16 @@ class HistogramRegistry
615678
return pathAndName;
616679
}
617680

618-
std::string name{};
619-
bool enabled{};
620-
bool mCreateFolder{};
621-
OutputObjHandlingPolicy policy{};
622-
uint32_t taskHash{};
681+
std::string mName{};
682+
OutputObjHandlingPolicy mPolicy{};
683+
bool mCreateRegistryDir{};
684+
bool mSortHistos{};
685+
uint32_t mTaskHash{};
623686

624687
/// The maximum number of histograms in buffer is currently set to 512
625688
/// which seems to be both reasonably large and allowing for very fast lookup
626-
static constexpr uint32_t mask = 0x1FF;
627-
static constexpr uint32_t MAX_REGISTRY_SIZE = mask + 1;
689+
static constexpr uint32_t MASK{0x1FF};
690+
static constexpr uint32_t MAX_REGISTRY_SIZE{MASK + 1};
628691
std::array<uint32_t, MAX_REGISTRY_SIZE> mRegistryKey{};
629692
std::array<HistPtr, MAX_REGISTRY_SIZE> mRegistryValue{};
630693
};

Framework/Core/test/benchmark_HistogramRegistry.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static void BM_HashedNameLookup(benchmark::State& state)
3232
for (auto i = 0; i < state.range(0); ++i) {
3333
histSpecs.push_back({fmt::format("histo{}", i + 1).c_str(), fmt::format("Histo {}", i + 1).c_str(), {HistType::kTH1F, {{100, 0, 1}}}});
3434
}
35-
HistogramRegistry registry{"registry", true, histSpecs};
35+
HistogramRegistry registry{"registry", histSpecs};
3636
state.ResumeTiming();
3737

3838
for (auto i = 0; i < nLookups; ++i) {

0 commit comments

Comments
 (0)