ndmspc v1.2.0-0.1.rc4
Loading...
Searching...
No Matches
NUtils.cxx
1#include <cstddef>
2#include <iostream>
3#include <TSystem.h>
4#include <TROOT.h>
5#include <TPad.h>
6#include <vector>
7#include <string>
8#include <sstream>
9#include <thread>
10#include <TF1.h>
11#include <TFile.h>
12#include <TThread.h>
13#include <TAxis.h>
14#include <THnSparse.h>
15#include <string>
16#include <vector>
17#include <stdexcept>
18#include <TString.h>
19#if defined(__linux__)
20#include <fstream>
21#elif defined(__APPLE__)
22#include <ifaddrs.h>
23#include <net/if.h>
24#include <sys/types.h>
25#include <sys/socket.h>
26#endif
27#include "NLogger.h"
28#include "NHttpRequest.h"
29#include "ndmspc.h"
30#ifdef WITH_PARQUET
31#include <arrow/api.h>
32#include <arrow/io/api.h>
33#include <parquet/arrow/reader.h>
34#include <parquet/exception.h>
35#endif
36#include "NUtils.h"
37
38using std::ifstream;
39
41ClassImp(Ndmspc::NUtils);
43
44namespace Ndmspc {
45
46bool NUtils::EnableMT(Int_t numthreads)
47{
52 bool previouslyEnabled = ROOT::IsImplicitMTEnabled();
53
54 if (ROOT::IsImplicitMTEnabled()) {
55 ROOT::DisableImplicitMT();
56 }
57
58 // TH1D * h = new TH1D("h", "Test Histogram", 20, -10, 10);
59 // h->FillRandom("gaus", 1000);
60 // TF1 * f1 = new TF1("f1", "gaus", 0, 10);
61 // h->Fit(f1, "N");
62 // delete h;
63
64 if (numthreads == -1) {
65 // take numeber of cores from env variable
66 const char * nThreadsEnv = gSystem->Getenv("ROOT_MAX_THREADS");
67 if (nThreadsEnv) {
68 try {
69 numthreads = std::stoul(nThreadsEnv);
70 }
71 catch (const std::exception & e) {
72 NLogError("Error parsing ROOT_MAX_THREADS: %s !!! Setting it to '1' ...", e.what());
73 numthreads = 1;
74 }
75 }
76 else {
77 numthreads = 1; // use default
78 }
79 }
80
81 // Initialise ROOT's thread-safety infrastructure (gROOTMutex, etc.)
82 ROOT::EnableThreadSafety();
83
84 // Enable IMT with default number of threads (usually number of CPU cores)
85 if (numthreads > 0) {
86 ROOT::EnableImplicitMT(numthreads);
87 }
88
89 // Check if IMT is enabled
90 if (ROOT::IsImplicitMTEnabled()) {
91 NLogInfo("ROOT::ImplicitMT is enabled with number of threads: %d", ROOT::GetThreadPoolSize());
92 }
93
94 return previouslyEnabled;
95}
96
97bool NUtils::IsFileSupported(std::string filename)
98{
102
103 if (filename.find("http://") == 0 || filename.find("https://") == 0 || filename.find("root://") == 0 ||
104 filename.find("file://") == 0 || filename.find("alien://") == 0) {
105 return true;
106 }
107 TString fn(filename.c_str());
108 if (fn.BeginsWith("/") || !fn.Contains("://")) {
109 return true;
110 }
111 NLogError("NUtils::IsFileSupported: File '%s' not found", filename.c_str());
112 return false;
113}
114
115bool NUtils::AccessPathName(std::string path)
116{
120 TString pathStr(gSystem->ExpandPathName(path.c_str()));
121
122 if (pathStr.BeginsWith("http://") || pathStr.BeginsWith("https://")) {
123 // TODO: check if URL exists via HTTP request
124 NHttpRequest request;
125 // request.SetUrl(pathStr.Data());
126 // return request.Exists();
127 int http_code = request.head(pathStr.Data());
128 if (http_code == 200) {
129 return true;
130 }
131
132 return false;
133 }
134 else if (pathStr.BeginsWith("file://") || pathStr.BeginsWith("/") || !pathStr.Contains("://")) {
135
136 return gSystem->AccessPathName(pathStr.Data()) == false;
137 }
138 else if (pathStr.BeginsWith("root://") || pathStr.BeginsWith("alien://")) {
139 // For root and alien protocols, we can try to open the file
140 if (!pathStr.EndsWith(".root")) {
141 // For raw files, we cannot use TFile
142 pathStr += "?filetype=raw";
143 }
144 NLogDebug("NUtils::AccessPathName: Trying to open file '%s' ...", pathStr.Data());
145 TFile * f = TFile::Open(pathStr.Data());
146 if (f && !f->IsZombie()) {
147 f->Close();
148 return true;
149 }
150 return false;
151 }
152 return false;
153}
154
155int NUtils::Cp(std::string source, std::string destination, Bool_t progressbar )
156{
160 int rc = 0;
161
162 if (source.empty()) {
163 NLogError("NUtils::Cp: Source file is empty");
164 return -1;
165 }
166 if (destination.empty()) {
167 NLogError("NUtils::Cp: Destination file is empty");
168 return -1;
169 }
170
171 if (IsFileSupported(source) == false) {
172 NLogError("NUtils::Cp: Source file '%s' is not supported", source.c_str());
173 return -1;
174 }
175 if (IsFileSupported(destination) == false) {
176 NLogError("NUtils::Cp: Destination file '%s' is not supported", destination.c_str());
177 return -1;
178 }
179
180 NLogInfo("Copying file from '%s' to '%s' ...", source.c_str(), destination.c_str());
181 rc = TFile::Cp(source.c_str(), destination.c_str(), progressbar);
182 return rc;
183}
184
185TAxis * NUtils::CreateAxisFromLabels(const std::string & name, const std::string & title,
186 const std::vector<std::string> & labels)
187{
191 int nBins = labels.size();
192 TAxis * a = new TAxis(nBins, 0, nBins);
193 a->SetName(name.c_str());
194 a->SetTitle(title.c_str());
195 for (int i = 0; i < nBins; i++) {
196 NLogTrace("NUtils::CreateAxisFromLabels: Adding label: %s", labels[i].c_str());
197 a->SetBinLabel(i + 1, labels[i].c_str());
198 }
199 return a;
200}
201
202TAxis * NUtils::CreateAxisFromLabelsSet(const std::string & name, const std::string & title,
203 const std::set<std::string> & labels)
204{
208 int nBins = labels.size();
209 TAxis * a = new TAxis(nBins, 0, nBins);
210 a->SetName(name.c_str());
211 a->SetTitle(title.c_str());
212 int i = 1;
213 for (const auto & label : labels) {
214 NLogTrace("NUtils::CreateAxisFromLabels: Adding label: %s", label.c_str());
215 a->SetBinLabel(i, label.c_str());
216 i++;
217 }
218 return a;
219}
220
221THnSparse * NUtils::Convert(TH1 * h1, std::vector<std::string> names, std::vector<std::string> titles)
222{
226
227 if (h1 == nullptr) {
228 NLogError("TH1 h1 is null");
229 return nullptr;
230 }
231
232 NLogInfo("Converting TH1 '%s' to THnSparse ...", h1->GetName());
233
234 int nDims = 1;
235 // Int_t bins[nDims];
236 // Double_t xmin[nDims];
237 // Double_t xmax[nDims];
238 auto bins = std::make_unique<Int_t[]>(nDims);
239 auto xmin = std::make_unique<Double_t[]>(nDims);
240 auto xmax = std::make_unique<Double_t[]>(nDims);
241
242 TAxis * aIn = h1->GetXaxis();
243 bins[0] = aIn->GetNbins();
244 xmin[0] = aIn->GetXmin();
245 xmax[0] = aIn->GetXmax();
246
247 THnSparse * hns = new THnSparseD(h1->GetName(), h1->GetTitle(), nDims, bins.get(), xmin.get(), xmax.get());
248
249 // loop over all axes
250 for (int i = 0; i < nDims; i++) {
251 TAxis * a = hns->GetAxis(i);
252 TAxis * aIn = h1->GetXaxis();
253 a->SetName(aIn->GetName());
254 a->SetTitle(aIn->GetTitle());
255 if (aIn->GetXbins()->GetSize() > 0) {
256 // Double_t arr[aIn->GetNbins() + 1];
257 auto arr = std::make_unique<Double_t[]>(aIn->GetNbins() + 1);
258 arr[0] = aIn->GetBinLowEdge(1);
259 for (int iBin = 1; iBin <= aIn->GetNbins(); iBin++) {
260 arr[iBin] = aIn->GetBinUpEdge(iBin);
261 }
262 a->Set(a->GetNbins(), arr.get());
263 }
264 }
265
266 for (int i = 0; i < nDims; i++) {
267 if (!names[i].empty()) hns->GetAxis(i)->SetName(names[i].c_str());
268 if (!titles[i].empty()) hns->GetAxis(i)->SetTitle(titles[i].c_str());
269 }
270
271 // fill the sparse with the content of the TH3
272 for (Int_t i = 0; i <= h1->GetNbinsX() + 1; i++) {
273 double content = h1->GetBinContent(i);
274 Int_t p[1] = {i}; // bin indices in TH3
275 hns->SetBinContent(p, content);
276 }
277
278 hns->SetEntries(h1->GetEntries());
279 if (h1->GetSumw2N() > 0) {
280 hns->Sumw2();
281 }
282
283 return hns;
284}
285
286THnSparse * NUtils::Convert(TH2 * h2, std::vector<std::string> names, std::vector<std::string> titles)
287{
291 if (h2 == nullptr) {
292 NLogError("TH2 h2 is null");
293 return nullptr;
294 }
295 NLogInfo("Converting TH2 '%s' to THnSparse ...", h2->GetName());
296 int nDims = 2;
297 auto bins = std::make_unique<Int_t[]>(nDims);
298 auto xmin = std::make_unique<Double_t[]>(nDims);
299 auto xmax = std::make_unique<Double_t[]>(nDims);
300
301 for (int i = 0; i < nDims; i++) {
302 TAxis * aIn = nullptr;
303 if (i == 0)
304 aIn = h2->GetXaxis();
305 else if (i == 1)
306 aIn = h2->GetYaxis();
307 else {
308 NLogError("Invalid axis index %d", i);
309 return nullptr;
310 }
311 bins[i] = aIn->GetNbins();
312 xmin[i] = aIn->GetXmin();
313 xmax[i] = aIn->GetXmax();
314 }
315
316 THnSparse * hns = new THnSparseD(h2->GetName(), h2->GetTitle(), nDims, bins.get(), xmin.get(), xmax.get());
317
318 for (Int_t i = 0; i < nDims; i++) {
319 TAxis * a = hns->GetAxis(i);
320 TAxis * aIn = nullptr;
321 if (i == 0)
322 aIn = h2->GetXaxis();
323 else if (i == 1)
324 aIn = h2->GetYaxis();
325 else {
326 NLogError("Invalid axis index %d", i);
327 delete hns;
328 return nullptr;
329 }
330 a->SetName(aIn->GetName());
331 a->SetTitle(aIn->GetTitle());
332 if (aIn->GetXbins()->GetSize() > 0) {
333 auto arr = std::make_unique<Double_t[]>(aIn->GetNbins() + 1);
334 arr[0] = aIn->GetBinLowEdge(1);
335 for (int iBin = 1; iBin <= aIn->GetNbins(); iBin++) {
336 arr[iBin] = aIn->GetBinUpEdge(iBin);
337 }
338 a->Set(a->GetNbins(), arr.get());
339 }
340 }
341
342 for (Int_t i = 0; i < nDims; i++) {
343 if (!names[i].empty()) hns->GetAxis(i)->SetName(names[i].c_str());
344 if (!titles[i].empty()) hns->GetAxis(i)->SetTitle(titles[i].c_str());
345 }
346
347 // fill the sparse with the content of the TH2
348 for (Int_t i = 0; i <= h2->GetNbinsX() + 1; i++) {
349 for (Int_t j = 0; j <= h2->GetNbinsY() + 1; j++) {
350 double content = h2->GetBinContent(i, j);
351 Int_t p[2] = {i, j}; // bin indices in TH3
352 hns->SetBinContent(p, content);
353 }
354 }
355
356 hns->SetEntries(h2->GetEntries());
357 if (h2->GetSumw2N() > 0) {
358 hns->Sumw2();
359 }
360
361 return hns;
362}
363
364THnSparse * NUtils::Convert(TH3 * h3, std::vector<std::string> names, std::vector<std::string> titles)
365{
369
370 if (h3 == nullptr) {
371 NLogError("TH3 h3 is null");
372 return nullptr;
373 }
374
375 NLogInfo("Converting TH3 '%s' to THnSparse ...", h3->GetName());
376
377 int nDims = 3;
378 auto bins = std::make_unique<Int_t[]>(nDims);
379 auto xmin = std::make_unique<Double_t[]>(nDims);
380 auto xmax = std::make_unique<Double_t[]>(nDims);
381
382 for (int i = 0; i < nDims; i++) {
383 TAxis * aIn = nullptr;
384 if (i == 0)
385 aIn = h3->GetXaxis();
386 else if (i == 1)
387 aIn = h3->GetYaxis();
388 else if (i == 2)
389 aIn = h3->GetZaxis();
390 else {
391 NLogError("Invalid axis index %d", i);
392 return nullptr;
393 }
394 bins[i] = aIn->GetNbins();
395 xmin[i] = aIn->GetXmin();
396 xmax[i] = aIn->GetXmax();
397 }
398
399 THnSparse * hns = new THnSparseD(h3->GetName(), h3->GetTitle(), nDims, bins.get(), xmin.get(), xmax.get());
400
401 // loop over all axes
402 for (int i = 0; i < nDims; i++) {
403 TAxis * a = hns->GetAxis(i);
404 TAxis * aIn = nullptr;
405 if (i == 0)
406 aIn = h3->GetXaxis();
407 else if (i == 1)
408 aIn = h3->GetYaxis();
409 else if (i == 2)
410 aIn = h3->GetZaxis();
411 else {
412 NLogError("Invalid axis index %d", i);
413 delete hns;
414 return nullptr;
415 }
416 a->SetName(aIn->GetName());
417 a->SetTitle(aIn->GetTitle());
418 if (aIn->GetXbins()->GetSize() > 0) {
419 auto arr = std::make_unique<Double_t[]>(aIn->GetNbins() + 1);
420 arr[0] = aIn->GetBinLowEdge(1);
421 for (int iBin = 1; iBin <= aIn->GetNbins(); iBin++) {
422 arr[iBin] = aIn->GetBinUpEdge(iBin);
423 }
424 a->Set(a->GetNbins(), arr.get());
425 }
426 }
427
428 for (Int_t i = 0; i < nDims; i++) {
429 if (!names[i].empty()) hns->GetAxis(i)->SetName(names[i].c_str());
430 if (!titles[i].empty()) hns->GetAxis(i)->SetTitle(titles[i].c_str());
431 }
432
433 // fill the sparse with the content of the TH3
434 for (Int_t i = 0; i <= h3->GetNbinsX() + 1; i++) {
435 for (Int_t j = 0; j <= h3->GetNbinsY() + 1; j++) {
436 for (Int_t k = 0; k <= h3->GetNbinsZ() + 1; k++) {
437 double content = h3->GetBinContent(i, j, k);
438 Int_t p[3] = {i, j, k}; // bin indices in TH3
439 hns->SetBinContent(p, content);
440 }
441 }
442 }
443
444 hns->SetEntries(h3->GetEntries());
445 if (h3->GetSumw2N() > 0) {
446 hns->Sumw2();
447 }
448
449 return hns;
450}
451
452THnSparse * NUtils::ReshapeSparseAxes(THnSparse * hns, std::vector<int> order, std::vector<TAxis *> newAxes,
453 std::vector<int> newPoint, Option_t * option)
454{
458
459 TString opt(option);
460
461 if (hns == nullptr) {
462 NLogError("NUtils::ReshapeSparseAxes: THnSparse hns is null");
463 return nullptr;
464 }
465
466 if (order.empty()) {
467 NLogTrace("NUtils::ReshapeSparseAxes: Order vector is empty");
468 for (long unsigned int i = 0; i < hns->GetNdimensions() + newAxes.size(); i++) {
469 NLogTrace("NUtils::ReshapeSparseAxes: Adding axis %d to order", i);
470 order.push_back(i);
471 }
472 }
473
474 if (order.size() != hns->GetNdimensions() + newAxes.size()) {
475 NLogError("NUtils::ReshapeSparseAxes: Invalid size %d [order] != %d [hns->GetNdimensions()+newAxes]", order.size(),
476 hns->GetNdimensions() + newAxes.size());
477 return nullptr;
478 }
479
480 if (newPoint.empty()) {
481 }
482 else {
483 if (newAxes.size() != newPoint.size()) {
484 NLogError("NUtils::ReshapeSparseAxes: Invalid size %d [newAxes] != %d [newPoint]", newAxes.size(),
485 newPoint.size());
486 return nullptr;
487 }
488 }
489 // loop over order and check if order contains values from 0 to hns->GetNdimensions() + newAxes.size()
490 for (size_t i = 0; i < order.size(); i++) {
491 if (order[i] < 0 || order[i] >= hns->GetNdimensions() + (int)newAxes.size()) {
492 NLogError("NUtils::ReshapeSparseAxes: Invalid order[%d]=%d. Value is negative or higher then "
493 "'hns->GetNdimensions() + newAxes.size()' !!!",
494 i, order[i]);
495 return nullptr;
496 }
497 }
498
499 // check if order contains unique values
500 for (size_t i = 0; i < order.size(); i++) {
501 for (size_t j = i + 1; j < order.size(); j++) {
502 if (order[i] == order[j]) {
503 NLogError("NUtils::ReshapeSparseAxes: Invalid order[%d]=%d and order[%d]=%d. Value is not unique !!!", i,
504 order[i], j, order[j]);
505 return nullptr;
506 }
507 }
508 }
509
510 // print info about original THnSparse
511 // NLogDebug("NUtils::ReshapeSparseAxes: Original THnSparse object:");
512 // hns->Print();
513
514 NLogTrace("NUtils::ReshapeSparseAxes: Reshaping sparse axes ...");
515
516 int nDims = hns->GetNdimensions() + newAxes.size();
517 auto bins = std::make_unique<Int_t[]>(nDims);
518 auto xmin = std::make_unique<Double_t[]>(nDims);
519 auto xmax = std::make_unique<Double_t[]>(nDims);
521 int newAxesIndex = 0;
522 for (int i = 0; i < nDims; i++) {
523 TAxis * a = nullptr;
524 int id = order[i];
525 if (id < hns->GetNdimensions()) {
526 a = hns->GetAxis(id);
527 NLogTrace("NUtils::ReshapeSparseAxes: [ORIG] Axis [%d]->[%d]: %s %s %d %.2f %.2f", id, i, a->GetName(),
528 a->GetTitle(), a->GetNbins(), a->GetXmin(), a->GetXmax());
529 }
530 else {
531 newAxesIndex = id - hns->GetNdimensions();
532 a = newAxes[newAxesIndex];
533 NLogTrace("NUtils::ReshapeSparseAxes: [NEW ] Axis [%d]->[%d]: %s %s %d %.2f %.2f", id, i, a->GetName(),
534 a->GetTitle(), a->GetNbins(), a->GetXmin(), a->GetXmax());
535 }
536 bins[i] = a->GetNbins();
537 xmin[i] = a->GetXmin();
538 xmax[i] = a->GetXmax();
539 }
540
541 THnSparse * hnsNew = new THnSparseD(hns->GetName(), hns->GetTitle(), nDims, bins.get(), xmin.get(), xmax.get());
542
543 // loop over all axes
544 for (int i = 0; i < hnsNew->GetNdimensions(); i++) {
545 TAxis * aIn = nullptr;
546 if (order[i] < hns->GetNdimensions()) {
547 aIn = hns->GetAxis(order[i]);
548 }
549 else {
550 newAxesIndex = order[i] - hns->GetNdimensions();
551 aIn = newAxes[newAxesIndex];
552 }
553
554 TAxis * a = hnsNew->GetAxis(i);
555 a->SetName(aIn->GetName());
556 a->SetTitle(aIn->GetTitle());
557 if (aIn->GetXbins()->GetSize() > 0) {
558 auto arr = std::make_unique<Double_t[]>(aIn->GetNbins() + 1);
559 arr[0] = aIn->GetBinLowEdge(1);
560 for (int iBin = 1; iBin <= aIn->GetNbins(); iBin++) {
561 arr[iBin] = aIn->GetBinUpEdge(iBin);
562 }
563 a->Set(a->GetNbins(), arr.get());
564 }
565
566 // copy bin labels
567 if (aIn->IsAlphanumeric()) {
568 for (int j = 1; j <= aIn->GetNbins(); j++) {
569 const char * label = aIn->GetBinLabel(j);
570 a->SetBinLabel(j, label);
571 }
572 }
573 }
574
575 if (newPoint.empty()) {
576 NLogTrace("NUtils::ReshapeSparseAxes: New point is empty, filling is skipped and doing reset ...");
577 // hnsNew->Reset();
578 // hnsNew->SetEntries(0);
579 return hnsNew;
580 }
581
582 if (hns->GetNbins() > 0) {
583 // loop over all bins
584 NLogTrace("NUtils::ReshapeSparseAxes: Filling all bins ...");
585 for (Long64_t i = 0; i < hns->GetNbins(); i++) {
586 auto p = std::make_unique<Int_t[]>(nDims);
587 auto pNew = std::make_unique<Int_t[]>(nDims);
588 hns->GetBinContent(i, p.get());
589 Double_t v = hns->GetBinContent(i);
590 // remap p to pNew
591 for (int j = 0; j < nDims; j++) {
592 int id = order[j];
593 if (id < hns->GetNdimensions()) {
594 pNew[j] = p[id];
595 }
596 else {
597 newAxesIndex = id - hns->GetNdimensions();
598 pNew[j] = newPoint[newAxesIndex];
599 }
600 }
601 hnsNew->SetBinContent(pNew.get(), v);
602 }
603 hnsNew->SetEntries(hns->GetEntries());
604 }
605 // Calsculate sumw2
606 if (opt.Contains("E")) {
607 NLogTrace("ReshapeSparseAxes: Calculating sumw2 ...");
608 hnsNew->Sumw2();
609 }
610 NLogTrace("ReshapeSparseAxes: Reshaped sparse axes:");
611 // print all axes
612 for (int i = 0; i < nDims; i++) {
613 TAxis * a = hnsNew->GetAxis(i);
614 NLogTrace("ReshapeSparseAxes: Axis %d: %s %s %d %.2f %.2f", i, a->GetName(), a->GetTitle(), a->GetNbins(),
615 a->GetXmin(), a->GetXmax());
616 }
617 // hnsNew->Print("all");
618 return hnsNew;
619}
620
621void NUtils::GetTrueHistogramMinMax(const TH1 * h, double & min_val, double & max_val, bool include_overflow_underflow)
622{
626 if (!h) {
627 max_val = 0.0;
628 min_val = 0.0;
629 return;
630 }
631
632 max_val = -std::numeric_limits<double>::max(); // Initialize with smallest possible double
633 min_val = std::numeric_limits<double>::max(); // Initialize with largest possible double
634
635 int first_bin_x = include_overflow_underflow ? 0 : 1;
636 int last_bin_x = include_overflow_underflow ? h->GetNbinsX() + 1 : h->GetNbinsX();
637
638 int first_bin_y = include_overflow_underflow ? 0 : 1;
639 int last_bin_y = include_overflow_underflow ? h->GetNbinsY() + 1 : h->GetNbinsY();
640
641 int first_bin_z = include_overflow_underflow ? 0 : 1;
642 int last_bin_z = include_overflow_underflow ? h->GetNbinsZ() + 1 : h->GetNbinsZ();
643
644 // Determine the dimensionality of the histogram
645 if (h->GetDimension() == 1) { // TH1
646 for (int i = first_bin_x; i <= last_bin_x; ++i) {
647 double content = h->GetBinContent(i);
648 if (content > max_val) max_val = content;
649 if (content < min_val) min_val = content;
650 }
651 }
652 else if (h->GetDimension() == 2) { // TH2
653 for (int i = first_bin_x; i <= last_bin_x; ++i) {
654 for (int j = first_bin_y; j <= last_bin_y; ++j) {
655 double content = h->GetBinContent(i, j);
656 if (content > max_val) max_val = content;
657 if (content < min_val) min_val = content;
658 }
659 }
660 }
661 else if (h->GetDimension() == 3) { // TH3
662 for (int i = first_bin_x; i <= last_bin_x; ++i) {
663 for (int j = first_bin_y; j <= last_bin_y; ++j) {
664 for (int k = first_bin_z; k <= last_bin_z; ++k) {
665 double content = h->GetBinContent(i, j, k);
666 if (content > max_val) max_val = content;
667 if (content < min_val) min_val = content;
668 }
669 }
670 }
671 }
672 else {
673 NLogWarning("GetTrueHistogramMinMax: Histogram '%s' has unsupported dimension %d. "
674 "Using GetMaximum/GetMinimum as fallback.",
675 h->GetName(), h->GetDimension());
676 // As a fallback, try to get from GetMaximum/GetMinimum if dimension not 1,2,3
677 max_val = h->GetMaximum();
678 min_val = h->GetMinimum();
679 }
680
681 // Handle the case where all bins might be empty or zero
682 if (max_val == -std::numeric_limits<double>::max() && min_val == std::numeric_limits<double>::max()) {
683 max_val = 0.0; // If no content was found, assume 0
684 min_val = 0.0;
685 }
686}
687
688bool NUtils::CreateDirectory(const std::string & path)
689{
695
696 if (path.empty()) return false;
697
698 TString dir(path.c_str());
699 bool isLocalFile = dir.BeginsWith("file://");
700 if (isLocalFile) {
701 dir.ReplaceAll("file://", "");
702 } else {
703 isLocalFile = !dir.Contains("://");
704 }
705
706 if (!isLocalFile) return true; // remote path — nothing to do locally
707
708 std::string pwd = gSystem->pwd();
709 if (dir[0] != '/') dir = (pwd + "/" + std::string(dir.Data())).c_str();
710 dir.ReplaceAll("?remote=1&", "?");
711 dir.ReplaceAll("?remote=1", "");
712 dir.ReplaceAll("&remote=1", "");
713 TUrl url(dir.Data());
714
715 const std::string localDir = url.GetFile();
716 return gSystem->mkdir(localDir.c_str(), kTRUE) == 0;
717}
718
719TFile * NUtils::OpenFile(std::string filename, std::string mode, bool createLocalDir)
720{
724
725 filename = gSystem->ExpandPathName(filename.c_str());
726 if (createLocalDir) {
727 if (!mode.compare("RECREATE") || !mode.compare("UPDATE") || !mode.compare("WRITE")) {
728 const std::string dir = gSystem->GetDirName(filename.c_str()).Data();
729 CreateDirectory(dir);
730 }
731 }
732 return TFile::Open(filename.c_str(), mode.c_str());
733}
734
735std::string NUtils::OpenRawFile(std::string filename)
736{
740
741 std::string content;
742 TFile * f = OpenFile(TString::Format("%s?filetype=raw", filename.c_str()).Data());
743 if (!f) return "";
744
745 // Printf("%lld", f->GetSize());
746
747 int buffsize = 4096;
748 // FIXME: use smart pointer to avoid large stack allocation (check if working)
749 auto buff = std::make_unique<char[]>(buffsize + 1);
750 // char buff[buffsize + 1];
751
752 Long64_t buffread = 0;
753 while (buffread < f->GetSize()) {
754 if (buffread + buffsize > f->GetSize()) buffsize = f->GetSize() - buffread;
755
756 // Printf("Buff %lld %d", buffread, buffsize);
757 f->ReadBuffer(buff.get(), buffread, buffsize);
758 buff[buffsize] = '\0';
759 content += buff.get();
760 buffread += buffsize;
761 }
762 f->Close();
763 return content;
764}
765bool NUtils::SaveRawFile(std::string filename, std::string content)
766{
770
771 TFile * f = OpenFile(TString::Format("%s?filetype=raw", filename.c_str()).Data(), "RECREATE");
772 if (!f) {
773 NLogError("Error: Problem opening file '%s' in 'rw' mode ...", filename.c_str());
774 return false;
775 }
776 f->WriteBuffer(content.c_str(), content.size());
777 f->Close();
778 return true;
779}
780
781TMacro * NUtils::OpenMacro(std::string filename)
782{
786
787 std::string content;
788 if (filename.find("http://") == 0 || filename.find("https://") == 0) {
789 NHttpRequest request;
790 content = request.get(filename);
791 if (content.empty()) {
792 Printf("Error: Problem fetching macro from '%s' ...", filename.c_str());
793 return nullptr;
794 }
795 }
796 else {
797 content = OpenRawFile(filename);
798 if (content.empty()) {
799 Printf("Error: Problem opening macro '%s' ...", filename.c_str());
800 return nullptr;
801 }
802 }
803 Printf("Using macro '%s' ...", filename.c_str());
804 TUrl url(filename.c_str());
805 std::string basefilename = gSystem->BaseName(url.GetFile());
806 basefilename.pop_back();
807 basefilename.pop_back();
808 TMacro * m = new TMacro();
809 m->SetName(basefilename.c_str());
810 m->AddLine(content.c_str());
811 return m;
812}
813
814bool NUtils::LoadJsonFile(json & cfg, std::string filename)
815{
819
820 std::string content = OpenRawFile(filename);
821 if (content.empty()) {
822 NLogError("NUtils::LoadJsonFile: Problem opening JSON file '%s' ...", filename.c_str());
823 return false;
824 }
825
826 try {
827 json myCfg = json::parse(content.c_str());
828 cfg.merge_patch(myCfg);
829 NLogInfo("NUtils::LoadJsonFile: Successfully parsed JSON file '%s' ...", filename.c_str());
830 }
831 catch (json::parse_error & e) {
832 NLogError("NUtils::LoadJsonFile: JSON parse error in file '%s' at byte %d: %s", filename.c_str(), e.byte, e.what());
833 return false;
834 }
835
836 return true;
837}
838
839std::string NUtils::InjectRawJson(json & j, const RawJsonInjections & injections)
840{
841 const std::string kPlaceholderBase = "##RAW_JSON_INJECT_";
842
843 // Set all placeholders with unique index suffixes
844 for (size_t i = 0; i < injections.size(); ++i) {
845 const auto & keys = injections[i].first;
846 if (keys.empty()) {
847 throw std::invalid_argument("Keys array must not be empty at injection index " + std::to_string(i));
848 }
849
850 json * current = &j;
851 for (size_t k = 0; k < keys.size() - 1; ++k) {
852 if (!current->contains(keys[k])) {
853 (*current)[keys[k]] = json::object();
854 }
855 current = &(*current)[keys[k]];
856 }
857 (*current)[keys.back()] = kPlaceholderBase + std::to_string(i) + "##";
858 }
859
860 std::string result = j.dump();
861
862 // Replace each placeholder with the corresponding raw JSON.
863 // Replace all occurrences defensively in case the placeholder appears more than once.
864 for (size_t i = 0; i < injections.size(); ++i) {
865 const std::string quotedPlaceholder = "\"" + kPlaceholderBase + std::to_string(i) + "##\"";
866
867 size_t pos = result.find(quotedPlaceholder);
868 if (pos == std::string::npos) {
869 throw std::runtime_error("Placeholder not found for key path ending in \"" + injections[i].first.back() + "\"");
870 }
871
872 while (pos != std::string::npos) {
873 result.replace(pos, quotedPlaceholder.length(), injections[i].second);
874 pos = result.find(quotedPlaceholder, pos + injections[i].second.size());
875 }
876 }
877
878 return result;
879}
880
881void NUtils::AddRawJsonInjection(json & j, const std::vector<std::string> & path, const std::string & rawJson,
882 const std::string & injectionsKey)
883{
884 if (path.empty()) {
885 throw std::invalid_argument("AddRawJsonInjection: path must not be empty");
886 }
887
888 // Walk to the parent of the target key and capture any existing object members so
889 // they are preserved in the final injected string (merged at string level).
890 json * current = &j;
891 bool pathReachable = true;
892 for (size_t k = 0; k + 1 < path.size(); ++k) {
893 if (!current->is_object() || !current->contains(path[k])) {
894 pathReachable = false;
895 break;
896 }
897 current = &(*current)[path[k]];
898 }
899
900 std::string valueToStore = rawJson;
901 if (pathReachable && current->is_object() && current->contains(path.back())) {
902 const json & existing = (*current)[path.back()];
903 if (existing.is_object() && !existing.empty()) {
904 // Append existing key/value pairs into the raw JSON object string.
905 // Works only when rawJson is itself a JSON object (starts with '{').
906 size_t lastBrace = valueToStore.rfind('}');
907 if (lastBrace != std::string::npos) {
908 std::string extras = existing.dump(); // e.g. {"key":"val"}
909 std::string extraFields = extras.substr(1, extras.size() - 2); // strip outer { }
910 if (!extraFields.empty()) {
911 valueToStore = valueToStore.substr(0, lastBrace) + "," + extraFields + "}";
912 }
913 }
914 }
915 }
916
917 if (!j.contains(injectionsKey) || !j[injectionsKey].is_array()) {
918 j[injectionsKey] = json::array();
919 }
920
921 j[injectionsKey].push_back({{"path", path}, {"value", valueToStore}});
922}
923
924bool NUtils::CollectRawJsonInjections(const json & j, RawJsonInjections & injections, const std::string & injectionsKey)
925{
926 injections.clear();
927 if (!j.contains(injectionsKey) || !j[injectionsKey].is_array()) {
928 return false;
929 }
930
931 for (const auto & entry : j[injectionsKey]) {
932 if (!entry.contains("path") || !entry["path"].is_array() || !entry.contains("value") || !entry["value"].is_string()) {
933 continue;
934 }
935 injections.emplace_back(entry["path"].get<std::vector<std::string>>(), entry["value"].get<std::string>());
936 }
937
938 return !injections.empty();
939}
940
941std::string NUtils::MergeRawJsonWithMetadata(const std::string & rawJson, const json & metadata)
942{
946
947 try {
948 json obj = json::parse(rawJson);
949
950 // Merge metadata fields into the parsed object
951 for (const auto & [key, val] : metadata.items()) {
952 obj[key] = val;
953 }
954
955 return obj.dump();
956 }
957 catch (const std::exception & e) {
958 NLogError("NUtils::MergeRawJsonWithMetadata: Failed to parse raw JSON: %s", e.what());
959 return rawJson; // Return original if parsing fails
960 }
961}
962
963std::vector<std::string> NUtils::Find(std::string path, std::string filename)
964{
968
969 std::vector<std::string> files;
970 TString pathStr = gSystem->ExpandPathName(path.c_str());
971 if (pathStr.IsNull() || filename.empty()) {
972 NLogError("NUtils::Find: Path or filename is empty");
973 return files;
974 }
975
976 if (pathStr.BeginsWith("root://")) {
977 return FindEos(path, filename);
978 }
979 else {
980 return FindLocal(path, filename);
981 }
982
983 return files;
984}
985
986std::vector<std::string> NUtils::FindLocal(std::string path, std::string filename)
987{
991
992 std::vector<std::string> files;
993 if (gSystem->AccessPathName(path.c_str())) {
994 NLogError("NUtils::FindLocal: Path '%s' does not exist", path.c_str());
995 return files;
996 }
997 NLogInfo("Doing find %s -name %s", path.c_str(), filename.c_str());
998 std::string linesMerge =
999 gSystem->GetFromPipe(TString::Format("find %s -name %s", path.c_str(), filename.c_str())).Data();
1000
1001 std::stringstream check2(linesMerge);
1002 std::string line;
1003 while (std::getline(check2, line)) {
1004 files.push_back(line);
1005 }
1006 return files;
1007}
1008std::vector<std::string> NUtils::FindEos(std::string path, std::string filename)
1009{
1013
1014 std::vector<std::string> files;
1015 NLogInfo("Doing eos find -f --name %s %s ", filename.c_str(), path.c_str());
1016
1017 TUrl url(path.c_str());
1018 std::string host = url.GetHost();
1019 std::string directory = url.GetFile();
1020 std::string findUrl = "root://";
1021 findUrl += host + "//proc/user/";
1022 findUrl += "?mgm.cmd=find&mgm.find.match=" + filename;
1023 findUrl += "&mgm.path=" + directory;
1024 findUrl += "&mgm.format=json&mgm.option=f&filetype=raw";
1025 NLogInfo("Doing TFile::Open on '%s' ...", findUrl.c_str());
1026
1027 TFile * f = Ndmspc::NUtils::OpenFile(findUrl.c_str());
1028 if (!f) return files;
1029
1030 // Printf("%lld", f->GetSize());
1031
1032 int buffsize = 4096;
1033 // FIXME: use smart pointer to avoid large stack allocation (check if working)
1034 auto buff = std::make_unique<char[]>(buffsize + 1);
1035 // char buff[buffsize + 1];
1036
1037 Long64_t buffread = 0;
1038 std::string content;
1039 while (buffread < f->GetSize()) {
1040
1041 if (buffread + buffsize > f->GetSize()) buffsize = f->GetSize() - buffread;
1042
1043 // Printf("Buff %lld %d", buffread, buffsize);
1044 f->ReadBuffer(buff.get(), buffread, buffsize);
1045 buff[buffsize] = '\0';
1046 content += buff.get();
1047 buffread += buffsize;
1048 }
1049
1050 f->Close();
1051
1052 std::string ss = "mgm.proc.stdout=";
1053 size_t pos = ss.size() + 1;
1054 content = content.substr(pos);
1055
1056 // stringstream class check1
1057 std::stringstream check1(content);
1058
1059 std::string intermediate;
1060
1061 // Tokenizing w.r.t. space '&'
1062 std::vector<std::string> tokens;
1063 while (getline(check1, intermediate, '&')) {
1064 tokens.push_back(intermediate);
1065 }
1066 std::string linesString = tokens[0];
1067 for (auto & line : NUtils::Tokenize(linesString, '\n')) {
1068 files.push_back("root://" + host + "/" + line);
1069 }
1070 return files;
1071}
1072
1073std::vector<std::string> NUtils::Tokenize(std::string_view input, const char delim)
1074{
1078 std::vector<std::string> out;
1079 size_t start = 0;
1080 size_t end = input.find(delim);
1081
1082 while (end != std::string_view::npos) {
1083 if (end > start) {
1084 out.emplace_back(input.substr(start, end - start));
1085 }
1086 start = end + 1;
1087 end = input.find(delim, start);
1088 }
1089
1090 if (start < input.length()) {
1091 out.emplace_back(input.substr(start));
1092 }
1093 return out;
1094}
1095std::vector<int> NUtils::TokenizeInt(std::string_view input, const char delim)
1096{
1100
1101 std::vector<int> out;
1102 std::vector<std::string> tokens = Tokenize(input, delim);
1103 for (auto & t : tokens) {
1104 if (t.empty()) continue;
1105 out.push_back(std::stoi(t));
1106 }
1107
1108 return out;
1109}
1110
1111std::string NUtils::Join(const std::vector<std::string> & values, const char delim)
1112{
1116
1117 std::string out;
1118 for (const auto & v : values) {
1119 if (!out.empty()) out += delim;
1120 out += v;
1121 }
1122 return out;
1123}
1124std::string NUtils::Join(const std::vector<int> & values, const char delim)
1125{
1129
1130 std::string out;
1131 for (const auto & v : values) {
1132 if (!out.empty()) out += delim;
1133 out += std::to_string(v);
1134 }
1135 return out;
1136}
1137
1138std::vector<std::string> NUtils::Truncate(std::vector<std::string> values, std::string value)
1139{
1143
1144 std::vector<std::string> out;
1145 for (auto & v : values) {
1146 v = std::string(v.begin() + value.size(), v.end());
1147 out.push_back(v);
1148 }
1149 return out;
1150}
1151
1152std::set<std::string> NUtils::Unique(std::vector<std::string> & paths, int axis, std::string path, char token)
1153{
1157
1158 std::set<std::string> out;
1159 std::vector<std::string> truncatedPaths = NUtils::Truncate(paths, path);
1160 for (auto & p : truncatedPaths) {
1161 std::vector<std::string> tokens = Tokenize(p, token);
1162 out.insert(tokens[axis]);
1163 }
1164 return out;
1165}
1166
1167TH1 * NUtils::ProjectTHnSparse(THnSparse * sparse, const std::vector<int> & axes, Option_t * option)
1168{
1172 if (sparse == nullptr) {
1173 NLogError("Error: Sparse is nullptr ...");
1174 return nullptr;
1175 }
1176
1177 TH1 * h = nullptr;
1178 if (axes.size() == 1) {
1179 h = sparse->Projection(axes[0], option);
1180 }
1181 else if (axes.size() == 2) {
1182 h = sparse->Projection(axes[1], axes[0], option);
1183 }
1184 else if (axes.size() == 3) {
1185 h = sparse->Projection(axes[0], axes[1], axes[2], option);
1186 }
1187 else {
1188 NLogError("Error: Only projection onto single axis is supported for TH1 ...");
1189 }
1190
1191 h->SetName(TString::Format("%s_proj", sparse->GetName()).Data());
1192 h->SetTitle(TString::Format("%s Projection", sparse->GetTitle()).Data());
1193 // Detach from gDirectory to prevent TFile from claiming ownership of the histogram.
1194 // Without this, if gDirectory is an open TFile, the TFile will delete this histogram
1195 // when closed, causing a double-free when THStack/canvas tries to clean it up later.
1196 h->SetDirectory(nullptr);
1197
1198 // Set labels for axis
1199 for (size_t i = 0; i < axes.size(); i++) {
1200 TAxis * axisSparse = sparse->GetAxis(axes[i]);
1201 TAxis * axisHist = h->GetXaxis();
1202 if (i == 1) axisHist = h->GetYaxis();
1203 if (i == 2) axisHist = h->GetZaxis();
1204
1205 axisHist->SetName(axisSparse->GetName());
1206 axisHist->SetTitle(axisSparse->GetTitle());
1207
1208 // Copy bin labels if alphanumeric
1209 if (axisSparse->IsAlphanumeric()) {
1210 for (int j = 1; j <= axisSparse->GetNbins(); j++) {
1211 const char * label = axisSparse->GetBinLabel(j);
1212 axisHist->SetBinLabel(j, label);
1213 }
1214 }
1215 }
1216
1217 return h;
1218}
1219
1220bool NUtils::SetAxisRanges(THnSparse * sparse, std::vector<std::vector<int>> ranges, bool withOverflow,
1221 bool modifyTitle, bool reset)
1222{
1227
1228 if (sparse == nullptr) {
1229 NLogError("Error: Sparse is nullptr ...");
1230 return false;
1231 }
1232 if (sparse->GetNdimensions() == 0) return true;
1233
1234 if (reset) {
1235 NLogTrace("Setting axis ranges on '%s' THnSparse ...", sparse->GetName());
1237 for (int i = 0; i < sparse->GetNdimensions(); i++) {
1238 if (withOverflow) {
1239 NLogTrace("Resetting '%s' axis ...", sparse->GetAxis(i)->GetName());
1240 sparse->GetAxis(i)->SetRange(0, 0);
1241 }
1242 else {
1243 NLogTrace("Resetting '%s' axis [%d,%d] ...", sparse->GetAxis(i)->GetName(), 1, sparse->GetAxis(i)->GetNbins());
1244 sparse->GetAxis(i)->SetRange(1, sparse->GetAxis(i)->GetNbins());
1245 }
1246 }
1247 }
1248
1249 if (ranges.empty()) {
1250 NLogTrace("No axis ranges to set ...");
1251 return true;
1252 }
1253
1254 TAxis * axis = nullptr;
1255 TString title = sparse->GetTitle();
1256 if (modifyTitle) title += " Ranges:";
1257 for (size_t i = 0; i < ranges.size(); i++) {
1258 axis = sparse->GetAxis(ranges[i][0]);
1259 NLogTrace("Setting axis range %s=[%d,%d] ...", axis->GetName(), ranges[i][1], ranges[i][2]);
1260 if (ranges[i].size() != 3) {
1261 NLogError("Error: Axis range must have 3 values, but has %zu ...", ranges[i].size());
1262 return false;
1263 }
1264 axis->SetRange(ranges[i][1], ranges[i][2]);
1265 if (axis->IsAlphanumeric()) {
1266
1267 title += TString::Format(" %s[%s]", axis->GetName(), axis->GetBinLabel(ranges[i][1]));
1268 }
1269 else {
1270 title += TString::Format(" %s[%0.2f - %0.2f]", axis->GetName(), axis->GetBinLowEdge(ranges[i][1]),
1271 axis->GetBinUpEdge(ranges[i][2]));
1272 }
1273 }
1274 if (modifyTitle) sparse->SetTitle(title.Data());
1275 return true;
1276}
1277
1278bool NUtils::SetAxisRanges(THnSparse * sparse, std::map<int, std::vector<int>> ranges, bool withOverflow,
1279 bool modifyTitle, bool reset)
1280{
1285
1286 if (sparse == nullptr) {
1287 NLogError("NUtils::SetAxisRanges: Sparse is nullptr ...");
1288 return false;
1289 }
1290 if (sparse->GetNdimensions() == 0) return true;
1291
1292 NLogTrace("NUtils::SetAxisRanges: Setting axis ranges on '%s' THnSparse ...", sparse->GetName());
1293 if (reset) {
1295 for (int i = 0; i < sparse->GetNdimensions(); i++) {
1296 if (withOverflow) {
1297 NLogTrace("NUtils::SetAxisRanges: Resetting '%s' axis ...", sparse->GetAxis(i)->GetName());
1298 sparse->GetAxis(i)->SetRange(0, 0);
1299 }
1300 else {
1301 NLogTrace("NUtils::SetAxisRanges: Resetting '%s' axis [%d,%d] ...", sparse->GetAxis(i)->GetName(), 1,
1302 sparse->GetAxis(i)->GetNbins());
1303 sparse->GetAxis(i)->SetRange(1, sparse->GetAxis(i)->GetNbins());
1304 }
1305 }
1306 }
1307
1308 if (ranges.empty()) {
1309 NLogTrace("NUtils::SetAxisRanges: No axis ranges to set ...");
1310 return true;
1311 }
1312 TAxis * axis = nullptr;
1313 TString title = sparse->GetTitle();
1314 for (const auto & [key, val] : ranges) {
1315 NLogTrace("NUtils::SetAxisRanges: Setting axis range for axis %d to [%d,%d] ...", key, val[0], val[1]);
1316 axis = sparse->GetAxis(key);
1317 if (axis == nullptr) {
1318 NLogError("NUtils::SetAxisRanges: Axis %d is nullptr ...", key);
1319 return false;
1320 }
1321 NLogTrace("NUtils::SetAxisRanges: Setting axis range %s=[%d,%d] ...", axis->GetName(), val[0], val[1]);
1322 axis->SetRange(val[0], val[1]);
1323 if (axis->IsAlphanumeric()) {
1324
1325 title += TString::Format(" %s[%s]", axis->GetName(), axis->GetBinLabel(val[0]));
1326 }
1327 else {
1328 title += TString::Format(" %s[%0.2f - %0.2f]", axis->GetName(), axis->GetBinLowEdge(val[0]),
1329 axis->GetBinUpEdge(val[1]));
1330 }
1331 }
1332
1333 if (modifyTitle) sparse->SetTitle(title.Data());
1334 NLogTrace("NUtils::SetAxisRanges: New title: %s", sparse->GetTitle());
1335
1336 return true;
1337}
1338bool NUtils::GetAxisRangeInBase(TAxis * a, int rebin, int rebin_start, int bin, int & min, int & max)
1339{
1343 if (a == nullptr) {
1344 NLogError("Error: Axis is nullptr ...");
1345 return false;
1346 }
1347 min = -1;
1348 max = -1;
1349
1350 NLogTrace("Getting axis range in base for '%s' rebin=%d rebin_start=%d bin=%d...", a->GetName(), rebin, rebin_start,
1351 bin);
1352
1353 min = rebin * (bin - 1) + rebin_start;
1354 max = min + rebin - 1;
1355 NLogTrace("Axis '%s' min=%d max=%d", a->GetName(), min, max);
1356
1357 if (min < 1) {
1358 NLogError("Error: Axis '%s' min=%d is lower then 1 ...", a->GetName(), min);
1359 min = -1;
1360 max = -1;
1361 return false;
1362 }
1363
1364 if (max > a->GetNbins()) {
1365 NLogError("Error: Axis '%s' max=%d is higher then %d ...", a->GetName(), max, a->GetNbins());
1366 min = -1;
1367 max = -1;
1368 return false;
1369 }
1370
1371 return true;
1372}
1373bool NUtils::GetAxisRangeInBase(TAxis * a, int min, int max, TAxis * base, int & minBase, int & maxBase)
1374{
1379 int rebin = base->GetNbins() / a->GetNbins();
1380
1381 // TODO: Improve handling of rebin_start correctly (depending on axis min and max of first bin)
1382 int rebin_start = (base->GetNbins() % a->GetNbins()) + 1;
1383 rebin_start = rebin != 1 ? rebin_start : 1; // start from 1
1384
1385 NLogTrace("Getting axis range in base for '%s' min=%d max=%d rebin=%d rebin_start=%d...", a->GetName(), min, max,
1386 rebin, rebin_start);
1387
1388 int tmp;
1389 GetAxisRangeInBase(base, rebin, rebin_start, min, minBase, tmp);
1390 GetAxisRangeInBase(base, rebin, rebin_start, max, tmp, maxBase);
1391 NLogTrace("Axis '%s' minBase=%d maxBase=%d", a->GetName(), minBase, maxBase);
1392
1393 return true;
1394}
1395
1396TObjArray * NUtils::AxesFromDirectory(const std::vector<std::string> paths, const std::string & findPath,
1397 const std::string & fileName, const std::vector<std::string> & axesNames)
1398{
1399 if (paths.empty()) {
1400 NLogError("Error: No paths provided ...");
1401 return nullptr;
1402 }
1403
1404 std::map<std::string, std::set<std::string>> axes;
1405 for (const auto & path : paths) {
1406 NLogInfo("Found file: %s", path.c_str());
1407 // remove prefix basePath from path
1408 TString relativePath = path;
1409 relativePath.ReplaceAll(findPath.c_str(), "");
1410 relativePath.ReplaceAll(fileName.c_str(), "");
1411 // relativePath.ReplaceAll("years", "");
1412 // relativePath.ReplaceAll("data", "");
1413 relativePath.ReplaceAll("//", "/");
1414 // remove leading slash
1415 relativePath.Remove(0, relativePath.BeginsWith("/") ? 1 : 0);
1416 // remove trailing slash
1417 relativePath.Remove(relativePath.EndsWith("/") ? relativePath.Length() - 1 : relativePath.Length(), 1);
1418
1419 std::vector<std::string> tokens = Ndmspc::NUtils::Tokenize(relativePath.Data(), '/');
1420
1421 // if (tokens.size() < axesNames.size()) {
1422 // tokens.push_back("mb");
1423 // }
1424 //
1425 if (tokens.size() != axesNames.size()) {
1426 continue;
1427 }
1428
1429 for (size_t i = 0; i < tokens.size(); ++i) {
1430 axes[axesNames[i]].insert(tokens[i]);
1431 }
1432 }
1433
1434 TObjArray * axesArr = new TObjArray();
1435 for (const auto & axisName : axesNames) {
1436 TAxis * axis = Ndmspc::NUtils::CreateAxisFromLabelsSet(axisName, axisName, axes[axisName]); // Convert set to vector
1437 axesArr->Add(axis);
1438 }
1439
1440 return axesArr;
1441}
1442std::string NUtils::GetJsonString(json j)
1443{
1447
1448 if (j.is_string()) {
1449 return j.get<std::string>();
1450 }
1451 else if (j.is_number_integer()) {
1452 return std::to_string(j.get<int>());
1453 }
1454 else if (j.is_number_float()) {
1455 return std::to_string(j.get<double>());
1456 }
1457 else if (j.is_boolean()) {
1458 return j.get<bool>() ? "true" : "false";
1459 }
1460 else if (j.is_null()) {
1461 return "";
1462 }
1463 else {
1464 return "";
1465 }
1466}
1468{
1472
1473 if (j.is_number_integer()) {
1474 return j.get<int>();
1475 }
1476 else if (j.is_number_float()) {
1477 return static_cast<int>(j.get<double>());
1478 }
1479 else if (j.is_boolean()) {
1480 return j.get<bool>() ? 1 : 0;
1481 }
1482 else if (j.is_null()) {
1483 return -1;
1484 }
1485 else {
1486 return -1;
1487 }
1488}
1489
1491{
1495
1496 if (j.is_number_float()) {
1497 return j.get<double>();
1498 }
1499 else if (j.is_number_integer()) {
1500 return static_cast<double>(j.get<int>());
1501 }
1502 else if (j.is_boolean()) {
1503 return j.get<bool>() ? 1.0 : 0.0;
1504 }
1505 else if (j.is_null()) {
1506 return -1.0;
1507 }
1508 else {
1509 return -1.0;
1510 }
1511}
1512
1514{
1518
1519 if (j.is_boolean()) {
1520 return j.get<bool>();
1521 }
1522 else if (j.is_number_integer()) {
1523 return j.get<int>() != 0;
1524 }
1525 else if (j.is_number_float()) {
1526 return j.get<double>() != 0.0;
1527 }
1528 else if (j.is_null()) {
1529 return false;
1530 }
1531 else {
1532 return false;
1533 }
1534}
1535
1536std::vector<std::string> NUtils::GetJsonStringArray(json j)
1537{
1541
1542 std::vector<std::string> out;
1543 if (j.is_array()) {
1544 for (auto & v : j) {
1545 out.push_back(GetJsonString(v));
1546 }
1547 }
1548 return out;
1549}
1550
1551std::vector<int> NUtils::ArrayToVector(Int_t * v1, int size)
1552{
1556
1557 std::vector<int> v2;
1558 for (int i = 0; i < size; i++) {
1559 v2.push_back(v1[i]);
1560 }
1561 return v2;
1562}
1563
1564void NUtils::VectorToArray(std::vector<int> v1, Int_t * v2)
1565{
1569
1570 for (size_t i = 0; i < v1.size(); i++) {
1571 v2[i] = v1[i];
1572 }
1573}
1574std::string NUtils::GetCoordsString(const std::vector<Long64_t> & coords, int index, int width)
1575{
1579 std::stringstream msg;
1580 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1581 msg << "[";
1582 for (size_t i = 0; i < coords.size(); ++i) {
1583 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1584 }
1585 msg << "]";
1586 return msg.str();
1587}
1588std::string NUtils::GetCoordsString(const std::vector<int> & coords, int index, int width)
1589{
1593 std::stringstream msg;
1594 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1595 msg << "[";
1596 for (size_t i = 0; i < coords.size(); ++i) {
1597 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1598 }
1599 msg << "]";
1600 return msg.str();
1601}
1602std::string NUtils::GetCoordsString(const std::vector<size_t> & coords, int index, int width)
1603{
1607 std::stringstream msg;
1608 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1609 msg << "[";
1610 for (size_t i = 0; i < coords.size(); ++i) {
1611 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1612 }
1613 msg << "]";
1614 return msg.str();
1615}
1616std::string NUtils::GetCoordsString(const std::vector<std::string> & coords, int index, int width)
1617{
1621 std::stringstream msg;
1622 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1623 msg << "[";
1624 for (size_t i = 0; i < coords.size(); ++i) {
1625 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1626 }
1627 msg << "]";
1628 return msg.str();
1629}
1630void NUtils::PrintPointSafe(const std::vector<int> & coords, int index)
1631{
1635
1636 NLogInfo("%s", GetCoordsString(coords, index).c_str());
1637}
1638
1639std::vector<std::vector<int>> NUtils::Permutations(const std::vector<int> & v)
1640{
1644 std::vector<std::vector<int>> result;
1645 std::vector<int> current = v;
1646 std::sort(current.begin(), current.end());
1647 do {
1648 result.push_back(current);
1649 } while (std::next_permutation(current.begin(), current.end()));
1650
1651 // print the permutations
1652 NLogTrace("Permutations of vector: %s", GetCoordsString(v).c_str());
1653 for (const auto & perm : result) {
1654 NLogTrace("Permutation: %s", GetCoordsString(perm).c_str());
1655 }
1656
1657 return result;
1658}
1659
1660std::string NUtils::FormatTime(long long seconds)
1661{
1662 long long hours = seconds / 3600;
1663 seconds %= 3600;
1664 long long minutes = seconds / 60;
1665 seconds %= 60;
1666
1667 std::stringstream ss;
1668 ss << std::setw(2) << std::setfill('0') << hours << ":" << std::setw(2) << std::setfill('0') << minutes << ":"
1669 << std::setw(2) << std::setfill('0') << seconds;
1670 return ss.str();
1671}
1672
1673void NUtils::ProgressBar(int current, int total, std::string prefix, std::string suffix, int barWidth)
1674{
1675
1679 if (total == 0) return; // Avoid division by zero
1680
1681 // Let's do protection against any log to be written during progress bar
1682 std::lock_guard<std::mutex> lock(NLogger::GetLoggerMutex());
1683
1684 float percentage = static_cast<float>(current) / total;
1685 int numChars = static_cast<int>(percentage * barWidth);
1686
1687 std::cout << "\r"; // Carriage return
1688 if (!prefix.empty()) std::cout << "[" << prefix << "]"; // Carriage return
1689 std::cout << "["; // Carriage return
1690
1691 for (int i = 0; i < numChars; ++i) {
1692 std::cout << "=";
1693 }
1694 for (int i = 0; i < barWidth - numChars; ++i) {
1695 std::cout << " ";
1696 }
1697 std::cout << "] " << static_cast<int>(percentage * 100.0) << "%"
1698 << " (" << current << "/" << total << ")";
1699 if (!suffix.empty()) std::cout << " [" << suffix << "]";
1700 if (current == total) std::cout << std::endl;
1701 std::cout << std::flush; // Ensure immediate output
1702}
1703
1704void NUtils::ProgressBar(int current, int total, std::chrono::high_resolution_clock::time_point startTime,
1705 std::string prefix, std::string suffix, int barWidth)
1706{
1710 if (total == 0) return; // Avoid division by zero
1711 std::lock_guard<std::mutex> lock(NLogger::GetLoggerMutex());
1712 if (current > total) current = total; // Cap current to total for safety
1713
1714 float percentage = static_cast<float>(current) / total;
1715 int numChars = static_cast<int>(percentage * barWidth);
1716
1717 // std::cout << "\r[" << prefix << "] ["; // Carriage return
1718 std::cout << "\r[";
1719 if (!prefix.empty()) std::cout << prefix << "]["; // Carriage return
1720 for (int i = 0; i < numChars; ++i) {
1721 std::cout << "=";
1722 }
1723 for (int i = 0; i < barWidth - numChars; ++i) {
1724 std::cout << " ";
1725 }
1726 std::cout << "] " << std::setw(3) << static_cast<int>(percentage * 100.0) << "%";
1727
1728 // Calculate elapsed time
1729 auto currentTime = std::chrono::high_resolution_clock::now();
1730 auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(currentTime - startTime).count();
1731
1732 // Calculate estimated remaining time (only if we've made some progress)
1733 long long estimatedRemainingSeconds = 0;
1734 if (current > 0 && percentage > 0) {
1735 // Total estimated time = (elapsed time / current progress) * 100%
1736 long long totalEstimatedSeconds = static_cast<long long>(elapsedSeconds / percentage);
1737 estimatedRemainingSeconds = totalEstimatedSeconds - elapsedSeconds;
1738 }
1739
1740 std::cout << " (" << current << "/" << total << ") "
1741 << "Elapsed: " << FormatTime(elapsedSeconds) << " "
1742 << "ETA: " << FormatTime(estimatedRemainingSeconds);
1743 if (!suffix.empty()) std::cout << " [" << suffix << "]";
1744 if (current == total) std::cout << std::endl;
1745 std::cout << std::flush; // Ensure immediate output
1746}
1747
1748TCanvas * NUtils::CreateCanvas(const std::string & name, const std::string & title, int width, int height)
1749{
1753
1754 TThread::Lock();
1755 TCanvas * c = new TCanvas("", title.c_str(), width, height);
1756 gROOT->GetListOfCanvases()->Remove(c);
1757 c->ResetBit(kMustCleanup);
1758 c->SetBit(kCanDelete, kFALSE);
1759 c->SetName(name.c_str());
1760 TThread::UnLock();
1761 return c;
1762}
1763
1764#ifdef WITH_PARQUET
1765THnSparse * NUtils::CreateSparseFromParquetTaxi(const std::string & filename, THnSparse * hns, Int_t nMaxRows)
1766{
1770 // Open the Parquet file
1771
1772 if (hns == nullptr) {
1773 NLogError("NUtils::CreateSparseFromParquetTaxi: THnSparse 'hns' is nullptr ...");
1774 return nullptr;
1775 }
1776
1777 std::shared_ptr<arrow::io::ReadableFile> infile;
1778 arrow::Result<std::shared_ptr<arrow::io::ReadableFile>> infile_result = arrow::io::ReadableFile::Open(filename);
1779 if (!infile_result.ok()) {
1780 NLogError("NUtils::CreateSparseFromParquetTaxi: Error opening file %s: %s", filename.c_str(),
1781 infile_result.status().ToString().c_str());
1782 return nullptr;
1783 }
1784 infile = infile_result.ValueUnsafe();
1785
1786 // Create a Parquet reader using the modern arrow::Result API
1787 std::unique_ptr<parquet::arrow::FileReader> reader;
1788
1789 // The new approach using arrow::Result:
1790 arrow::Result<std::unique_ptr<parquet::arrow::FileReader>> reader_result =
1791 parquet::arrow::OpenFile(infile, arrow::default_memory_pool()); // No third parameter!
1792 if (!reader_result.ok()) {
1793 NLogError("NUtils::CreateSparseFromParquetTaxi: Error opening Parquet file reader for file %s: %s",
1794 filename.c_str(), reader_result.status().ToString().c_str());
1795 arrow::Status status = infile->Close(); // Attempt to close
1796 return nullptr;
1797 }
1798 reader = std::move(reader_result).ValueUnsafe(); // Transfer ownership from Result to unique_ptr
1799
1800 // Get file metadata (optional)
1801 // Note: parquet_reader() returns a const ptr, and metadata() returns a shared_ptr
1802 std::shared_ptr<parquet::FileMetaData> file_metadata = reader->parquet_reader()->metadata();
1803 NLogTrace("Parquet file '%s' opened successfully.", filename.c_str());
1804 NLogTrace("Parquet file version: %d", file_metadata->version());
1805 NLogTrace("Parquet created by: %s", file_metadata->created_by().c_str());
1806 NLogTrace("Parquet number of columns: %d", file_metadata->num_columns());
1807 NLogTrace("Parquet number of rows: %lld", file_metadata->num_rows());
1808 NLogTrace("Parquet number of row groups: %d", file_metadata->num_row_groups());
1809
1810 // Read the entire file as a Table
1811 // std::shared_ptr<arrow::Table> table;
1812 // arrow::Status status = reader->ReadTable(&table); // ReadTable still returns Status
1813 std::shared_ptr<arrow::RecordBatchReader> batch_reader;
1814 arrow::Status status = reader->GetRecordBatchReader(&batch_reader);
1815 if (!status.ok()) {
1816 NLogError("NUtils::CreateSparseFromParquetTaxi: Error reading table from Parquet file %s: %s", filename.c_str(),
1817 status.ToString().c_str());
1818 status = infile->Close();
1819 return nullptr;
1820 }
1821
1822 // It's good practice to close the input file stream when done
1823 status = infile->Close();
1824 if (!status.ok()) {
1825 NLogWarning("NUtils::CreateSparseFromParquetTaxi: Error closing input file %s: %s", filename.c_str(),
1826 status.ToString().c_str());
1827 // This is a warning, we still want to return the table.
1828 }
1829
1830 // Print schema of the table
1831 NLogTrace("Parquet Table Schema:\n%s", batch_reader->schema()->ToString().c_str());
1832
1833 const Int_t nDims = hns->GetNdimensions();
1834 std::vector<std::string> column_names;
1835 for (int i = 0; i < nDims; ++i) {
1836 column_names.push_back(hns->GetAxis(i)->GetName());
1837 }
1838 // std::cout << "\nData (first 5 rows):\n";
1839
1840 // int max_rows = table->num_rows();
1841 int max_rows = 1e8;
1842 max_rows = nMaxRows > 0 ? std::min(max_rows, nMaxRows) : max_rows;
1843 int print_rows = std::min(max_rows, 5);
1844 // auto table_batch_reader = std::make_shared<arrow::TableBatchReader>(*table);
1845 auto table_batch_reader = batch_reader;
1846 std::shared_ptr<arrow::RecordBatch> batch;
1847 auto point = std::make_unique<Double_t[]>(nDims);
1848 // Double_t point[nDims];
1849
1850 if (print_rows > 0) {
1851 NLogTrace("Printing first %d rows of Parquet file '%s' ...", print_rows, filename.c_str());
1852 // NLogInfo("Columns: %s", NUtils::Join(column_names, '\t').c_str());
1853 }
1854
1855 int batch_count = 0;
1856 while (table_batch_reader->ReadNext(&batch).ok() && batch) {
1857 batch_count++;
1858 NLogTrace("Processing batch with %d rows and %d columns ...", batch->num_rows(), batch->num_columns());
1859 for (int i = 0; i < batch->num_rows(); ++i) {
1860 if (i >= max_rows) break; // Limit to first 5 rows for display
1861
1862 bool isValid = true;
1863 int idx = 0;
1864 for (int j = 0; j < batch->num_columns(); ++j) {
1865 if (std::find(column_names.begin(), column_names.end(), batch->column_name(j)) == column_names.end())
1866 continue; // Skip columns not in our list
1867 // NLogDebug("[%d %s]Processing row %d, column '%s' ...", idx, hns->GetAxis(idx)->GetName(), i,
1868 // batch->column_name(j).c_str());
1869 // std::cout << batch->column_name(j) << "\t";
1870 const auto & array = batch->column(j);
1871 arrow::Result<std::shared_ptr<arrow::Scalar>> scalar_result = array->GetScalar(i);
1872 if (scalar_result.ok()) {
1873 // if (i * batch_count < print_rows) std::cout << scalar_result.ValueUnsafe()->ToString() << "\t";
1874 if (scalar_result.ValueUnsafe()->is_valid) {
1875 TAxis * axis = hns->GetAxis(idx);
1876 if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::STRING ||
1877 scalar_result.ValueUnsafe()->type->id() == arrow::Type::LARGE_STRING) {
1878 // Arrow StringScalar's value is an arrow::util::string_view or arrow::util::string_view
1879 // It's best to convert it to std::string for general use.
1880 std::string value = scalar_result.ValueUnsafe()->ToString();
1881 // TODO: check if not shifted by one
1882 // NLogInfo("NUtils::CreateSparseFromParquetTaxi: Mapping string value '%s' to axis '%s' ...",
1883 // value.c_str(), axis->GetName());
1884 point[idx] = axis->GetBinCenter(axis->FindBin(value.c_str()));
1885 }
1886 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::INT32) {
1887 auto int_scalar = std::static_pointer_cast<arrow::Int32Scalar>(scalar_result.ValueUnsafe());
1888
1889 point[idx] = static_cast<Double_t>(int_scalar->value);
1890 }
1891 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::INT64) {
1892 auto int64_scalar = std::static_pointer_cast<arrow::Int64Scalar>(scalar_result.ValueUnsafe());
1893 point[idx] = static_cast<Double_t>(int64_scalar->value);
1894 }
1895 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::UINT32) {
1896 auto uint32_scalar = std::static_pointer_cast<arrow::UInt32Scalar>(scalar_result.ValueUnsafe());
1897 point[idx] = static_cast<Double_t>(uint32_scalar->value);
1898 }
1899 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::FLOAT) {
1900 auto float_scalar = std::static_pointer_cast<arrow::FloatScalar>(scalar_result.ValueUnsafe());
1901 point[idx] = static_cast<Double_t>(float_scalar->value);
1902 }
1903 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::DOUBLE) {
1904 auto double_scalar = std::static_pointer_cast<arrow::DoubleScalar>(scalar_result.ValueUnsafe());
1905 point[idx] = double_scalar->value;
1906 }
1907 else {
1908 NLogError("NUtils::CreateSparseFromParquetTaxi: Unsupported data type for column '%s' ...",
1909 batch->column_name(j).c_str());
1910 isValid = false;
1911 }
1912 }
1913 else {
1914 // Handle null values (set to 0 or some default)
1915 //
1916 //
1917 point[idx] = -1000;
1918 isValid = false;
1919 isValid = true;
1920 }
1921 }
1922 else {
1923 NLogError("NUtils::CreateSparseFromParquetTaxi: Error getting scalar at (%d,%d): %s", i, j,
1924 scalar_result.status().ToString().c_str());
1925 isValid = false;
1926 }
1927 idx++;
1928 }
1929 // if (i * batch_count < print_rows) std::cout << std::endl;
1930 if (isValid) {
1931 // print point
1932 // for (int d = 0; d < nDims; ++d) {
1933 // NLogDebug("Point[%d=%s]=%f", d, hns->GetAxis(d)->GetName(), point[d]);
1934 // }
1935 hns->Fill(point.get());
1936 }
1937 else {
1938 NLogWarning("Skipping row %d due to invalid data.", i);
1939 }
1940 }
1941 }
1942 return hns;
1943}
1944#else
1945THnSparse * NUtils::CreateSparseFromParquetTaxi(const std::string & /*filename*/, THnSparse * /*hns*/,
1946 Int_t /*nMaxRows*/)
1947{
1948 NLogError("Parquet support is not enabled. Please compile with Parquet support.");
1949 return nullptr;
1950}
1951#endif
1952
1953void NUtils::SafeDeleteObjects(std::vector<TObject *> & objects)
1954{
1955 if (objects.empty()) return;
1956
1957 // With EnableThreadSafety(), every TList has fUsingRWLock=true, so
1958 // TList::Clear() acquires gCoreMutex and calls GarbageCollect() on each
1959 // element. When deleting a TCanvas, the cascade TPad::Close() →
1960 // fPrimitives->Clear() → GarbageCollect() crashes.
1961 //
1962 // Fix: fully disarm every pad's primitive list (disable RW lock, mark
1963 // non-owning, remove all links with "nodelete"), then delete the objects.
1964 // Orphaned primitives (histograms, frames, sub-pads) are collected and
1965 // deleted manually afterwards.
1966
1967 Bool_t prevMustClean = gROOT->MustClean();
1968 gROOT->SetMustClean(kFALSE);
1969
1970 // Collect all pads breadth-first (top-level + nested sub-pads)
1971 std::vector<TPad *> pads;
1972 for (auto * obj : objects) {
1973 if (obj && obj->InheritsFrom(TPad::Class())) pads.push_back(static_cast<TPad *>(obj));
1974 }
1975 for (size_t i = 0; i < pads.size(); ++i) {
1976 TList * prims = pads[i]->GetListOfPrimitives();
1977 if (!prims || prims->IsEmpty()) continue;
1978 for (TObjLink * lnk = prims->FirstLink(); lnk; lnk = lnk->Next()) {
1979 TObject * child = lnk->GetObject();
1980 if (child && child->InheritsFrom(TPad::Class())) pads.push_back(static_cast<TPad *>(child));
1981 }
1982 }
1983
1984 // Track input objects to avoid double-deleting shared primitives
1985 std::set<TObject *> inputSet(objects.begin(), objects.end());
1986 inputSet.erase(nullptr);
1987
1988 // Disarm all pad primitive lists (deepest first) and collect orphans
1989 std::set<TObject *> orphans;
1990 for (auto it = pads.rbegin(); it != pads.rend(); ++it) {
1991 TList * prims = (*it)->GetListOfPrimitives();
1992 if (!prims) continue;
1993 // Collect primitives not in the input vector — they'd leak otherwise
1994 for (TObjLink * lnk = prims->FirstLink(); lnk; lnk = lnk->Next()) {
1995 TObject * child = lnk->GetObject();
1996 if (child && inputSet.find(child) == inputSet.end()) orphans.insert(child);
1997 }
1998 // Disarm: no lock, non-owning, remove links only (no GarbageCollect)
1999 prims->UseRWLock(kFALSE);
2000 prims->SetOwner(kFALSE);
2001 prims->Clear("nodelete");
2002 }
2003
2004 // Delete all input objects (canvas/pad destructors find empty primitive lists)
2005 for (auto * obj : objects) {
2006 if (obj) delete obj;
2007 }
2008 objects.clear();
2009
2010 // Delete orphaned primitives (histograms, frames, sub-pads extracted above)
2011 for (auto * obj : orphans) {
2012 delete obj;
2013 }
2014
2015 gROOT->SetMustClean(prevMustClean);
2016}
2017
2018void NUtils::SafeDeleteTList(TList *& lst)
2019{
2020 if (!lst) return;
2021
2022 // Extract objects from the TList into a vector
2023 std::vector<TObject *> objects;
2024 for (TObjLink * lnk = lst->FirstLink(); lnk; lnk = lnk->Next()) {
2025 TObject * obj = lnk->GetObject();
2026 if (obj) objects.push_back(obj);
2027 }
2028
2029 // Destroy the TList shell without touching objects
2030 lst->UseRWLock(kFALSE);
2031 lst->SetOwner(kFALSE);
2032 lst->Clear("nodelete");
2033 delete lst;
2034 lst = nullptr;
2035
2036 // Delete all collected objects safely
2037 SafeDeleteObjects(objects);
2038}
2039
2040void NUtils::SafeDeleteObject(TObject *& obj)
2041{
2042 if (!obj) return;
2043
2044 if (obj->InheritsFrom(TList::Class())) {
2045 TList * lst = static_cast<TList *>(obj);
2046 obj = nullptr;
2047 SafeDeleteTList(lst);
2048 }
2049 else {
2050 delete obj;
2051 obj = nullptr;
2052 }
2053}
2054
2056{
2057 json out;
2058 ProcInfo_t info;
2059 gSystem->GetProcInfo(&info);
2060
2061 out["cpu_user"] = info.fCpuUser;
2062 out["cpu_sys"] = info.fCpuSys;
2063 out["cpu_total"] = info.fCpuUser + info.fCpuSys;
2064 out["mem_rss_kb"] = info.fMemResident;
2065 out["mem_vsize_kb"] = info.fMemVirtual;
2066
2067 // Report number of logical CPUs available on the host
2068 unsigned int hc = std::thread::hardware_concurrency();
2069 out["cpu_count"] = (hc == 0) ? 1 : static_cast<int>(hc);
2070
2071 return out;
2072}
2073
2075{
2076 json out;
2077 out["totalRead"] = 0LL;
2078 out["totalWritten"] = 0LL;
2079
2080 TList * files = (TList *)gROOT->GetListOfFiles();
2081 if (!files) return out;
2082
2083 Long64_t totalRead = 0;
2084 Long64_t totalWritten = 0;
2085
2086 TIter next(files);
2087 TObject * obj = nullptr;
2088 while ((obj = next())) {
2089 TFile * f = dynamic_cast<TFile *>(obj);
2090 if (!f) continue;
2091 json fi;
2092 fi["name"] = f->GetName() ? f->GetName() : "";
2093 fi["isZombie"] = (bool)f->IsZombie();
2094 fi["isOpen"] = (bool)f->IsOpen();
2095
2096 // Try to read per-file counters if available in this ROOT build
2097 Long64_t bytesRead = 0;
2098 Long64_t bytesWritten = 0;
2099 // Many ROOT versions expose GetBytesRead/GetBytesWritten on TFile; attempt to call them.
2100 // If they are not available, these calls will fail to link — in that case, users
2101 // can replace this implementation with a platform-specific /proc reader.
2102#if 1
2103 // Use C-style cast to call methods if they exist; rely on linker to resolve.
2104 // If unavailable, these lines may need adjustment for older ROOT versions.
2105 try {
2106 bytesRead = f->GetBytesRead();
2107 bytesWritten = f->GetBytesWritten();
2108 }
2109 catch (...) {
2110 bytesRead = 0;
2111 bytesWritten = 0;
2112 }
2113#endif
2114
2115 fi["bytesRead"] = bytesRead;
2116 fi["bytesWritten"] = bytesWritten;
2117
2118 totalRead += bytesRead;
2119 totalWritten += bytesWritten;
2120
2121 out["files"].push_back(fi);
2122 }
2123
2124 out["totalRead"] = totalRead;
2125 out["totalWritten"] = totalWritten;
2126
2127 return out;
2128}
2129
2131{
2132 json out;
2133 out["total_rx"] = 0ULL;
2134 out["total_tx"] = 0ULL;
2135
2136#if defined(__linux__)
2137 std::ifstream f("/proc/net/dev");
2138 if (!f.good()) return out;
2139 std::string line;
2140 // skip headers
2141 std::getline(f, line);
2142 std::getline(f, line);
2143 while (std::getline(f, line)) {
2144 if (line.empty()) continue;
2145 size_t colon = line.find(':');
2146 if (colon == std::string::npos) continue;
2147 std::string ifname = line.substr(0, colon);
2148 // trim
2149 auto ltrim = [](std::string & s) {
2150 size_t start = s.find_first_not_of(" \t");
2151 if (start != std::string::npos)
2152 s = s.substr(start);
2153 else
2154 s.clear();
2155 };
2156 auto rtrim = [](std::string & s) {
2157 size_t end = s.find_last_not_of(" \t");
2158 if (end != std::string::npos)
2159 s = s.substr(0, end + 1);
2160 else
2161 s.clear();
2162 };
2163 ltrim(ifname);
2164 rtrim(ifname);
2165 std::string rest = line.substr(colon + 1);
2166 std::stringstream ss(rest);
2167 std::vector<unsigned long long> vals;
2168 std::string tok;
2169 while (ss >> tok) {
2170 try {
2171 vals.push_back(std::stoull(tok));
2172 }
2173 catch (...) {
2174 vals.push_back(0ULL);
2175 }
2176 }
2177 if (vals.size() >= 9) {
2178 unsigned long long rx = vals[0];
2179 unsigned long long tx = vals[8];
2180 json iface;
2181 iface["name"] = ifname;
2182 iface["rx"] = rx;
2183 iface["tx"] = tx;
2184 out["interfaces"].push_back(iface);
2185 out["total_rx"] = static_cast<unsigned long long>(
2186 out["total_rx"].is_null() ? 0ULL : out["total_rx"].get<unsigned long long>()) +
2187 rx;
2188 out["total_tx"] = static_cast<unsigned long long>(
2189 out["total_tx"].is_null() ? 0ULL : out["total_tx"].get<unsigned long long>()) +
2190 tx;
2191 }
2192 }
2193
2194#elif defined(__APPLE__)
2195 struct ifaddrs * ifap = nullptr;
2196 if (getifaddrs(&ifap) != 0) return out;
2197 for (struct ifaddrs * ifa = ifap; ifa; ifa = ifa->ifa_next) {
2198 if (!ifa->ifa_data) continue;
2199 struct if_data * ifd = (struct if_data *)ifa->ifa_data;
2200 if (!ifd) continue;
2201 unsigned long long rx = (unsigned long long)ifd->ifi_ibytes;
2202 unsigned long long tx = (unsigned long long)ifd->ifi_obytes;
2203 json iface;
2204 iface["name"] = ifa->ifa_name ? ifa->ifa_name : std::string();
2205 iface["rx"] = rx;
2206 iface["tx"] = tx;
2207 out["interfaces"].push_back(iface);
2208 out["total_rx"] =
2209 static_cast<unsigned long long>(out["total_rx"].is_null() ? 0ULL : out["total_rx"].get<unsigned long long>()) +
2210 rx;
2211 out["total_tx"] =
2212 static_cast<unsigned long long>(out["total_tx"].is_null() ? 0ULL : out["total_tx"].get<unsigned long long>()) +
2213 tx;
2214 }
2215 freeifaddrs(ifap);
2216#else
2217 // Unsupported platform: return empty totals
2218#endif
2219
2220 return out;
2221}
2222
2223} // namespace Ndmspc
Provides HTTP request functionality using libcurl.
std::string get(const std::string &url, const std::string &cert_path="", const std::string &key_path="", const std::string &key_password_file="", bool insecure=false)
Performs an HTTP GET request.
int head(const std::string &url, const std::string &cert_path="", const std::string &key_path="", const std::string &key_password_file="", bool insecure=false)
Performs an HTTP HEAD request.
static std::mutex & GetLoggerMutex()
Get logger mutex reference.
Definition NLogger.h:551
Utility class providing static helper functions for file operations, histogram manipulations,...
Definition NUtils.h:25
static void GetTrueHistogramMinMax(const TH1 *h, double &min_val, double &max_val, bool include_overflow_underflow=false)
Get minimum and maximum value of histogram bins.
Definition NUtils.cxx:621
static TFile * OpenFile(std::string filename, std::string mode="READ", bool createLocalDir=true)
Open a ROOT file.
Definition NUtils.cxx:719
static void AddRawJsonInjection(json &j, const std::vector< std::string > &path, const std::string &rawJson, const std::string &injectionsKey="__raw_json_injections")
Add one raw JSON injection entry into metadata field.
Definition NUtils.cxx:881
static bool SetAxisRanges(THnSparse *sparse, std::vector< std::vector< int > > ranges={}, bool withOverflow=false, bool modifyTitle=false, bool reset=true)
Set axis ranges for THnSparse using vector of ranges.
Definition NUtils.cxx:1220
static std::vector< std::string > Truncate(std::vector< std::string > values, std::string value)
Truncate vector of strings by a value.
Definition NUtils.cxx:1138
static TH1 * ProjectTHnSparse(THnSparse *hns, const std::vector< int > &axes, Option_t *option="")
Project a THnSparse histogram onto specified axes.
Definition NUtils.cxx:1167
static bool IsFileSupported(std::string filename)
Check if a file is supported.
Definition NUtils.cxx:97
static std::vector< std::string > FindEos(std::string path, std::string filename="")
Find EOS files in a path matching filename.
Definition NUtils.cxx:1008
static bool LoadJsonFile(json &cfg, std::string filename)
Loads a JSON configuration file into the provided json object.
Definition NUtils.cxx:814
static json GetTFileIOStats()
Get TFile read/write statistics by inspecting ROOT's list of open files.
Definition NUtils.cxx:2074
static bool SaveRawFile(std::string filename, std::string content)
Save content to a raw file.
Definition NUtils.cxx:765
static std::string OpenRawFile(std::string filename)
Open a raw file and return its content as string.
Definition NUtils.cxx:735
static std::vector< std::string > FindLocal(std::string path, std::string filename="")
Find local files in a path matching filename.
Definition NUtils.cxx:986
static void SafeDeleteTList(TList *&lst)
Safely delete a TList and all its contents, bypassing ROOT's GarbageCollect.
Definition NUtils.cxx:2018
static void PrintPointSafe(const std::vector< int > &coords, int index=-1)
Print coordinates safely.
Definition NUtils.cxx:1630
static TCanvas * CreateCanvas(const std::string &name, const std::string &title, int width=800, int height=600)
Create a ROOT TCanvas with specified name, title, and dimensions.
Definition NUtils.cxx:1748
static std::string MergeRawJsonWithMetadata(const std::string &rawJson, const json &metadata)
Merge raw JSON string with metadata fields.
Definition NUtils.cxx:941
static THnSparse * ReshapeSparseAxes(THnSparse *hns, std::vector< int > order, std::vector< TAxis * > newAxes={}, std::vector< int > newPoint={}, Option_t *option="E")
Reshape axes of THnSparse.
Definition NUtils.cxx:452
static json GetNetDevStats()
Get system-wide network interface totals (RX/TX bytes) in a cross-platform way. On Linux reads /proc/...
Definition NUtils.cxx:2130
static bool AccessPathName(std::string path)
Check if a path is accessible.
Definition NUtils.cxx:115
static std::vector< int > TokenizeInt(std::string_view input, const char delim)
Tokenize a string into integers by delimiter.
Definition NUtils.cxx:1095
static THnSparse * Convert(TH1 *h1, std::vector< std::string > names={}, std::vector< std::string > titles={})
Convert TH1 to THnSparse.
Definition NUtils.cxx:221
static TMacro * OpenMacro(std::string filename)
Open a macro file.
Definition NUtils.cxx:781
static std::vector< std::string > Tokenize(std::string_view input, const char delim)
Tokenize a string by delimiter.
Definition NUtils.cxx:1073
static bool CreateDirectory(const std::string &path)
Definition NUtils.cxx:688
static std::string FormatTime(long long seconds)
Format time in seconds to human-readable string.
Definition NUtils.cxx:1660
static TAxis * CreateAxisFromLabels(const std::string &name, const std::string &title, const std::vector< std::string > &labels)
Create a TAxis from a list of labels.
Definition NUtils.cxx:185
static bool GetAxisRangeInBase(TAxis *a, int rebin, int rebin_start, int bin, int &min, int &max)
Get axis range in base for rebinned axis.
Definition NUtils.cxx:1338
static json GetSystemStats()
Get process CPU and RSS memory statistics using ROOT's gSystem::GetProcInfo.
Definition NUtils.cxx:2055
static std::set< std::string > Unique(std::vector< std::string > &paths, int axis, std::string path, char token='/')
Get unique values from vector of strings at specified axis.
Definition NUtils.cxx:1152
static std::vector< std::string > GetJsonStringArray(json j)
Get JSON value as array of strings.
Definition NUtils.cxx:1536
static std::string GetJsonString(json j)
Get JSON value as string.
Definition NUtils.cxx:1442
static int Cp(std::string source, std::string destination, Bool_t progressbar=kTRUE)
Copy a file from source to destination.
Definition NUtils.cxx:155
static bool CollectRawJsonInjections(const json &j, RawJsonInjections &injections, const std::string &injectionsKey="__raw_json_injections")
Collect raw JSON injection entries from metadata field.
Definition NUtils.cxx:924
static bool EnableMT(Int_t numthreads=-1)
Enable multi-threading with specified number of threads.
Definition NUtils.cxx:46
static int GetJsonInt(json j)
Get JSON value as integer.
Definition NUtils.cxx:1467
static std::string Join(const std::vector< std::string > &values, const char delim=',')
Join vector of strings into a single string with delimiter.
Definition NUtils.cxx:1111
static void ProgressBar(int current, int total, std::string prefix="", std::string suffix="", int barWidth=50)
Display progress bar.
Definition NUtils.cxx:1673
static std::string GetCoordsString(const std::vector< int > &coords, int index=-1, int width=0)
Get string representation of coordinates.
Definition NUtils.cxx:1588
static void SafeDeleteObjects(std::vector< TObject * > &objects)
Safely delete a vector of ROOT objects, bypassing GarbageCollect.
Definition NUtils.cxx:1953
static THnSparse * CreateSparseFromParquetTaxi(const std::string &filename, THnSparse *hns=nullptr, Int_t nMaxRows=-1)
Create THnSparse from Parquet Taxi file.
Definition NUtils.cxx:1945
static void SafeDeleteObject(TObject *&obj)
Safely delete a TObject, handling TList contents and TCanvas/TPad cleanup.
Definition NUtils.cxx:2040
static void VectorToArray(std::vector< int > v1, Int_t *v2)
Convert vector to array.
Definition NUtils.cxx:1564
static std::vector< int > ArrayToVector(Int_t *v1, int size)
Convert array to vector.
Definition NUtils.cxx:1551
static double GetJsonDouble(json j)
Get JSON value as double.
Definition NUtils.cxx:1490
static bool GetJsonBool(json j)
Get JSON value as boolean.
Definition NUtils.cxx:1513
static TObjArray * AxesFromDirectory(const std::vector< std::string > paths, const std::string &findPath, const std::string &fileName, const std::vector< std::string > &axesNames)
Creates an array of axes objects from files in specified directories.
Definition NUtils.cxx:1396
static std::vector< std::string > Find(std::string path, std::string filename="")
Find files in a path matching filename.
Definition NUtils.cxx:963
static std::vector< std::vector< int > > Permutations(const std::vector< int > &v)
Generate all permutations of a vector.
Definition NUtils.cxx:1639
static TAxis * CreateAxisFromLabelsSet(const std::string &name, const std::string &title, const std::set< std::string > &labels)
Create a TAxis from a set of labels.
Definition NUtils.cxx:202
static std::string InjectRawJson(json &j, const RawJsonInjections &injections)
Definition NUtils.cxx:839
Global callback function for libwebsockets client events.