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