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