Iguana LATEST
Implementation Guardian of Analysis Algorithms
Loading...
Searching...
No Matches
iguana_ex_cpp_dataframes.cc
Go to the documentation of this file.
1
17
18#include <hipo4/RIguanaDS.hxx>
19#include <hipo4/ThreadedAlgo.hxx>
20
21#include <iguana/algorithms/clas12/EventBuilderFilter/Algorithm.h>
22#include <iguana/algorithms/clas12/SectorFinder/Algorithm.h>
23#include <iguana/algorithms/clas12/rga/MomentumCorrection/Algorithm.h>
24
25#include <TApplication.h>
26#include <TCanvas.h>
27
29int main(int argc, char** argv)
30{
31
32 // parse arguments
33 if(argc <= 1) {
34 fmt::println(stderr, "USAGE: {} [HIPO_FILE] <NUM_INSPECT> <INTERACTIVE_MODE>", argv[0]);
35 fmt::println(stderr, " NUM_INSPECT is optional; by default a few events are inspected");
36 fmt::println(stderr, " INTERACTIVE_MODE=\"true\" will draw the plots interactively; default is non-interactive");
37 return 2;
38 }
39 char const* in_file = argc > 1 ? argv[1] : "data.hipo";
40 int const num_events = argc > 2 ? std::stoi(argv[2]) : 100;
41 bool const interactive_mode = argc > 3 ? std::string(argv[3]) == "true" : false;
42
43 // enable interactive mode
44 auto app = interactive_mode ? new TApplication("app", &argc, argv) : nullptr;
45
46 // enable multi-threading
47 // ROOT::EnableImplicitMT();
48
49 // open the HIPO data file, using `RIguanaDS`, a data source (`RDataSource`) subclass; we will apply Iguana
50 // algorithms to this object, then transform it to an `RDataFrame` afterward
51 auto ds = std::make_unique<RIguanaDS>(in_file, num_events);
52
53 // clone the `REC::Particle` bank, since we want to compare its values to those after Iguana processing
54 // FIXME: we could use "ORIG::REC::Particle" and every `::` will be replaced by `_`, but currently (HIPO v4.5.0)
55 // only the first `::` gets replaced;
56 // see https://code.jlab.org/hallb/clas12/hipo-cpp/-/work_items/12
57 ds->CloneBankTo("REC::Particle", "ORIG_REC_Particle");
58
59 // define iguana algorithms
60 // - for dataframe usage, they must be `ThreadedAlgo` types
61 iguana::ThreadedAlgo<iguana::clas12::EventBuilderFilter> algo_eventbuilder_filter; // filter by Event Builder PID (a filter algorithm)
62 iguana::ThreadedAlgo<iguana::clas12::rga::MomentumCorrection> algo_momentum_correction; // momentum corrections (a transformer algorithm)
63 // - creator-type algorithms must be `ThreadedCreatorAlgo` types, and they require knowledge of the `RIguanaDS`
64 iguana::ThreadedCreatorAlgo<iguana::clas12::SectorFinder> algo_sector_finder(ds.get()); // get the sector for each particle (a creator algorithm)
65
66 // configure iguana algorithms
67 // FIXME: `ThreadAlgo` defines one algorithm instance per thread ("slot"), but currenlty (HIPO v4.5.0)
68 // does not provide a way to loop over the instances, so we have to re-define the number of slots here in order
69 // to be able to call a function on each algorithm instance;
70 // see https://code.jlab.org/hallb/clas12/hipo-cpp/-/work_items/11
71 unsigned int nSlots = ROOT::IsImplicitMTEnabled() ? ROOT::GetThreadPoolSize() : 1;
72 // now we can loop over all the instances, calling `SetConfigFile` on each
73 for(unsigned int i = 0; i < nSlots; i++) {
74 algo_eventbuilder_filter[i].SetConfigFile("examples/config_for_examples.yaml");
75 algo_momentum_correction[i].SetConfigFile("examples/config_for_examples.yaml");
76 algo_sector_finder[i].SetConfigFile("examples/config_for_examples.yaml");
77 }
78
79 // start the algorithms
80 algo_eventbuilder_filter.Start();
81 algo_momentum_correction.Start();
82 algo_sector_finder.Start();
83
84 // filtered banks will need a synthetic boolean column, a "mask";
85 // - `algo_eventbuilder_filter` will filter `REC::Particle`
86 // - NOTE: the column name will be uses as is, that is, `::` will not be replaced by `_`
87 ds->AddIguanaMask("REC::Particle", "MASK_REC_Particle");
88
89 // define bank indices
90 // - banks read from the input HIPO file
91 int i_particle = ds->GetBankIndex("REC::Particle");
92 int i_config = ds->GetBankIndex("RUN::config");
93 int i_track = ds->GetBankIndex("REC::Track");
94 int i_calorimeter = ds->GetBankIndex("REC::Calorimeter");
95 int i_scintillator = ds->GetBankIndex("REC::Scintillator");
96 // - banks created by iguana
97 int i_sector = ds->GetBankIndex("REC::Particle::Sector");
98
99 // describe how to call iguana algorithms' `Run` functions with a callback function definition
100 auto iguana_run_callback = [&](unsigned int slot, hipo::banklist& banks) -> bool {
101 auto& bank_particle = banks[i_particle];
102 auto& bank_config = banks[i_config];
103 auto& bank_track = banks[i_track];
104 auto& bank_calorimeter = banks[i_calorimeter];
105 auto& bank_scintillator = banks[i_scintillator];
106 auto& bank_sector = banks[i_sector];
107 if(!algo_eventbuilder_filter[slot].Run(bank_particle))
108 return false;
109 if(!algo_sector_finder[slot].Run(bank_particle, bank_track, bank_calorimeter, bank_scintillator, bank_sector))
110 return false;
111 if(!algo_momentum_correction[slot].Run(bank_particle, bank_sector, bank_config))
112 return false;
113 return true;
114 };
115 ds->SetEventCallback(iguana_run_callback);
116
117 // build the dataframe (`RDataFrame`) from the data source object (`RIguanaDS`)
118 auto df = ROOT::RDataFrame(std::move(ds));
119
120 // print the column names
121 auto const column_names = df.GetColumnNames();
122 fmt::println("\nDATAFRAME COLUMNS:");
123 for(decltype(column_names)::size_type i = 0; i < column_names.size(); i++) {
124 fmt::print("{:<30}", column_names[i]);
125 if((i + 1) % 3 == 0 || i + 1 == column_names.size())
126 fmt::print("\n");
127 }
128 fmt::print("\n");
129
130 // the column `MASK_REC_Particle` is composed of `RVec<int>` objects, where each element of the `RVec`
131 // indicates whether (`1`) or not (`0`) the corresponding row of `REC::Particle` passed the Iguana filter algorithms;
132 // to filter, use the expression `"<col_name>[MASK_REC_Particle == 1]"`, where `<col_name>` should be replaced by the column name
133 // to be filtered; let's do this for the PDG distribution
134 int pdg_max = 300;
135 int pdg_min = -pdg_max;
136 int pdg_bins = pdg_max - pdg_min;
137 // define fitered distribution:
138 auto hist_pdg_filtered = df
139 .Define("pid", "REC_Particle_pid[MASK_REC_Particle == 1]")
140 .Histo1D({"pid_filtered", "PDG filtered", pdg_bins, static_cast<double>(pdg_min), static_cast<double>(pdg_max)}, "pid");
141 // and let's compare it to the unfiltered PDG distribution:
142 auto hist_pdg_unfiltered = df.Histo1D({"pid_unfiltered", "PDG unfiltered", pdg_bins, static_cast<double>(pdg_min), static_cast<double>(pdg_max)}, "REC_Particle_pid");
143
144 // the created `REC::Particle::Sector` bank has the same rows as `REC::Particle`, so we can apply the filter the same way, making
145 // a sector distribution; let's also restrict it to electrons only
146 // FIXME: we have to make an alias, to workaround https://code.jlab.org/hallb/clas12/hipo-cpp/-/work_items/12
147 auto hist_ele_sector = df
148 .Alias("REC_Particle_Sector_sector", "REC_Particle::Sector_sector")
149 .Define("sector", "REC_Particle_Sector_sector[MASK_REC_Particle == 1 && REC_Particle_pid == 11]")
150 .Histo1D({"ele_sector", "Electron Sector", 6, 0, 6}, "sector");
151
152 // if you want to filter _all_ the `REC::Particle` columns, you could use an `RNode`, but note that may be slower
153 // than just filtering just the columns that you need; here we define filtered `REC::Particle` columns, named
154 // `MASK_REC_Particle_*`, by applying the mask to each;
155 std::vector<std::string> rec_particle_vars{"pid", "px", "py", "pz", "vx", "vy", "vz", "vt", "charge", "beta", "chi2pid", "status"};
156 ROOT::RDF::RNode df_filtered = df;
157 for(auto const& var : rec_particle_vars)
158 df_filtered = df_filtered.Define(
159 fmt::format("MASK_REC_Particle_{}", var),
160 fmt::format("REC_Particle_{}[MASK_REC_Particle == 1]", var));
161 // let's also do this for the `ORIG_REC_Particle` columns, calling them `MASK_ORIG_REC_Particle_*`
162 for(auto const& var : rec_particle_vars)
163 df_filtered = df_filtered.Define(
164 fmt::format("MASK_ORIG_REC_Particle_{}", var),
165 fmt::format("ORIG_REC_Particle_{}[MASK_REC_Particle == 1]", var));
166
167 // now let's plot the effect of momentum corrections on all electrons
168 // NOTE: this is _all_ electrons that pass the Iguana filter, _i.e._, not just the scattered electron
169 auto df_mom_corr = df_filtered
170 .Alias("px_orig", "MASK_ORIG_REC_Particle_px")
171 .Alias("py_orig", "MASK_ORIG_REC_Particle_py")
172 .Alias("pz_orig", "MASK_ORIG_REC_Particle_pz")
173 .Alias("px", "MASK_REC_Particle_px")
174 .Alias("py", "MASK_REC_Particle_py")
175 .Alias("pz", "MASK_REC_Particle_pz")
176 .Define("p_orig", "sqrt(px_orig*px_orig + py_orig*py_orig + pz_orig*pz_orig)")
177 .Define("p", "sqrt(px*px + py*py + pz*pz)")
178 .Define("delta_p", "p - p_orig");
179 // make the 2D histogram and profile:
180 auto hist_mom_corr = df_mom_corr.Histo2D({"mom_corr", "Momentum Correction;p [GeV];#Delta p [GeV]", 10, 0, 12, 100, -0.2, 0.2}, "p_orig", "delta_p");
181 auto prof_mom_corr = df_mom_corr.Profile1D({"prof_mom_corr", "", 10, 0, 12}, "p_orig", "delta_p");
182
183 // draw or write canvases
184 auto canv = new TCanvas("canv", "canv", 1600, 1200);
185 canv->SetGrid(1, 1);
186 canv->Divide(2, 2);
187 for(int p = 1; p <= 4; p++) {
188 auto pad = canv->GetPad(p);
189 pad->cd();
190 switch(p) {
191 case 1:
192 pad->SetLogy();
193 hist_pdg_unfiltered->Draw();
194 break;
195 case 2:
196 pad->SetLogy();
197 hist_pdg_filtered->Draw();
198 break;
199 case 3:
200 hist_ele_sector->Draw();
201 break;
202 case 4:
203 hist_mom_corr->Draw("colz");
204 prof_mom_corr->SetLineColor(kBlack);
205 prof_mom_corr->SetLineWidth(5);
206 prof_mom_corr->SetErrorOption("s");
207 prof_mom_corr->Draw("same");
208 break;
209 }
210 }
211 if(interactive_mode) {
212 fmt::print("\n\nShowing plots interactively;\npress ^C to exit.\n\n");
213 app->Run();
214 }
215 else
216 canv->SaveAs("out-iguana-dataframe-example.png");
217
218 return 0;
219}
int main(int argc, char **argv)
main function