ndmspc  v1.2.0-0.1.rc5
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 
38 using std::ifstream;
39 
41 ClassImp(Ndmspc::NUtils);
43 
44 namespace Ndmspc {
45 
46 bool 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 
97 bool 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 
115 bool 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 
155 int 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 
185 TAxis * 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 
202 TAxis * 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 
221 THnSparse * 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 
286 THnSparse * 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 
364 THnSparse * 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 
452 THnSparse * 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 
621 void 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 
688 bool 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 
719 TFile * 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 
735 std::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 }
765 bool 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 
781 TMacro * 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 
818 bool 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 
843 std::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 
885 void 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 
928 bool 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 
945 std::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 
967 std::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 
990 std::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 }
1012 std::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 
1077 std::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 }
1099 std::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 
1115 std::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 }
1128 std::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 
1142 std::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 
1156 std::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 
1171 TH1 * 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 
1224 bool 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 
1282 bool 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 }
1342 bool 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 }
1377 bool 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 
1400 TObjArray * 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 }
1446 std::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 
1540 std::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 
1555 std::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 
1568 void 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 }
1578 std::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 }
1592 std::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 }
1606 std::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 }
1620 std::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 }
1634 void NUtils::PrintPointSafe(const std::vector<int> & coords, int index)
1635 {
1639 
1640  NLogInfo("%s", GetCoordsString(coords, index).c_str());
1641 }
1642 
1643 std::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 
1664 std::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 
1677 void 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 
1708 void 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 
1752 TCanvas * 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
1769 THnSparse * 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
1949 THnSparse * 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 
1957 void 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 
2022 void 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 
2044 void 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.
Definition: NHttpRequest.h:21
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 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 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 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