R3BROOT
R3B analysis software
Loading...
Searching...
No Matches
R3BNeulandApp.cxx
Go to the documentation of this file.
1#include "R3BNeulandApp.h"
3#include "R3BException.h"
4#include "R3BFileSource2.h"
5#include "R3BParRootFileIo.h"
6#include "R3BShared.h"
7#include <CLI/CLI.hpp>
8#include <FairParRootFileIo.h>
9#include <FairRootFileSink.h>
10#include <FairRun.h>
11#include <FairRuntimeDb.h>
12#include <boost/algorithm/string/classification.hpp>
13#include <boost/algorithm/string/split.hpp>
14#include <boost/algorithm/string/trim.hpp>
15#include <boost/range/adaptor/reversed.hpp>
16#include <cmath>
17#include <cstdint>
18#include <fairlogger/Logger.h>
19#include <filesystem>
20#include <fmt/color.h>
21#include <fmt/core.h>
22#include <fstream>
23#include <functional>
24#include <gsl/span>
25#include <memory>
26#include <nlohmann/json.hpp>
27#include <nlohmann/json_fwd.hpp>
28#include <string>
29#include <string_view>
30#include <utility>
31#include <vector>
32#ifdef HAS_MPI
33#include <mpi.h>
34#endif
35
36using gsl::span;
37namespace
38{
39 template <typename T>
40 auto get_partition_from(const std::vector<T>& elements, int num_of_partitions, int partition_num) -> span<const T>
41 {
42 const auto total_size = elements.size();
43 const auto step =
44 static_cast<int>(std::ceil(static_cast<float>(total_size) / static_cast<float>(num_of_partitions)));
45 if (step * partition_num >= total_size)
46 {
47 return {};
48 }
49 const auto span_size = step * (partition_num + 1) < total_size ? step : total_size - (step * partition_num);
50 return span<const T>{ &(elements.at(step * partition_num)), span_size };
51 }
52
53 enum class JSONConfigInputType : uint8_t
54 {
55 file,
56 string,
57 invalid
58 };
59
60 auto check_json_input_type(std::string_view input) -> JSONConfigInputType
61 {
62 auto is_json_string = [](std::string_view string) -> bool { return string.find('=') != std::string::npos; };
63 auto is_a_file = [](std::string_view string) -> bool { return std::filesystem::exists(string); };
64
65 if (is_json_string(input))
66 {
67 return JSONConfigInputType::string;
68 }
69 if (is_a_file(input))
70 {
71 return JSONConfigInputType::file;
72 }
73 return JSONConfigInputType::invalid;
74 }
75
76 auto transform_to_json_string(std::string_view input) -> std::string
77 {
78 auto raw_string = std::string{ input };
79 auto equal_pos = raw_string.find('=');
80 auto keys_string = raw_string.substr(0, equal_pos);
81 auto value_string = raw_string.substr(equal_pos + 1);
82
83 boost::algorithm::trim(keys_string);
84 boost::algorithm::trim(value_string);
85
86 auto keys = std::vector<std::string>{};
87 boost::split(keys, keys_string, boost::is_any_of("."));
88
89 for (const auto& key : boost::adaptors::reverse(keys))
90 {
91 value_string = fmt::format("{{ {:?} : {}}}", key, value_string);
92 }
93 return value_string;
94 }
95} // namespace
96
97namespace R3B::Neuland
98{
99 using json = nlohmann::ordered_json;
100 CLIApplication::CLIApplication(std::string_view name,
101 std::unique_ptr<FairRun> run,
102 std::reference_wrapper<Options> option)
103 : app_name_{ name }
104 , dump_json_filename_{ fmt::format("{}_{}", name, DEFAULT_JSON_FILENAME) }
105 , run_(std::move(run))
106 , option_{ option }
107 {
108 setup_logger();
109 timer_.Start();
110 }
111
112 CLIApplication::~CLIApplication() // NOLINT: an unknown place may throw
113 {
114 timer_.Stop();
115
116 if (has_inited())
117 {
118 LOGP(info, "Writting all parameters to files");
119 run_->GetRuntimeDb()->writeContainers();
120 if (auto* runtime_db = run_->GetRuntimeDb(); runtime_db != nullptr)
121 {
122 runtime_db->writeContainers();
123 }
124 if (auto* sink = run_->GetSink(); sink != nullptr)
125 {
126 sink->Close();
127 }
128
129 if (has_failed())
130 {
131 fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
132 "\nNeuland Application finished with a failure!\n\n");
133 }
134 else
135 {
136 fmt::print(fmt::emphasis::bold | fg(fmt::color::green),
137 "\nNeuland Application finished successfully!\n\n");
138 }
139 fmt::println("Real time: {}s, cpu time: {}s", timer_.RealTime(), timer_.CpuTime());
140 }
141 }
142
144 {
145 auto spec1 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
146 fair::VerbositySpec::Info::file_line_function);
147 fair::Logger::DefineVerbosity("user1", spec1);
148
149 auto spec2 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
150 fair::VerbositySpec::Info::timestamp_s,
151 fair::VerbositySpec::Info::file_line_function);
152 fair::Logger::DefineVerbosity("user2", spec2);
153
154 fair::Logger::SetConsoleColor(true);
155 }
156
158 {
159 fair::Logger::SetConsoleSeverity(option_.get().log_level);
160 fair::Logger::SetVerbosity(option_.get().verbose_level);
161 }
162
164 {
165 LOGP(info, "Initializaing application ...");
166 set_inited(true);
169 pre_init(run_.get());
170 run_->Init();
171 post_init(run_.get());
172 LOGP(info, "Application is initialized.");
173 }
174
175 void CLIApplication::setup_options(CLI::App& program_options)
176 {
177 setup_common_options(program_options);
178 setup_application_options(program_options);
179 }
180
181 void CLIApplication::setup_common_options(CLI::App& program_options)
182 {
183 auto dump_config_callback = [this](const std::string& filename)
184 {
185 is_dump_ = true;
186 dump_json_filename_ = filename;
187 };
188
189 auto use_config_callback = [this](const std::vector<std::string>& filename_or_option)
190 {
191 if (not is_already_parsed_)
192 {
193 ParseApplicationOption(filename_or_option);
194 is_already_parsed_ = true;
195 }
196 };
197
198 auto& options = option_.get();
199 program_options
200 .add_option_function<std::vector<std::string>>(
201 "-c, --use-config", use_config_callback, "Set the json config file")
202 ->default_val(fmt::format("{}_{}", app_name_, DEFAULT_JSON_FILENAME))
203 ->run_callback_for_default()
204 ->allow_extra_args()
205 ->trigger_on_parse();
206 program_options.add_flag("--print-config", has_print_default_options_, "Print default option value");
207 program_options
208 .add_option_function<std::string>("--dump-config", dump_config_callback, "Dump the config into a json file")
209 ->default_val(dump_json_filename_)
210 ->run_callback_for_default()
211 ->expected(0, 1);
212 program_options.add_option("-s, --severity", options.log_level, "Set the severity level");
213 program_options.add_option("-v, --verbose", options.verbose_level, "Set the verbose level");
214 program_options.add_option("-n, --event-num", options.event_num, "Set the event number")->capture_default_str();
215 program_options.add_option("--run-id", options.run_id, "Set the run id")->capture_default_str();
216
217 program_options.add_option("-i, --input-file", options.input.data, "Set the input filenames (regex)")
218 ->capture_default_str()
219 ->group("Input options");
220 program_options
221 .add_option("--input-tree-file",
222 options.input.tree_data,
223 "Set the input filenames (regex) containing only root tree")
224 ->group("Input options");
225 program_options.add_option("--par-in", options.input.par, "Set the filename of the input parameter root file")
226 ->capture_default_str()
227 ->group("Input options");
228
229 program_options.add_option("-o, --output-file", options.output.data, "Set the output filename")
230 ->capture_default_str()
231 ->group("Output options");
232 program_options
233 .add_option("--par-out", options.output.par, "Set the filename of the output parameter root file")
234 ->capture_default_str()
235 ->group("Output options");
236 }
237
239 {
240 const auto& option = option_.get();
241
242 // output files:
243 const auto output_name =
244 option.enable_mpi ? fmt::format("{}.{}", option.output.data, rank_num_) : option.output.data;
245 if (not option_.get().output.data.empty())
246 {
247 // check if path if relative or full
248 auto file_path = option.output.working_dir.empty()
249 ? fs::path{ output_name }
250 : fs::path{ option.output.working_dir } / fs::path{ output_name };
251 auto file_sink = std::make_unique<FairRootFileSink>(file_path.c_str());
252 run_->SetSink(file_sink.release());
253 }
254
255 // input files:
256 auto file_source = std::make_unique<R3BFileSource2>();
257 if (option_.get().run_id >= 0)
258 {
259 file_source->SetInitRunID(option_.get().run_id);
260 run_->SetRunId(option_.get().run_id);
261 LOGP(info, "Filesource2: Set to run id {}", option_.get().run_id);
262 }
263 add_input_filename(file_source.get());
264 if (not file_source->IsEmpty())
265 {
266 run_->SetSource(file_source.release());
267 }
268 }
269
271 {
272 const auto& input_option = option_.get().input;
273 const auto& output_option = option_.get().output;
274 const auto& input_wd = input_option.working_dir;
275 const auto& output_wd = output_option.working_dir;
276
277 if (not input_option.par.empty())
278 {
279 auto input_par_fileio = ParRootFileIo::Input();
280 for (const auto& par_filename : input_option.par)
281 {
282 if (not par_filename.empty())
283 {
284 auto file_path = [&]()
285 {
286 auto par_file = fs::path{ par_filename };
287
288 if (par_file.is_absolute())
289 {
290 return par_file;
291 }
292 return input_wd.empty() ? par_file : fs::path{ input_wd } / par_file;
293 }();
294 LOGP(info, "Input first parameter file is {:?}", file_path.c_str());
295 input_par_fileio->open(file_path.c_str(), "READ");
296 }
297 }
298 run_->GetRuntimeDb()->setFirstInput(input_par_fileio.release());
299 }
300
301 if (not output_option.par.empty())
302 {
303 const auto& option = option_.get();
304 const auto output_name =
305 option.enable_mpi ? fmt::format("{}.{}", output_option.par, rank_num_) : output_option.par;
306 auto file_path =
307 output_wd.empty() ? fs::path{ output_name } : fs::path{ output_wd } / fs::path{ output_name };
308 LOGP(info, "Ouptut parameter file is {:?}", file_path.string());
309 auto output_par_fileio = ParRootFileIo::Output();
310 output_par_fileio->open(file_path.c_str(), option.output.mode);
311 // auto fileio = std::make_unique<FairParRootFileIo>(true);
312 // fileio->open(file_path.c_str(), option.output.mode.c_str());
313 auto* rtdb = run_->GetRuntimeDb();
314 rtdb->setOutput(output_par_fileio.release());
315 }
316 }
317
319 {
320 auto max_event = option_.get().event_num;
321 max_event = max_event > 0 ? max_event : 0;
322 if (max_event > 0)
323 {
324 LOGP(info, "{} is set to run with {} events", app_name_, max_event);
325 }
326 run_action(run_.get(), max_event);
327 }
328
329 void CLIApplication::run_action(FairRun* run, int num_of_events) { run->Run(0, num_of_events); }
330
332 {
334 auto input_span = option_.get().enable_mpi ? get_partition_from(input_files_, num_of_procs_, rank_num_)
335 : span{ input_files_ };
336 for (const auto& [filename, is_tree] : input_span)
337 {
338 filesource->AddFile(filename, is_tree);
339 }
340 }
341
343 {
344 auto file_path = fs::path{};
345 const auto& working_dir = option_.get().input.working_dir;
346 for (const auto& filename : option_.get().input.data)
347 {
348 // check if path if relative or full
349 file_path = working_dir.empty() ? fs::path{ filename } : fs::path{ working_dir } / fs::path{ filename };
350 auto fairroot_input_files = R3B::GetFilesFromRegex(file_path.string());
351 for (const auto& fairroot_input_file : fairroot_input_files)
352 {
353 input_files_.emplace_back(fairroot_input_file, false);
354 }
355 }
356 for (const auto& filename : option_.get().input.tree_data)
357 {
358 auto tree_input_files = R3B::GetFilesFromRegex(filename);
359 for (const auto& tree_input_file : tree_input_files)
360 {
361 input_files_.emplace_back(tree_input_file, true);
362 }
363 }
364 }
365
366 void CLIApplication::patch_files_or_strings(nlohmann::ordered_json& json_obj,
367 const std::vector<std::string>& filenames_or_options)
368 {
369 for (const auto& filename_or_option : filenames_or_options)
370 {
371 auto json_file_obj = [&filename_or_option]()
372 {
373 switch (check_json_input_type(filename_or_option))
374 {
375 case JSONConfigInputType::string:
376 {
377 auto json_string = transform_to_json_string(filename_or_option);
378 LOGP(info, "Reading the configuration from the string {:?}.", json_string);
379 return nlohmann::ordered_json::parse(std::move(json_string), nullptr, true, true);
380 }
381 case JSONConfigInputType::file:
382 {
383 auto file = std::ifstream{ filename_or_option };
384 LOGP(info, "Reading the configuration from the json file {:?}.", filename_or_option);
385 return nlohmann::ordered_json::parse(file, nullptr, true, true);
386 }
387 case JSONConfigInputType::invalid:
388 break;
389 }
390 throw R3B::logic_error(fmt::format("Cannot parse the string {:?}", filename_or_option));
391 }();
392 json_obj.merge_patch(json_file_obj);
393 }
394 }
395} // namespace R3B::Neuland
auto has_failed() const -> bool
auto has_inited() const -> bool
void set_inited(bool is_inited)
void post_parse() override
Action done after the option parsing.
static void patch_files_or_strings(nlohmann::ordered_json &json_obj, const std::vector< std::string > &filenames_or_options)
virtual void setup_application_options(CLI::App &program_options)
std::vector< std::pair< std::string, bool > > input_files_
virtual void post_init(FairRun *run)
void run() override
Run the CLI program.
std::reference_wrapper< Options > option_
void add_input_filename(R3BFileSource2 *filesource)
CLIApplication(std::string_view name, std::unique_ptr< FairRun > run, std::reference_wrapper< Options > option)
virtual void ParseApplicationOption(const std::vector< std::string > &filename_or_option)=0
void setup_options(CLI::App &program_options) override
Setup the CLI options given to the program.
virtual void pre_init(FairRun *run)=0
virtual void run_action(FairRun *run, int num_of_events)
void init() override
Initialization of a CLI program.
std::unique_ptr< FairRun > run_
void setup_common_options(CLIAPP &program_options, OptionType &options)
static auto Output()
Factory method to create an instance for parameter output.
static auto Input()
Factory method to create an instance for parameter input.
void AddFile(std::string file_name, bool is_tree_file=false)
Simulation of NeuLAND Bar/Paddle.
const auto DEFAULT_JSON_FILENAME
nlohmann::ordered_json json
auto GetFilesFromRegex(std::string_view filename_regex) -> std::vector< std::string >
Definition R3BShared.h:203