BDSIM
BDSIM is a Geant4 extension toolkit for simulation of particle transport in accelerator beamlines.
Loading...
Searching...
No Matches
bdsimCombine.cc
Go to the documentation of this file.
1/*
2Beam Delivery Simulation (BDSIM) Copyright (C) BDSIM Collaboration, 2001 - 2026.
3
4This file is part of BDSIM.
5
6BDSIM is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published
8by the Free Software Foundation version 3 of the License.
9
10BDSIM is distributed in the hope that it will be useful, but
11WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with BDSIM. If not, see <http://www.gnu.org/licenses/>.
17*/
21#include "FileMapper.hh"
22#include "Header.hh"
23
24#include "BDSOutputROOTEventHeader.hh"
25
26#include "TChain.h"
27#include "TFile.h"
28#include "TTree.h"
29
30#include <exception>
31#include <glob.h>
32#include <iostream>
33#include <string>
34#include <vector>
35
36int main(int argc, char* argv[])
37{
38 if (argc < 3)
39 {
40 std::cout << "usage: bdsimCombine result.root file1.root file2.root ..." << std::endl;
41 exit(1);
42 }
43
44 // build input file list
45 std::vector<std::string> inputFiles;
46 for (int i = 2; i < argc; ++i)
47 {inputFiles.emplace_back(std::string(argv[i]));}
48 // see if we're globbing files
49 if (inputFiles[0].find('*') != std::string::npos)
50 {
51 std::vector<std::string> fileNamesTemp;
52 glob_t glob_result;
53 glob(inputFiles[0].c_str(),GLOB_TILDE,nullptr,&glob_result);
54 for (unsigned int i=0; i<glob_result.gl_pathc; ++i)
55 {fileNamesTemp.emplace_back(glob_result.gl_pathv[i]);}
56 globfree(&glob_result);
57 inputFiles = fileNamesTemp;
58 }
59
60 // checks
61 if (inputFiles.size() == 1)
62 {
63 std::cout << "Only one input file provided \"" << inputFiles[0] << "\" - no point." << std::endl;
64 exit(1);
65 }
66 // check for wrong order of arguments which is common mistake
67 std::string outputFile = std::string(argv[1]);
68 if (outputFile.find('*') != std::string::npos)
69 {
70 std::cerr << "First argument for output file \"" << outputFile << "\" contains an *." << std::endl;
71 std::cerr << "Should only be a singular file - check order of arguments." << std::endl;
72 exit(1);
73 }
74
75 // merge event trees - this is done by ROOT and it writes the output file and closes it
76 TChain* eventsMerged = new TChain("Event");
77 for (const auto& filename : inputFiles)
78 {eventsMerged->Add(filename.c_str());}
79 std::cout << "Beginning merge of Event Tree" << std::endl;
80 Long64_t operationCode = eventsMerged->Merge(outputFile.c_str());
81 if (operationCode == 0)
82 {// from inspection of ROOT TChain.cxx ~line 1866, it returns 0 if there's a problem
83 std::cerr << "Problem in TTree::Merge opening output file \"" << outputFile << "\"" << std::endl;
84 return 1;
85 }
86 else
87 {std::cout << "Finished merge of Event Tree" << std::endl;}
88
89 // loop over input files loading headers to accumulate number of original events and
90 // the number of input events from an optional distribution file
91 unsigned long long int nOriginalEvents = 0;
92 unsigned long long int nEventsRequested = 0;
93 unsigned long long int nEventsInFile = 0;
94 unsigned long long int nEventsInFileSkipped = 0;
95 unsigned int distrFileLoopNTimes = 0;
96 bool skimmedFile = false;
97 unsigned long int i = 0;
98 std::cout << "Counting number of original events from headers of files" << std::endl;
99 std::vector<unsigned long long int> nEventsPerTree;
100 for (const auto& filename : inputFiles)
101 {
102 TFile* f = new TFile(filename.c_str(), "READ");
104 {
105 std::cout << "File \"" << filename << "\" skipped as not a valid BDSIM file" << std::endl;
106 if (!f->IsZombie())
107 {f->Close();}
108 delete f;
109 continue;
110 }
111 std::cout << "Accumulating> " << filename << std::endl;
112 TTree* headerTree = dynamic_cast<TTree*>(f->Get("Header")); // should be safe given check we've just done
113 if (!headerTree)
114 {
115 std::cerr << "Problem getting header from file " << filename << std::endl;
116 f->Close();
117 delete f;
118 continue;
119 }
120
121 Header* headerLocal = new Header();
122 headerLocal->SetBranchAddress(headerTree);
123 Long64_t nEntriesHeader = headerTree->GetEntries();
124 headerTree->GetEntry(nEntriesHeader - 1); // get the last entry (2nd is more up to date if it exists)
125 // We also want to explicitly copy the skim variables that might only be known in the 2nd instance.
126 BDSOutputROOTEventHeader* h = headerLocal->header;
127 if (i == 0) // take only from the first file and assume the same for all
128 {distrFileLoopNTimes = h->distrFileLoopNTimes;}
129
130 nOriginalEvents += h->nOriginalEvents;
131 nEventsRequested += h->nEventsRequested;
132 nEventsInFile += h->nEventsInFile;
133 nEventsInFileSkipped += h->nEventsInFileSkipped;
134 skimmedFile = skimmedFile || h->skimmedFile;
135
136 unsigned long long int nEventsThisFile = 0;
137 TTree* eventTree = dynamic_cast<TTree*>(f->Get("Event"));
138 if (eventTree)
139 {nEventsThisFile = (unsigned long long int)eventTree->GetEntries();}
140 nEventsPerTree.push_back(nEventsThisFile);
141
142 delete headerLocal;
143 f->Close();
144 delete f;
145 i++;
146 }
147
148 // checks
149 if (i == 0)
150 {std::cerr << "No valid files found" << std::endl; return 1;}
151
152 // now we produce a new header and update the file as well as copy over the other trees from the first valid
153 // input file in the list (i.e. tolerate the odd zombie file from a big run)
154 TFile* input = nullptr;
155 bool validFile = false;
156 i = 0;
157 while (!validFile)
158 {// assume we have one valid file from check above
159 if (i > (unsigned long int)inputFiles.size())
160 {return 1;}
161 input = new TFile(inputFiles[i].c_str(), "READ");
162 validFile = RBDS::IsBDSIMOutputFile(input);
163 i++;
164 }
165
166 TFile* output = new TFile(outputFile.c_str(), "UPDATE");
167 if (output->IsZombie())
168 {std::cerr << "Could not reopen output file to add other trees"; return 1;}
169 output->cd();
171 headerOut->Fill(std::vector<std::string>(), inputFiles); // updates time stamp
172 headerOut->SetFileType("BDSIM");
173 headerOut->skimmedFile = skimmedFile;
174 headerOut->nOriginalEvents = nOriginalEvents;
175 headerOut->nEventsRequested = nEventsRequested;
176 headerOut->nEventsInFile = nEventsInFile;
177 headerOut->nEventsInFileSkipped = nEventsInFileSkipped;
178 headerOut->distrFileLoopNTimes = distrFileLoopNTimes;
179 TTree* headerTree = new TTree("Header", "BDSIM Header");
180 headerTree->Branch("Header.", "BDSOutputROOTEventHeader", headerOut);
181 headerTree->Fill();
182
183 // go over all other trees and copy them (in the original order) from the first file to the output
184 std::cout << "Merging rest of file contents" << std::endl;
185 std::vector<std::string> treeNames = {"ParticleData", "Beam", "Options", "Model", "Run"};
186 for (const auto& tn : treeNames)
187 {
188 TTree* original = dynamic_cast<TTree*>(input->Get(tn.c_str()));
189 if (!original)
190 {
191 std::cerr << "Failed to load Tree named " << tn << std::endl;
192 delete output;
193 delete input;
194 return 1;
195 }
196 original->CloneTree();
197 }
198
199 TTree* eventCombineInfoTree = new TTree("EventCombineInfo", "EventCombineInfo");
200 UInt_t originalID = 0;
201 eventCombineInfoTree->Branch("combinedFileIndex", &originalID);
202 for (int fileIndex = 0; fileIndex < (int)nEventsPerTree.size(); fileIndex++)
203 {
204 unsigned long long int v = nEventsPerTree[fileIndex];
205 for (unsigned long long int j = 0; j < v; j++)
206 {
207 originalID = (UInt_t)fileIndex;
208 eventCombineInfoTree->Fill();
209 }
210 }
211
212 TTree* eventTree = dynamic_cast<TTree*>(output->Get("Event"));
213 if (eventTree)
214 {eventTree->AddFriend(eventCombineInfoTree);}
215
216 // write only once!!
217 output->Write(nullptr, TObject::kOverwrite);
218
219 output->Close();
220 delete output;
221
222 std::cout << "Combined result of " << inputFiles.size() << " files written to: " << outputFile << std::endl;
223 std::cout << "Run histograms are not summed" << std::endl; // TODO
224 return 0;
225}
Information about the software and the file.
unsigned long long int nEventsRequested
Number of events requested to be simulated from the file.
unsigned int distrFileLoopNTimes
Number of times a distribution file was replayed.
bool skimmedFile
Whether this is a skimmed output file.
void SetFileType(const std::string &fileTypeIn)
Update the file type.
unsigned long long int nEventsInFileSkipped
Number of events from distribution file that were skipped due to filters.
unsigned long long int nOriginalEvents
Number of original events if skimmed.
void Fill(const std::vector< std::string > &analysedFilesIn=std::vector< std::string >(), const std::vector< std::string > &combinedFilesIn=std::vector< std::string >())
unsigned long long int nEventsInFile
Number of events in the input distribution file irrespective of filters.
Header loader.
Definition Header.hh:33
BDSOutputROOTEventHeader * header
Member that ROOT can map file data to locally.
Definition Header.hh:43
void SetBranchAddress(TTree *t)
Set the branch addresses to address the contents of the file.
Definition Header.cc:40
bool IsBDSIMOutputFile(TFile *file, int *dataVersion=nullptr)
Definition FileMapper.cc:65