ndmspc v1.2.0-0.1.rc5
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 // process's environment before the ?filetype=raw suffix is appended.
798 TString expanded(filename.c_str());
799 gSystem->ExpandPathName(expanded);
800 filename = expanded.Data();
801 content = OpenRawFile(filename);
802 if (content.empty()) {
803 Printf("Error: Problem opening macro '%s' ...", filename.c_str());
804 return nullptr;
805 }
806 }
807 Printf("Using macro '%s' ...", filename.c_str());
808 TUrl url(filename.c_str());
809 std::string basefilename = gSystem->BaseName(url.GetFile());
810 basefilename.pop_back();
811 basefilename.pop_back();
812 TMacro * m = new TMacro();
813 m->SetName(basefilename.c_str());
814 m->AddLine(content.c_str());
815 return m;
816}
817
818bool NUtils::LoadJsonFile(json & cfg, std::string filename)
819{
823
824 std::string content = OpenRawFile(filename);
825 if (content.empty()) {
826 NLogError("NUtils::LoadJsonFile: Problem opening JSON file '%s' ...", filename.c_str());
827 return false;
828 }
829
830 try {
831 json myCfg = json::parse(content.c_str());
832 cfg.merge_patch(myCfg);
833 NLogInfo("NUtils::LoadJsonFile: Successfully parsed JSON file '%s' ...", filename.c_str());
834 }
835 catch (json::parse_error & e) {
836 NLogError("NUtils::LoadJsonFile: JSON parse error in file '%s' at byte %d: %s", filename.c_str(), e.byte, e.what());
837 return false;
838 }
839
840 return true;
841}
842
843std::string NUtils::InjectRawJson(json & j, const RawJsonInjections & injections)
844{
845 const std::string kPlaceholderBase = "##RAW_JSON_INJECT_";
846
847 // Set all placeholders with unique index suffixes
848 for (size_t i = 0; i < injections.size(); ++i) {
849 const auto & keys = injections[i].first;
850 if (keys.empty()) {
851 throw std::invalid_argument("Keys array must not be empty at injection index " + std::to_string(i));
852 }
853
854 json * current = &j;
855 for (size_t k = 0; k < keys.size() - 1; ++k) {
856 if (!current->contains(keys[k])) {
857 (*current)[keys[k]] = json::object();
858 }
859 current = &(*current)[keys[k]];
860 }
861 (*current)[keys.back()] = kPlaceholderBase + std::to_string(i) + "##";
862 }
863
864 std::string result = j.dump();
865
866 // Replace each placeholder with the corresponding raw JSON.
867 // Replace all occurrences defensively in case the placeholder appears more than once.
868 for (size_t i = 0; i < injections.size(); ++i) {
869 const std::string quotedPlaceholder = "\"" + kPlaceholderBase + std::to_string(i) + "##\"";
870
871 size_t pos = result.find(quotedPlaceholder);
872 if (pos == std::string::npos) {
873 throw std::runtime_error("Placeholder not found for key path ending in \"" + injections[i].first.back() + "\"");
874 }
875
876 while (pos != std::string::npos) {
877 result.replace(pos, quotedPlaceholder.length(), injections[i].second);
878 pos = result.find(quotedPlaceholder, pos + injections[i].second.size());
879 }
880 }
881
882 return result;
883}
884
885void NUtils::AddRawJsonInjection(json & j, const std::vector<std::string> & path, const std::string & rawJson,
886 const std::string & injectionsKey)
887{
888 if (path.empty()) {
889 throw std::invalid_argument("AddRawJsonInjection: path must not be empty");
890 }
891
892 // Walk to the parent of the target key and capture any existing object members so
893 // they are preserved in the final injected string (merged at string level).
894 json * current = &j;
895 bool pathReachable = true;
896 for (size_t k = 0; k + 1 < path.size(); ++k) {
897 if (!current->is_object() || !current->contains(path[k])) {
898 pathReachable = false;
899 break;
900 }
901 current = &(*current)[path[k]];
902 }
903
904 std::string valueToStore = rawJson;
905 if (pathReachable && current->is_object() && current->contains(path.back())) {
906 const json & existing = (*current)[path.back()];
907 if (existing.is_object() && !existing.empty()) {
908 // Append existing key/value pairs into the raw JSON object string.
909 // Works only when rawJson is itself a JSON object (starts with '{').
910 size_t lastBrace = valueToStore.rfind('}');
911 if (lastBrace != std::string::npos) {
912 std::string extras = existing.dump(); // e.g. {"key":"val"}
913 std::string extraFields = extras.substr(1, extras.size() - 2); // strip outer { }
914 if (!extraFields.empty()) {
915 valueToStore = valueToStore.substr(0, lastBrace) + "," + extraFields + "}";
916 }
917 }
918 }
919 }
920
921 if (!j.contains(injectionsKey) || !j[injectionsKey].is_array()) {
922 j[injectionsKey] = json::array();
923 }
924
925 j[injectionsKey].push_back({{"path", path}, {"value", valueToStore}});
926}
927
928bool NUtils::CollectRawJsonInjections(const json & j, RawJsonInjections & injections, const std::string & injectionsKey)
929{
930 injections.clear();
931 if (!j.contains(injectionsKey) || !j[injectionsKey].is_array()) {
932 return false;
933 }
934
935 for (const auto & entry : j[injectionsKey]) {
936 if (!entry.contains("path") || !entry["path"].is_array() || !entry.contains("value") || !entry["value"].is_string()) {
937 continue;
938 }
939 injections.emplace_back(entry["path"].get<std::vector<std::string>>(), entry["value"].get<std::string>());
940 }
941
942 return !injections.empty();
943}
944
945std::string NUtils::MergeRawJsonWithMetadata(const std::string & rawJson, const json & metadata)
946{
950
951 try {
952 json obj = json::parse(rawJson);
953
954 // Merge metadata fields into the parsed object
955 for (const auto & [key, val] : metadata.items()) {
956 obj[key] = val;
957 }
958
959 return obj.dump();
960 }
961 catch (const std::exception & e) {
962 NLogError("NUtils::MergeRawJsonWithMetadata: Failed to parse raw JSON: %s", e.what());
963 return rawJson; // Return original if parsing fails
964 }
965}
966
967std::vector<std::string> NUtils::Find(std::string path, std::string filename)
968{
972
973 std::vector<std::string> files;
974 TString pathStr = gSystem->ExpandPathName(path.c_str());
975 if (pathStr.IsNull() || filename.empty()) {
976 NLogError("NUtils::Find: Path or filename is empty");
977 return files;
978 }
979
980 if (pathStr.BeginsWith("root://")) {
981 return FindEos(path, filename);
982 }
983 else {
984 return FindLocal(path, filename);
985 }
986
987 return files;
988}
989
990std::vector<std::string> NUtils::FindLocal(std::string path, std::string filename)
991{
995
996 std::vector<std::string> files;
997 if (gSystem->AccessPathName(path.c_str())) {
998 NLogError("NUtils::FindLocal: Path '%s' does not exist", path.c_str());
999 return files;
1000 }
1001 NLogInfo("Doing find %s -name %s", path.c_str(), filename.c_str());
1002 std::string linesMerge =
1003 gSystem->GetFromPipe(TString::Format("find %s -name %s", path.c_str(), filename.c_str())).Data();
1004
1005 std::stringstream check2(linesMerge);
1006 std::string line;
1007 while (std::getline(check2, line)) {
1008 files.push_back(line);
1009 }
1010 return files;
1011}
1012std::vector<std::string> NUtils::FindEos(std::string path, std::string filename)
1013{
1017
1018 std::vector<std::string> files;
1019 NLogInfo("Doing eos find -f --name %s %s ", filename.c_str(), path.c_str());
1020
1021 TUrl url(path.c_str());
1022 std::string host = url.GetHost();
1023 std::string directory = url.GetFile();
1024 std::string findUrl = "root://";
1025 findUrl += host + "//proc/user/";
1026 findUrl += "?mgm.cmd=find&mgm.find.match=" + filename;
1027 findUrl += "&mgm.path=" + directory;
1028 findUrl += "&mgm.format=json&mgm.option=f&filetype=raw";
1029 NLogInfo("Doing TFile::Open on '%s' ...", findUrl.c_str());
1030
1031 TFile * f = Ndmspc::NUtils::OpenFile(findUrl.c_str());
1032 if (!f) return files;
1033
1034 // Printf("%lld", f->GetSize());
1035
1036 int buffsize = 4096;
1037 // FIXME: use smart pointer to avoid large stack allocation (check if working)
1038 auto buff = std::make_unique<char[]>(buffsize + 1);
1039 // char buff[buffsize + 1];
1040
1041 Long64_t buffread = 0;
1042 std::string content;
1043 while (buffread < f->GetSize()) {
1044
1045 if (buffread + buffsize > f->GetSize()) buffsize = f->GetSize() - buffread;
1046
1047 // Printf("Buff %lld %d", buffread, buffsize);
1048 f->ReadBuffer(buff.get(), buffread, buffsize);
1049 buff[buffsize] = '\0';
1050 content += buff.get();
1051 buffread += buffsize;
1052 }
1053
1054 f->Close();
1055
1056 std::string ss = "mgm.proc.stdout=";
1057 size_t pos = ss.size() + 1;
1058 content = content.substr(pos);
1059
1060 // stringstream class check1
1061 std::stringstream check1(content);
1062
1063 std::string intermediate;
1064
1065 // Tokenizing w.r.t. space '&'
1066 std::vector<std::string> tokens;
1067 while (getline(check1, intermediate, '&')) {
1068 tokens.push_back(intermediate);
1069 }
1070 std::string linesString = tokens[0];
1071 for (auto & line : NUtils::Tokenize(linesString, '\n')) {
1072 files.push_back("root://" + host + "/" + line);
1073 }
1074 return files;
1075}
1076
1077std::vector<std::string> NUtils::Tokenize(std::string_view input, const char delim)
1078{
1082 std::vector<std::string> out;
1083 size_t start = 0;
1084 size_t end = input.find(delim);
1085
1086 while (end != std::string_view::npos) {
1087 if (end > start) {
1088 out.emplace_back(input.substr(start, end - start));
1089 }
1090 start = end + 1;
1091 end = input.find(delim, start);
1092 }
1093
1094 if (start < input.length()) {
1095 out.emplace_back(input.substr(start));
1096 }
1097 return out;
1098}
1099std::vector<int> NUtils::TokenizeInt(std::string_view input, const char delim)
1100{
1104
1105 std::vector<int> out;
1106 std::vector<std::string> tokens = Tokenize(input, delim);
1107 for (auto & t : tokens) {
1108 if (t.empty()) continue;
1109 out.push_back(std::stoi(t));
1110 }
1111
1112 return out;
1113}
1114
1115std::string NUtils::Join(const std::vector<std::string> & values, const char delim)
1116{
1120
1121 std::string out;
1122 for (const auto & v : values) {
1123 if (!out.empty()) out += delim;
1124 out += v;
1125 }
1126 return out;
1127}
1128std::string NUtils::Join(const std::vector<int> & values, const char delim)
1129{
1133
1134 std::string out;
1135 for (const auto & v : values) {
1136 if (!out.empty()) out += delim;
1137 out += std::to_string(v);
1138 }
1139 return out;
1140}
1141
1142std::vector<std::string> NUtils::Truncate(std::vector<std::string> values, std::string value)
1143{
1147
1148 std::vector<std::string> out;
1149 for (auto & v : values) {
1150 v = std::string(v.begin() + value.size(), v.end());
1151 out.push_back(v);
1152 }
1153 return out;
1154}
1155
1156std::set<std::string> NUtils::Unique(std::vector<std::string> & paths, int axis, std::string path, char token)
1157{
1161
1162 std::set<std::string> out;
1163 std::vector<std::string> truncatedPaths = NUtils::Truncate(paths, path);
1164 for (auto & p : truncatedPaths) {
1165 std::vector<std::string> tokens = Tokenize(p, token);
1166 out.insert(tokens[axis]);
1167 }
1168 return out;
1169}
1170
1171TH1 * NUtils::ProjectTHnSparse(THnSparse * sparse, const std::vector<int> & axes, Option_t * option)
1172{
1176 if (sparse == nullptr) {
1177 NLogError("Error: Sparse is nullptr ...");
1178 return nullptr;
1179 }
1180
1181 TH1 * h = nullptr;
1182 if (axes.size() == 1) {
1183 h = sparse->Projection(axes[0], option);
1184 }
1185 else if (axes.size() == 2) {
1186 h = sparse->Projection(axes[1], axes[0], option);
1187 }
1188 else if (axes.size() == 3) {
1189 h = sparse->Projection(axes[0], axes[1], axes[2], option);
1190 }
1191 else {
1192 NLogError("Error: Only projection onto single axis is supported for TH1 ...");
1193 }
1194
1195 h->SetName(TString::Format("%s_proj", sparse->GetName()).Data());
1196 h->SetTitle(TString::Format("%s Projection", sparse->GetTitle()).Data());
1197 // Detach from gDirectory to prevent TFile from claiming ownership of the histogram.
1198 // Without this, if gDirectory is an open TFile, the TFile will delete this histogram
1199 // when closed, causing a double-free when THStack/canvas tries to clean it up later.
1200 h->SetDirectory(nullptr);
1201
1202 // Set labels for axis
1203 for (size_t i = 0; i < axes.size(); i++) {
1204 TAxis * axisSparse = sparse->GetAxis(axes[i]);
1205 TAxis * axisHist = h->GetXaxis();
1206 if (i == 1) axisHist = h->GetYaxis();
1207 if (i == 2) axisHist = h->GetZaxis();
1208
1209 axisHist->SetName(axisSparse->GetName());
1210 axisHist->SetTitle(axisSparse->GetTitle());
1211
1212 // Copy bin labels if alphanumeric
1213 if (axisSparse->IsAlphanumeric()) {
1214 for (int j = 1; j <= axisSparse->GetNbins(); j++) {
1215 const char * label = axisSparse->GetBinLabel(j);
1216 axisHist->SetBinLabel(j, label);
1217 }
1218 }
1219 }
1220
1221 return h;
1222}
1223
1224bool NUtils::SetAxisRanges(THnSparse * sparse, std::vector<std::vector<int>> ranges, bool withOverflow,
1225 bool modifyTitle, bool reset)
1226{
1231
1232 if (sparse == nullptr) {
1233 NLogError("Error: Sparse is nullptr ...");
1234 return false;
1235 }
1236 if (sparse->GetNdimensions() == 0) return true;
1237
1238 if (reset) {
1239 NLogTrace("Setting axis ranges on '%s' THnSparse ...", sparse->GetName());
1241 for (int i = 0; i < sparse->GetNdimensions(); i++) {
1242 if (withOverflow) {
1243 NLogTrace("Resetting '%s' axis ...", sparse->GetAxis(i)->GetName());
1244 sparse->GetAxis(i)->SetRange(0, 0);
1245 }
1246 else {
1247 NLogTrace("Resetting '%s' axis [%d,%d] ...", sparse->GetAxis(i)->GetName(), 1, sparse->GetAxis(i)->GetNbins());
1248 sparse->GetAxis(i)->SetRange(1, sparse->GetAxis(i)->GetNbins());
1249 }
1250 }
1251 }
1252
1253 if (ranges.empty()) {
1254 NLogTrace("No axis ranges to set ...");
1255 return true;
1256 }
1257
1258 TAxis * axis = nullptr;
1259 TString title = sparse->GetTitle();
1260 if (modifyTitle) title += " Ranges:";
1261 for (size_t i = 0; i < ranges.size(); i++) {
1262 axis = sparse->GetAxis(ranges[i][0]);
1263 NLogTrace("Setting axis range %s=[%d,%d] ...", axis->GetName(), ranges[i][1], ranges[i][2]);
1264 if (ranges[i].size() != 3) {
1265 NLogError("Error: Axis range must have 3 values, but has %zu ...", ranges[i].size());
1266 return false;
1267 }
1268 axis->SetRange(ranges[i][1], ranges[i][2]);
1269 if (axis->IsAlphanumeric()) {
1270
1271 title += TString::Format(" %s[%s]", axis->GetName(), axis->GetBinLabel(ranges[i][1]));
1272 }
1273 else {
1274 title += TString::Format(" %s[%0.2f - %0.2f]", axis->GetName(), axis->GetBinLowEdge(ranges[i][1]),
1275 axis->GetBinUpEdge(ranges[i][2]));
1276 }
1277 }
1278 if (modifyTitle) sparse->SetTitle(title.Data());
1279 return true;
1280}
1281
1282bool NUtils::SetAxisRanges(THnSparse * sparse, std::map<int, std::vector<int>> ranges, bool withOverflow,
1283 bool modifyTitle, bool reset)
1284{
1289
1290 if (sparse == nullptr) {
1291 NLogError("NUtils::SetAxisRanges: Sparse is nullptr ...");
1292 return false;
1293 }
1294 if (sparse->GetNdimensions() == 0) return true;
1295
1296 NLogTrace("NUtils::SetAxisRanges: Setting axis ranges on '%s' THnSparse ...", sparse->GetName());
1297 if (reset) {
1299 for (int i = 0; i < sparse->GetNdimensions(); i++) {
1300 if (withOverflow) {
1301 NLogTrace("NUtils::SetAxisRanges: Resetting '%s' axis ...", sparse->GetAxis(i)->GetName());
1302 sparse->GetAxis(i)->SetRange(0, 0);
1303 }
1304 else {
1305 NLogTrace("NUtils::SetAxisRanges: Resetting '%s' axis [%d,%d] ...", sparse->GetAxis(i)->GetName(), 1,
1306 sparse->GetAxis(i)->GetNbins());
1307 sparse->GetAxis(i)->SetRange(1, sparse->GetAxis(i)->GetNbins());
1308 }
1309 }
1310 }
1311
1312 if (ranges.empty()) {
1313 NLogTrace("NUtils::SetAxisRanges: No axis ranges to set ...");
1314 return true;
1315 }
1316 TAxis * axis = nullptr;
1317 TString title = sparse->GetTitle();
1318 for (const auto & [key, val] : ranges) {
1319 NLogTrace("NUtils::SetAxisRanges: Setting axis range for axis %d to [%d,%d] ...", key, val[0], val[1]);
1320 axis = sparse->GetAxis(key);
1321 if (axis == nullptr) {
1322 NLogError("NUtils::SetAxisRanges: Axis %d is nullptr ...", key);
1323 return false;
1324 }
1325 NLogTrace("NUtils::SetAxisRanges: Setting axis range %s=[%d,%d] ...", axis->GetName(), val[0], val[1]);
1326 axis->SetRange(val[0], val[1]);
1327 if (axis->IsAlphanumeric()) {
1328
1329 title += TString::Format(" %s[%s]", axis->GetName(), axis->GetBinLabel(val[0]));
1330 }
1331 else {
1332 title += TString::Format(" %s[%0.2f - %0.2f]", axis->GetName(), axis->GetBinLowEdge(val[0]),
1333 axis->GetBinUpEdge(val[1]));
1334 }
1335 }
1336
1337 if (modifyTitle) sparse->SetTitle(title.Data());
1338 NLogTrace("NUtils::SetAxisRanges: New title: %s", sparse->GetTitle());
1339
1340 return true;
1341}
1342bool NUtils::GetAxisRangeInBase(TAxis * a, int rebin, int rebin_start, int bin, int & min, int & max)
1343{
1347 if (a == nullptr) {
1348 NLogError("Error: Axis is nullptr ...");
1349 return false;
1350 }
1351 min = -1;
1352 max = -1;
1353
1354 NLogTrace("Getting axis range in base for '%s' rebin=%d rebin_start=%d bin=%d...", a->GetName(), rebin, rebin_start,
1355 bin);
1356
1357 min = rebin * (bin - 1) + rebin_start;
1358 max = min + rebin - 1;
1359 NLogTrace("Axis '%s' min=%d max=%d", a->GetName(), min, max);
1360
1361 if (min < 1) {
1362 NLogError("Error: Axis '%s' min=%d is lower then 1 ...", a->GetName(), min);
1363 min = -1;
1364 max = -1;
1365 return false;
1366 }
1367
1368 if (max > a->GetNbins()) {
1369 NLogError("Error: Axis '%s' max=%d is higher then %d ...", a->GetName(), max, a->GetNbins());
1370 min = -1;
1371 max = -1;
1372 return false;
1373 }
1374
1375 return true;
1376}
1377bool NUtils::GetAxisRangeInBase(TAxis * a, int min, int max, TAxis * base, int & minBase, int & maxBase)
1378{
1383 int rebin = base->GetNbins() / a->GetNbins();
1384
1385 // TODO: Improve handling of rebin_start correctly (depending on axis min and max of first bin)
1386 int rebin_start = (base->GetNbins() % a->GetNbins()) + 1;
1387 rebin_start = rebin != 1 ? rebin_start : 1; // start from 1
1388
1389 NLogTrace("Getting axis range in base for '%s' min=%d max=%d rebin=%d rebin_start=%d...", a->GetName(), min, max,
1390 rebin, rebin_start);
1391
1392 int tmp;
1393 GetAxisRangeInBase(base, rebin, rebin_start, min, minBase, tmp);
1394 GetAxisRangeInBase(base, rebin, rebin_start, max, tmp, maxBase);
1395 NLogTrace("Axis '%s' minBase=%d maxBase=%d", a->GetName(), minBase, maxBase);
1396
1397 return true;
1398}
1399
1400TObjArray * NUtils::AxesFromDirectory(const std::vector<std::string> paths, const std::string & findPath,
1401 const std::string & fileName, const std::vector<std::string> & axesNames)
1402{
1403 if (paths.empty()) {
1404 NLogError("Error: No paths provided ...");
1405 return nullptr;
1406 }
1407
1408 std::map<std::string, std::set<std::string>> axes;
1409 for (const auto & path : paths) {
1410 NLogInfo("Found file: %s", path.c_str());
1411 // remove prefix basePath from path
1412 TString relativePath = path;
1413 relativePath.ReplaceAll(findPath.c_str(), "");
1414 relativePath.ReplaceAll(fileName.c_str(), "");
1415 // relativePath.ReplaceAll("years", "");
1416 // relativePath.ReplaceAll("data", "");
1417 relativePath.ReplaceAll("//", "/");
1418 // remove leading slash
1419 relativePath.Remove(0, relativePath.BeginsWith("/") ? 1 : 0);
1420 // remove trailing slash
1421 relativePath.Remove(relativePath.EndsWith("/") ? relativePath.Length() - 1 : relativePath.Length(), 1);
1422
1423 std::vector<std::string> tokens = Ndmspc::NUtils::Tokenize(relativePath.Data(), '/');
1424
1425 // if (tokens.size() < axesNames.size()) {
1426 // tokens.push_back("mb");
1427 // }
1428 //
1429 if (tokens.size() != axesNames.size()) {
1430 continue;
1431 }
1432
1433 for (size_t i = 0; i < tokens.size(); ++i) {
1434 axes[axesNames[i]].insert(tokens[i]);
1435 }
1436 }
1437
1438 TObjArray * axesArr = new TObjArray();
1439 for (const auto & axisName : axesNames) {
1440 TAxis * axis = Ndmspc::NUtils::CreateAxisFromLabelsSet(axisName, axisName, axes[axisName]); // Convert set to vector
1441 axesArr->Add(axis);
1442 }
1443
1444 return axesArr;
1445}
1446std::string NUtils::GetJsonString(json j)
1447{
1451
1452 if (j.is_string()) {
1453 return j.get<std::string>();
1454 }
1455 else if (j.is_number_integer()) {
1456 return std::to_string(j.get<int>());
1457 }
1458 else if (j.is_number_float()) {
1459 return std::to_string(j.get<double>());
1460 }
1461 else if (j.is_boolean()) {
1462 return j.get<bool>() ? "true" : "false";
1463 }
1464 else if (j.is_null()) {
1465 return "";
1466 }
1467 else {
1468 return "";
1469 }
1470}
1472{
1476
1477 if (j.is_number_integer()) {
1478 return j.get<int>();
1479 }
1480 else if (j.is_number_float()) {
1481 return static_cast<int>(j.get<double>());
1482 }
1483 else if (j.is_boolean()) {
1484 return j.get<bool>() ? 1 : 0;
1485 }
1486 else if (j.is_null()) {
1487 return -1;
1488 }
1489 else {
1490 return -1;
1491 }
1492}
1493
1495{
1499
1500 if (j.is_number_float()) {
1501 return j.get<double>();
1502 }
1503 else if (j.is_number_integer()) {
1504 return static_cast<double>(j.get<int>());
1505 }
1506 else if (j.is_boolean()) {
1507 return j.get<bool>() ? 1.0 : 0.0;
1508 }
1509 else if (j.is_null()) {
1510 return -1.0;
1511 }
1512 else {
1513 return -1.0;
1514 }
1515}
1516
1518{
1522
1523 if (j.is_boolean()) {
1524 return j.get<bool>();
1525 }
1526 else if (j.is_number_integer()) {
1527 return j.get<int>() != 0;
1528 }
1529 else if (j.is_number_float()) {
1530 return j.get<double>() != 0.0;
1531 }
1532 else if (j.is_null()) {
1533 return false;
1534 }
1535 else {
1536 return false;
1537 }
1538}
1539
1540std::vector<std::string> NUtils::GetJsonStringArray(json j)
1541{
1545
1546 std::vector<std::string> out;
1547 if (j.is_array()) {
1548 for (auto & v : j) {
1549 out.push_back(GetJsonString(v));
1550 }
1551 }
1552 return out;
1553}
1554
1555std::vector<int> NUtils::ArrayToVector(Int_t * v1, int size)
1556{
1560
1561 std::vector<int> v2;
1562 for (int i = 0; i < size; i++) {
1563 v2.push_back(v1[i]);
1564 }
1565 return v2;
1566}
1567
1568void NUtils::VectorToArray(std::vector<int> v1, Int_t * v2)
1569{
1573
1574 for (size_t i = 0; i < v1.size(); i++) {
1575 v2[i] = v1[i];
1576 }
1577}
1578std::string NUtils::GetCoordsString(const std::vector<Long64_t> & coords, int index, int width)
1579{
1583 std::stringstream msg;
1584 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1585 msg << "[";
1586 for (size_t i = 0; i < coords.size(); ++i) {
1587 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1588 }
1589 msg << "]";
1590 return msg.str();
1591}
1592std::string NUtils::GetCoordsString(const std::vector<int> & coords, int index, int width)
1593{
1597 std::stringstream msg;
1598 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1599 msg << "[";
1600 for (size_t i = 0; i < coords.size(); ++i) {
1601 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1602 }
1603 msg << "]";
1604 return msg.str();
1605}
1606std::string NUtils::GetCoordsString(const std::vector<size_t> & coords, int index, int width)
1607{
1611 std::stringstream msg;
1612 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1613 msg << "[";
1614 for (size_t i = 0; i < coords.size(); ++i) {
1615 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1616 }
1617 msg << "]";
1618 return msg.str();
1619}
1620std::string NUtils::GetCoordsString(const std::vector<std::string> & coords, int index, int width)
1621{
1625 std::stringstream msg;
1626 if (index >= 0) msg << "[" << std::setw(3) << std::setfill('0') << index << "] ";
1627 msg << "[";
1628 for (size_t i = 0; i < coords.size(); ++i) {
1629 msg << std::setw(width) << std::setfill(' ') << coords[i] << (i == coords.size() - 1 ? "" : ",");
1630 }
1631 msg << "]";
1632 return msg.str();
1633}
1634void NUtils::PrintPointSafe(const std::vector<int> & coords, int index)
1635{
1639
1640 NLogInfo("%s", GetCoordsString(coords, index).c_str());
1641}
1642
1643std::vector<std::vector<int>> NUtils::Permutations(const std::vector<int> & v)
1644{
1648 std::vector<std::vector<int>> result;
1649 std::vector<int> current = v;
1650 std::sort(current.begin(), current.end());
1651 do {
1652 result.push_back(current);
1653 } while (std::next_permutation(current.begin(), current.end()));
1654
1655 // print the permutations
1656 NLogTrace("Permutations of vector: %s", GetCoordsString(v).c_str());
1657 for (const auto & perm : result) {
1658 NLogTrace("Permutation: %s", GetCoordsString(perm).c_str());
1659 }
1660
1661 return result;
1662}
1663
1664std::string NUtils::FormatTime(long long seconds)
1665{
1666 long long hours = seconds / 3600;
1667 seconds %= 3600;
1668 long long minutes = seconds / 60;
1669 seconds %= 60;
1670
1671 std::stringstream ss;
1672 ss << std::setw(2) << std::setfill('0') << hours << ":" << std::setw(2) << std::setfill('0') << minutes << ":"
1673 << std::setw(2) << std::setfill('0') << seconds;
1674 return ss.str();
1675}
1676
1677void NUtils::ProgressBar(int current, int total, std::string prefix, std::string suffix, int barWidth)
1678{
1679
1683 if (total == 0) return; // Avoid division by zero
1684
1685 // Let's do protection against any log to be written during progress bar
1686 std::lock_guard<std::mutex> lock(NLogger::GetLoggerMutex());
1687
1688 float percentage = static_cast<float>(current) / total;
1689 int numChars = static_cast<int>(percentage * barWidth);
1690
1691 std::cout << "\r"; // Carriage return
1692 if (!prefix.empty()) std::cout << "[" << prefix << "]"; // Carriage return
1693 std::cout << "["; // Carriage return
1694
1695 for (int i = 0; i < numChars; ++i) {
1696 std::cout << "=";
1697 }
1698 for (int i = 0; i < barWidth - numChars; ++i) {
1699 std::cout << " ";
1700 }
1701 std::cout << "] " << static_cast<int>(percentage * 100.0) << "%"
1702 << " (" << current << "/" << total << ")";
1703 if (!suffix.empty()) std::cout << " [" << suffix << "]";
1704 if (current == total) std::cout << std::endl;
1705 std::cout << std::flush; // Ensure immediate output
1706}
1707
1708void NUtils::ProgressBar(int current, int total, std::chrono::high_resolution_clock::time_point startTime,
1709 std::string prefix, std::string suffix, int barWidth)
1710{
1714 if (total == 0) return; // Avoid division by zero
1715 std::lock_guard<std::mutex> lock(NLogger::GetLoggerMutex());
1716 if (current > total) current = total; // Cap current to total for safety
1717
1718 float percentage = static_cast<float>(current) / total;
1719 int numChars = static_cast<int>(percentage * barWidth);
1720
1721 // std::cout << "\r[" << prefix << "] ["; // Carriage return
1722 std::cout << "\r[";
1723 if (!prefix.empty()) std::cout << prefix << "]["; // Carriage return
1724 for (int i = 0; i < numChars; ++i) {
1725 std::cout << "=";
1726 }
1727 for (int i = 0; i < barWidth - numChars; ++i) {
1728 std::cout << " ";
1729 }
1730 std::cout << "] " << std::setw(3) << static_cast<int>(percentage * 100.0) << "%";
1731
1732 // Calculate elapsed time
1733 auto currentTime = std::chrono::high_resolution_clock::now();
1734 auto elapsedSeconds = std::chrono::duration_cast<std::chrono::seconds>(currentTime - startTime).count();
1735
1736 // Calculate estimated remaining time (only if we've made some progress)
1737 long long estimatedRemainingSeconds = 0;
1738 if (current > 0 && percentage > 0) {
1739 // Total estimated time = (elapsed time / current progress) * 100%
1740 long long totalEstimatedSeconds = static_cast<long long>(elapsedSeconds / percentage);
1741 estimatedRemainingSeconds = totalEstimatedSeconds - elapsedSeconds;
1742 }
1743
1744 std::cout << " (" << current << "/" << total << ") "
1745 << "Elapsed: " << FormatTime(elapsedSeconds) << " "
1746 << "ETA: " << FormatTime(estimatedRemainingSeconds);
1747 if (!suffix.empty()) std::cout << " [" << suffix << "]";
1748 if (current == total) std::cout << std::endl;
1749 std::cout << std::flush; // Ensure immediate output
1750}
1751
1752TCanvas * NUtils::CreateCanvas(const std::string & name, const std::string & title, int width, int height)
1753{
1757
1758 TThread::Lock();
1759 TCanvas * c = new TCanvas("", title.c_str(), width, height);
1760 gROOT->GetListOfCanvases()->Remove(c);
1761 c->ResetBit(kMustCleanup);
1762 c->SetBit(kCanDelete, kFALSE);
1763 c->SetName(name.c_str());
1764 TThread::UnLock();
1765 return c;
1766}
1767
1768#ifdef WITH_PARQUET
1769THnSparse * NUtils::CreateSparseFromParquetTaxi(const std::string & filename, THnSparse * hns, Int_t nMaxRows)
1770{
1774 // Open the Parquet file
1775
1776 if (hns == nullptr) {
1777 NLogError("NUtils::CreateSparseFromParquetTaxi: THnSparse 'hns' is nullptr ...");
1778 return nullptr;
1779 }
1780
1781 std::shared_ptr<arrow::io::ReadableFile> infile;
1782 arrow::Result<std::shared_ptr<arrow::io::ReadableFile>> infile_result = arrow::io::ReadableFile::Open(filename);
1783 if (!infile_result.ok()) {
1784 NLogError("NUtils::CreateSparseFromParquetTaxi: Error opening file %s: %s", filename.c_str(),
1785 infile_result.status().ToString().c_str());
1786 return nullptr;
1787 }
1788 infile = infile_result.ValueUnsafe();
1789
1790 // Create a Parquet reader using the modern arrow::Result API
1791 std::unique_ptr<parquet::arrow::FileReader> reader;
1792
1793 // The new approach using arrow::Result:
1794 arrow::Result<std::unique_ptr<parquet::arrow::FileReader>> reader_result =
1795 parquet::arrow::OpenFile(infile, arrow::default_memory_pool()); // No third parameter!
1796 if (!reader_result.ok()) {
1797 NLogError("NUtils::CreateSparseFromParquetTaxi: Error opening Parquet file reader for file %s: %s",
1798 filename.c_str(), reader_result.status().ToString().c_str());
1799 arrow::Status status = infile->Close(); // Attempt to close
1800 return nullptr;
1801 }
1802 reader = std::move(reader_result).ValueUnsafe(); // Transfer ownership from Result to unique_ptr
1803
1804 // Get file metadata (optional)
1805 // Note: parquet_reader() returns a const ptr, and metadata() returns a shared_ptr
1806 std::shared_ptr<parquet::FileMetaData> file_metadata = reader->parquet_reader()->metadata();
1807 NLogTrace("Parquet file '%s' opened successfully.", filename.c_str());
1808 NLogTrace("Parquet file version: %d", file_metadata->version());
1809 NLogTrace("Parquet created by: %s", file_metadata->created_by().c_str());
1810 NLogTrace("Parquet number of columns: %d", file_metadata->num_columns());
1811 NLogTrace("Parquet number of rows: %lld", file_metadata->num_rows());
1812 NLogTrace("Parquet number of row groups: %d", file_metadata->num_row_groups());
1813
1814 // Read the entire file as a Table
1815 // std::shared_ptr<arrow::Table> table;
1816 // arrow::Status status = reader->ReadTable(&table); // ReadTable still returns Status
1817 std::shared_ptr<arrow::RecordBatchReader> batch_reader;
1818 arrow::Status status = reader->GetRecordBatchReader(&batch_reader);
1819 if (!status.ok()) {
1820 NLogError("NUtils::CreateSparseFromParquetTaxi: Error reading table from Parquet file %s: %s", filename.c_str(),
1821 status.ToString().c_str());
1822 status = infile->Close();
1823 return nullptr;
1824 }
1825
1826 // It's good practice to close the input file stream when done
1827 status = infile->Close();
1828 if (!status.ok()) {
1829 NLogWarning("NUtils::CreateSparseFromParquetTaxi: Error closing input file %s: %s", filename.c_str(),
1830 status.ToString().c_str());
1831 // This is a warning, we still want to return the table.
1832 }
1833
1834 // Print schema of the table
1835 NLogTrace("Parquet Table Schema:\n%s", batch_reader->schema()->ToString().c_str());
1836
1837 const Int_t nDims = hns->GetNdimensions();
1838 std::vector<std::string> column_names;
1839 for (int i = 0; i < nDims; ++i) {
1840 column_names.push_back(hns->GetAxis(i)->GetName());
1841 }
1842 // std::cout << "\nData (first 5 rows):\n";
1843
1844 // int max_rows = table->num_rows();
1845 int max_rows = 1e8;
1846 max_rows = nMaxRows > 0 ? std::min(max_rows, nMaxRows) : max_rows;
1847 int print_rows = std::min(max_rows, 5);
1848 // auto table_batch_reader = std::make_shared<arrow::TableBatchReader>(*table);
1849 auto table_batch_reader = batch_reader;
1850 std::shared_ptr<arrow::RecordBatch> batch;
1851 auto point = std::make_unique<Double_t[]>(nDims);
1852 // Double_t point[nDims];
1853
1854 if (print_rows > 0) {
1855 NLogTrace("Printing first %d rows of Parquet file '%s' ...", print_rows, filename.c_str());
1856 // NLogInfo("Columns: %s", NUtils::Join(column_names, '\t').c_str());
1857 }
1858
1859 int batch_count = 0;
1860 while (table_batch_reader->ReadNext(&batch).ok() && batch) {
1861 batch_count++;
1862 NLogTrace("Processing batch with %d rows and %d columns ...", batch->num_rows(), batch->num_columns());
1863 for (int i = 0; i < batch->num_rows(); ++i) {
1864 if (i >= max_rows) break; // Limit to first 5 rows for display
1865
1866 bool isValid = true;
1867 int idx = 0;
1868 for (int j = 0; j < batch->num_columns(); ++j) {
1869 if (std::find(column_names.begin(), column_names.end(), batch->column_name(j)) == column_names.end())
1870 continue; // Skip columns not in our list
1871 // NLogDebug("[%d %s]Processing row %d, column '%s' ...", idx, hns->GetAxis(idx)->GetName(), i,
1872 // batch->column_name(j).c_str());
1873 // std::cout << batch->column_name(j) << "\t";
1874 const auto & array = batch->column(j);
1875 arrow::Result<std::shared_ptr<arrow::Scalar>> scalar_result = array->GetScalar(i);
1876 if (scalar_result.ok()) {
1877 // if (i * batch_count < print_rows) std::cout << scalar_result.ValueUnsafe()->ToString() << "\t";
1878 if (scalar_result.ValueUnsafe()->is_valid) {
1879 TAxis * axis = hns->GetAxis(idx);
1880 if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::STRING ||
1881 scalar_result.ValueUnsafe()->type->id() == arrow::Type::LARGE_STRING) {
1882 // Arrow StringScalar's value is an arrow::util::string_view or arrow::util::string_view
1883 // It's best to convert it to std::string for general use.
1884 std::string value = scalar_result.ValueUnsafe()->ToString();
1885 // TODO: check if not shifted by one
1886 // NLogInfo("NUtils::CreateSparseFromParquetTaxi: Mapping string value '%s' to axis '%s' ...",
1887 // value.c_str(), axis->GetName());
1888 point[idx] = axis->GetBinCenter(axis->FindBin(value.c_str()));
1889 }
1890 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::INT32) {
1891 auto int_scalar = std::static_pointer_cast<arrow::Int32Scalar>(scalar_result.ValueUnsafe());
1892
1893 point[idx] = static_cast<Double_t>(int_scalar->value);
1894 }
1895 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::INT64) {
1896 auto int64_scalar = std::static_pointer_cast<arrow::Int64Scalar>(scalar_result.ValueUnsafe());
1897 point[idx] = static_cast<Double_t>(int64_scalar->value);
1898 }
1899 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::UINT32) {
1900 auto uint32_scalar = std::static_pointer_cast<arrow::UInt32Scalar>(scalar_result.ValueUnsafe());
1901 point[idx] = static_cast<Double_t>(uint32_scalar->value);
1902 }
1903 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::FLOAT) {
1904 auto float_scalar = std::static_pointer_cast<arrow::FloatScalar>(scalar_result.ValueUnsafe());
1905 point[idx] = static_cast<Double_t>(float_scalar->value);
1906 }
1907 else if (scalar_result.ValueUnsafe()->type->id() == arrow::Type::DOUBLE) {
1908 auto double_scalar = std::static_pointer_cast<arrow::DoubleScalar>(scalar_result.ValueUnsafe());
1909 point[idx] = double_scalar->value;
1910 }
1911 else {
1912 NLogError("NUtils::CreateSparseFromParquetTaxi: Unsupported data type for column '%s' ...",
1913 batch->column_name(j).c_str());
1914 isValid = false;
1915 }
1916 }
1917 else {
1918 // Handle null values (set to 0 or some default)
1919 //
1920 //
1921 point[idx] = -1000;
1922 isValid = false;
1923 isValid = true;
1924 }
1925 }
1926 else {
1927 NLogError("NUtils::CreateSparseFromParquetTaxi: Error getting scalar at (%d,%d): %s", i, j,
1928 scalar_result.status().ToString().c_str());
1929 isValid = false;
1930 }
1931 idx++;
1932 }
1933 // if (i * batch_count < print_rows) std::cout << std::endl;
1934 if (isValid) {
1935 // print point
1936 // for (int d = 0; d < nDims; ++d) {
1937 // NLogDebug("Point[%d=%s]=%f", d, hns->GetAxis(d)->GetName(), point[d]);
1938 // }
1939 hns->Fill(point.get());
1940 }
1941 else {
1942 NLogWarning("Skipping row %d due to invalid data.", i);
1943 }
1944 }
1945 }
1946 return hns;
1947}
1948#else
1949THnSparse * NUtils::CreateSparseFromParquetTaxi(const std::string & /*filename*/, THnSparse * /*hns*/,
1950 Int_t /*nMaxRows*/)
1951{
1952 NLogError("Parquet support is not enabled. Please compile with Parquet support.");
1953 return nullptr;
1954}
1955#endif
1956
1957void NUtils::SafeDeleteObjects(std::vector<TObject *> & objects)
1958{
1959 if (objects.empty()) return;
1960
1961 // With EnableThreadSafety(), every TList has fUsingRWLock=true, so
1962 // TList::Clear() acquires gCoreMutex and calls GarbageCollect() on each
1963 // element. When deleting a TCanvas, the cascade TPad::Close() →
1964 // fPrimitives->Clear() → GarbageCollect() crashes.
1965 //
1966 // Fix: fully disarm every pad's primitive list (disable RW lock, mark
1967 // non-owning, remove all links with "nodelete"), then delete the objects.
1968 // Orphaned primitives (histograms, frames, sub-pads) are collected and
1969 // deleted manually afterwards.
1970
1971 Bool_t prevMustClean = gROOT->MustClean();
1972 gROOT->SetMustClean(kFALSE);
1973
1974 // Collect all pads breadth-first (top-level + nested sub-pads)
1975 std::vector<TPad *> pads;
1976 for (auto * obj : objects) {
1977 if (obj && obj->InheritsFrom(TPad::Class())) pads.push_back(static_cast<TPad *>(obj));
1978 }
1979 for (size_t i = 0; i < pads.size(); ++i) {
1980 TList * prims = pads[i]->GetListOfPrimitives();
1981 if (!prims || prims->IsEmpty()) continue;
1982 for (TObjLink * lnk = prims->FirstLink(); lnk; lnk = lnk->Next()) {
1983 TObject * child = lnk->GetObject();
1984 if (child && child->InheritsFrom(TPad::Class())) pads.push_back(static_cast<TPad *>(child));
1985 }
1986 }
1987
1988 // Track input objects to avoid double-deleting shared primitives
1989 std::set<TObject *> inputSet(objects.begin(), objects.end());
1990 inputSet.erase(nullptr);
1991
1992 // Disarm all pad primitive lists (deepest first) and collect orphans
1993 std::set<TObject *> orphans;
1994 for (auto it = pads.rbegin(); it != pads.rend(); ++it) {
1995 TList * prims = (*it)->GetListOfPrimitives();
1996 if (!prims) continue;
1997 // Collect primitives not in the input vector — they'd leak otherwise
1998 for (TObjLink * lnk = prims->FirstLink(); lnk; lnk = lnk->Next()) {
1999 TObject * child = lnk->GetObject();
2000 if (child && inputSet.find(child) == inputSet.end()) orphans.insert(child);
2001 }
2002 // Disarm: no lock, non-owning, remove links only (no GarbageCollect)
2003 prims->UseRWLock(kFALSE);
2004 prims->SetOwner(kFALSE);
2005 prims->Clear("nodelete");
2006 }
2007
2008 // Delete all input objects (canvas/pad destructors find empty primitive lists)
2009 for (auto * obj : objects) {
2010 if (obj) delete obj;
2011 }
2012 objects.clear();
2013
2014 // Delete orphaned primitives (histograms, frames, sub-pads extracted above)
2015 for (auto * obj : orphans) {
2016 delete obj;
2017 }
2018
2019 gROOT->SetMustClean(prevMustClean);
2020}
2021
2022void NUtils::SafeDeleteTList(TList *& lst)
2023{
2024 if (!lst) return;
2025
2026 // Extract objects from the TList into a vector
2027 std::vector<TObject *> objects;
2028 for (TObjLink * lnk = lst->FirstLink(); lnk; lnk = lnk->Next()) {
2029 TObject * obj = lnk->GetObject();
2030 if (obj) objects.push_back(obj);
2031 }
2032
2033 // Destroy the TList shell without touching objects
2034 lst->UseRWLock(kFALSE);
2035 lst->SetOwner(kFALSE);
2036 lst->Clear("nodelete");
2037 delete lst;
2038 lst = nullptr;
2039
2040 // Delete all collected objects safely
2041 SafeDeleteObjects(objects);
2042}
2043
2044void NUtils::SafeDeleteObject(TObject *& obj)
2045{
2046 if (!obj) return;
2047
2048 if (obj->InheritsFrom(TList::Class())) {
2049 TList * lst = static_cast<TList *>(obj);
2050 obj = nullptr;
2051 SafeDeleteTList(lst);
2052 }
2053 else {
2054 delete obj;
2055 obj = nullptr;
2056 }
2057}
2058
2060{
2061 json out;
2062 ProcInfo_t info;
2063 gSystem->GetProcInfo(&info);
2064
2065 out["cpu_user"] = info.fCpuUser;
2066 out["cpu_sys"] = info.fCpuSys;
2067 out["cpu_total"] = info.fCpuUser + info.fCpuSys;
2068 out["mem_rss_kb"] = info.fMemResident;
2069 out["mem_vsize_kb"] = info.fMemVirtual;
2070
2071 // Report number of logical CPUs available on the host
2072 unsigned int hc = std::thread::hardware_concurrency();
2073 out["cpu_count"] = (hc == 0) ? 1 : static_cast<int>(hc);
2074
2075 return out;
2076}
2077
2079{
2080 json out;
2081 out["totalRead"] = 0LL;
2082 out["totalWritten"] = 0LL;
2083
2084 TList * files = (TList *)gROOT->GetListOfFiles();
2085 if (!files) return out;
2086
2087 Long64_t totalRead = 0;
2088 Long64_t totalWritten = 0;
2089
2090 TIter next(files);
2091 TObject * obj = nullptr;
2092 while ((obj = next())) {
2093 TFile * f = dynamic_cast<TFile *>(obj);
2094 if (!f) continue;
2095 json fi;
2096 fi["name"] = f->GetName() ? f->GetName() : "";
2097 fi["isZombie"] = (bool)f->IsZombie();
2098 fi["isOpen"] = (bool)f->IsOpen();
2099
2100 // Try to read per-file counters if available in this ROOT build
2101 Long64_t bytesRead = 0;
2102 Long64_t bytesWritten = 0;
2103 // Many ROOT versions expose GetBytesRead/GetBytesWritten on TFile; attempt to call them.
2104 // If they are not available, these calls will fail to link — in that case, users
2105 // can replace this implementation with a platform-specific /proc reader.
2106#if 1
2107 // Use C-style cast to call methods if they exist; rely on linker to resolve.
2108 // If unavailable, these lines may need adjustment for older ROOT versions.
2109 try {
2110 bytesRead = f->GetBytesRead();
2111 bytesWritten = f->GetBytesWritten();
2112 }
2113 catch (...) {
2114 bytesRead = 0;
2115 bytesWritten = 0;
2116 }
2117#endif
2118
2119 fi["bytesRead"] = bytesRead;
2120 fi["bytesWritten"] = bytesWritten;
2121
2122 totalRead += bytesRead;
2123 totalWritten += bytesWritten;
2124
2125 out["files"].push_back(fi);
2126 }
2127
2128 out["totalRead"] = totalRead;
2129 out["totalWritten"] = totalWritten;
2130
2131 return out;
2132}
2133
2135{
2136 json out;
2137 out["total_rx"] = 0ULL;
2138 out["total_tx"] = 0ULL;
2139
2140#if defined(__linux__)
2141 std::ifstream f("/proc/net/dev");
2142 if (!f.good()) return out;
2143 std::string line;
2144 // skip headers
2145 std::getline(f, line);
2146 std::getline(f, line);
2147 while (std::getline(f, line)) {
2148 if (line.empty()) continue;
2149 size_t colon = line.find(':');
2150 if (colon == std::string::npos) continue;
2151 std::string ifname = line.substr(0, colon);
2152 // trim
2153 auto ltrim = [](std::string & s) {
2154 size_t start = s.find_first_not_of(" \t");
2155 if (start != std::string::npos)
2156 s = s.substr(start);
2157 else
2158 s.clear();
2159 };
2160 auto rtrim = [](std::string & s) {
2161 size_t end = s.find_last_not_of(" \t");
2162 if (end != std::string::npos)
2163 s = s.substr(0, end + 1);
2164 else
2165 s.clear();
2166 };
2167 ltrim(ifname);
2168 rtrim(ifname);
2169 std::string rest = line.substr(colon + 1);
2170 std::stringstream ss(rest);
2171 std::vector<unsigned long long> vals;
2172 std::string tok;
2173 while (ss >> tok) {
2174 try {
2175 vals.push_back(std::stoull(tok));
2176 }
2177 catch (...) {
2178 vals.push_back(0ULL);
2179 }
2180 }
2181 if (vals.size() >= 9) {
2182 unsigned long long rx = vals[0];
2183 unsigned long long tx = vals[8];
2184 json iface;
2185 iface["name"] = ifname;
2186 iface["rx"] = rx;
2187 iface["tx"] = tx;
2188 out["interfaces"].push_back(iface);
2189 out["total_rx"] = static_cast<unsigned long long>(
2190 out["total_rx"].is_null() ? 0ULL : out["total_rx"].get<unsigned long long>()) +
2191 rx;
2192 out["total_tx"] = static_cast<unsigned long long>(
2193 out["total_tx"].is_null() ? 0ULL : out["total_tx"].get<unsigned long long>()) +
2194 tx;
2195 }
2196 }
2197
2198#elif defined(__APPLE__)
2199 struct ifaddrs * ifap = nullptr;
2200 if (getifaddrs(&ifap) != 0) return out;
2201 for (struct ifaddrs * ifa = ifap; ifa; ifa = ifa->ifa_next) {
2202 if (!ifa->ifa_data) continue;
2203 struct if_data * ifd = (struct if_data *)ifa->ifa_data;
2204 if (!ifd) continue;
2205 unsigned long long rx = (unsigned long long)ifd->ifi_ibytes;
2206 unsigned long long tx = (unsigned long long)ifd->ifi_obytes;
2207 json iface;
2208 iface["name"] = ifa->ifa_name ? ifa->ifa_name : std::string();
2209 iface["rx"] = rx;
2210 iface["tx"] = tx;
2211 out["interfaces"].push_back(iface);
2212 out["total_rx"] =
2213 static_cast<unsigned long long>(out["total_rx"].is_null() ? 0ULL : out["total_rx"].get<unsigned long long>()) +
2214 rx;
2215 out["total_tx"] =
2216 static_cast<unsigned long long>(out["total_tx"].is_null() ? 0ULL : out["total_tx"].get<unsigned long long>()) +
2217 tx;
2218 }
2219 freeifaddrs(ifap);
2220#else
2221 // Unsupported platform: return empty totals
2222#endif
2223
2224 return out;
2225}
2226
2227} // 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:885
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:1224
static std::vector< std::string > Truncate(std::vector< std::string > values, std::string value)
Truncate vector of strings by a value.
Definition NUtils.cxx:1142
static TH1 * ProjectTHnSparse(THnSparse *hns, const std::vector< int > &axes, Option_t *option="")
Project a THnSparse histogram onto specified axes.
Definition NUtils.cxx:1171
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:1012
static bool LoadJsonFile(json &cfg, std::string filename)
Loads a JSON configuration file into the provided json object.
Definition NUtils.cxx:818
static json GetTFileIOStats()
Get TFile read/write statistics by inspecting ROOT's list of open files.
Definition NUtils.cxx:2078
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:990
static void SafeDeleteTList(TList *&lst)
Safely delete a TList and all its contents, bypassing ROOT's GarbageCollect.
Definition NUtils.cxx:2022
static void PrintPointSafe(const std::vector< int > &coords, int index=-1)
Print coordinates safely.
Definition NUtils.cxx:1634
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:1752
static std::string MergeRawJsonWithMetadata(const std::string &rawJson, const json &metadata)
Merge raw JSON string with metadata fields.
Definition NUtils.cxx:945
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:2134
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:1099
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:1077
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:1664
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:1342
static json GetSystemStats()
Get process CPU and RSS memory statistics using ROOT's gSystem::GetProcInfo.
Definition NUtils.cxx:2059
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:1156
static std::vector< std::string > GetJsonStringArray(json j)
Get JSON value as array of strings.
Definition NUtils.cxx:1540
static std::string GetJsonString(json j)
Get JSON value as string.
Definition NUtils.cxx:1446
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:928
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:1471
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:1115
static void ProgressBar(int current, int total, std::string prefix="", std::string suffix="", int barWidth=50)
Display progress bar.
Definition NUtils.cxx:1677
static std::string GetCoordsString(const std::vector< int > &coords, int index=-1, int width=0)
Get string representation of coordinates.
Definition NUtils.cxx:1592
static void SafeDeleteObjects(std::vector< TObject * > &objects)
Safely delete a vector of ROOT objects, bypassing GarbageCollect.
Definition NUtils.cxx:1957
static THnSparse * CreateSparseFromParquetTaxi(const std::string &filename, THnSparse *hns=nullptr, Int_t nMaxRows=-1)
Create THnSparse from Parquet Taxi file.
Definition NUtils.cxx:1949
static void SafeDeleteObject(TObject *&obj)
Safely delete a TObject, handling TList contents and TCanvas/TPad cleanup.
Definition NUtils.cxx:2044
static void VectorToArray(std::vector< int > v1, Int_t *v2)
Convert vector to array.
Definition NUtils.cxx:1568
static std::vector< int > ArrayToVector(Int_t *v1, int size)
Convert array to vector.
Definition NUtils.cxx:1555
static double GetJsonDouble(json j)
Get JSON value as double.
Definition NUtils.cxx:1494
static bool GetJsonBool(json j)
Get JSON value as boolean.
Definition NUtils.cxx:1517
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:1400
static std::vector< std::string > Find(std::string path, std::string filename="")
Find files in a path matching filename.
Definition NUtils.cxx:967
static std::vector< std::vector< int > > Permutations(const std::vector< int > &v)
Generate all permutations of a vector.
Definition NUtils.cxx:1643
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:843
Global callback function for libwebsockets client events.