R3BROOT
R3B analysis software
Loading...
Searching...
No Matches
R3BNeulandApp.cxx
Go to the documentation of this file.
1#include "R3BNeulandApp.h"
3#include "R3BFileSource2.h"
4#include "R3BParRootFileIo.h"
5#include "R3BShared.h"
6#include <CLI/CLI.hpp>
7#include <FairParRootFileIo.h>
8#include <FairRootFileSink.h>
9#include <FairRun.h>
10#include <FairRuntimeDb.h>
11#include <algorithm>
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 <fairlogger/Logger.h>
18#include <filesystem>
19#include <fmt/base.h>
20#include <fmt/color.h>
21#include <fmt/core.h>
22#include <fmt/format.h>
23#include <functional>
24#include <gsl/span>
25#include <magic_enum/magic_enum.hpp>
26#include <memory>
27#include <ranges>
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 constexpr auto trim_space(std::string_view str) -> std::string_view
54 {
55 if (str.empty())
56 {
57 return std::string_view{};
58 }
59 const auto* start_pos = std::ranges::find_if(str, [](auto cha) -> bool { return cha != ' ' and cha != '\n'; });
60 auto end_pos = std::ranges::find_if(str | std::views::reverse,
61 [](auto cha) -> bool { return cha != ' ' and cha != '\n'; });
62 if (start_pos > end_pos.base())
63 {
64 return std::string_view{};
65 }
66 return std::string_view{ start_pos, end_pos.base() };
67 }
68
69} // namespace
70
71namespace R3B::Neuland
72{
73 CLIApplication::CLIApplication(std::string_view name,
74 std::unique_ptr<FairRun> run,
75 std::reference_wrapper<Options> option)
76 : app_name_{ name }
77 , dump_json_filename_{ fmt::format("{}_{}", name, DEFAULT_JSON_FILENAME) }
78 , run_(std::move(run))
79 , option_{ option }
80 {
82 timer_.Start();
83 }
84
85 CLIApplication::~CLIApplication() // NOLINT: an unknown place may throw
86 {
87 timer_.Stop();
88
89 if (has_inited())
90 {
91 LOGP(info, "Writing all parameters to files");
92 run_->GetRuntimeDb()->writeContainers();
93 if (auto* runtime_db = run_->GetRuntimeDb(); runtime_db != nullptr)
94 {
95 runtime_db->writeContainers();
96 }
97 if (auto* sink = run_->GetSink(); sink != nullptr)
98 {
99 sink->Close();
100 }
101
102 if (has_failed())
103 {
104 fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
105 "\nNeuland Application finished with a failure!\n\n");
106 }
107 else
108 {
109 fmt::print(fmt::emphasis::bold | fg(fmt::color::green),
110 "\nNeuland Application finished successfully!\n\n");
111 }
112 fmt::println("Real time: {}s, cpu time: {}s", timer_.RealTime(), timer_.CpuTime());
113 }
114 }
115
117 {
118 auto spec1 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
119 fair::VerbositySpec::Info::file_line_function);
120 fair::Logger::DefineVerbosity("user1", spec1);
121
122 auto spec2 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
123 fair::VerbositySpec::Info::timestamp_s,
124 fair::VerbositySpec::Info::file_line_function);
125 fair::Logger::DefineVerbosity("user2", spec2);
126
127 fair::Logger::SetConsoleColor(true);
128 }
129
131 {
132 fair::Logger::SetConsoleSeverity(option_.get().log_level);
133 fair::Logger::SetVerbosity(option_.get().verbose_level);
134 }
135
137 {
138 LOGP(info, "Initializing application ...");
139 set_inited(true);
142 pre_init(run_.get());
143 run_->Init();
144 post_init(run_.get());
145 LOGP(info, "Application is initialized.");
146 }
147
148 void CLIApplication::setup_options(CLI::App& program_options)
149 {
150 setup_common_options(program_options);
151 setup_application_options(program_options);
152 }
153
154 void CLIApplication::setup_common_options(CLI::App& program_options)
155 {
156 auto dump_config_callback = [this](const std::string& filename) -> void
157 {
158 is_dump_ = true;
159 dump_json_filename_ = filename;
160 };
161
162 auto use_config_callback = [this](const std::vector<std::string>& filename_or_option) -> void
163 {
164 if (not is_already_parsed_)
165 {
166 ParseApplicationOption(filename_or_option);
167 is_already_parsed_ = true;
168 }
169 };
170
171 auto& options = option_.get();
172 program_options
173 .add_option_function<std::vector<std::string>>(
174 "-c, --use-config", use_config_callback, "Set the json config file")
175 ->default_val(fmt::format("{}_{}", app_name_, DEFAULT_JSON_FILENAME))
176 ->run_callback_for_default()
177 ->allow_extra_args()
178 ->trigger_on_parse();
179 program_options.add_flag("--print-config", has_print_default_options_, "Print default option value");
180 program_options
181 .add_option_function<std::string>("--dump-config", dump_config_callback, "Dump the config into a json file")
182 ->default_val(dump_json_filename_)
183 ->run_callback_for_default()
184 ->expected(0, 1);
185 program_options.add_option("-s, --severity", options.log_level, "Set the severity level");
186 program_options.add_option("-v, --verbose", options.verbose_level, "Set the verbose level");
187 program_options.add_option("-n, --event-num", options.number_of_events, "Set the event number")
188 ->capture_default_str();
189 program_options.add_option("--run-id", options.run_id, "Set the run id")->capture_default_str();
190
191 program_options.add_option("-i, --input-file", options.input.data, "Set the input filenames (regex)")
192 ->capture_default_str()
193 ->group("Input options");
194 program_options
195 .add_option("--input-tree-file",
196 options.input.tree_data,
197 "Set the input filenames (regex) containing only root tree")
198 ->group("Input options");
199 program_options.add_option("--par-in", options.input.par, "Set the filename of the input parameter root file")
200 ->capture_default_str()
201 ->group("Input options");
202
203 program_options.add_option("-o, --output-file", options.output.data, "Set the output filename")
204 ->capture_default_str()
205 ->group("Output options");
206 program_options
207 .add_option("--par-out", options.output.par, "Set the filename of the output parameter root file")
208 ->capture_default_str()
209 ->group("Output options");
210 }
211
213 {
214 const auto& option = option_.get();
215
216 // output files:
217 const auto output_name =
218 option.enable_mpi ? fmt::format("{}.{}", option.output.data, rank_num_) : option.output.data;
219 if (not option_.get().output.data.empty())
220 {
221 // check if path if relative or full
222 auto file_path = option.output.working_dir.empty()
223 ? fs::path{ output_name }
224 : fs::path{ option.output.working_dir } / fs::path{ output_name };
225 auto root_file =
226 std::make_unique<TFile>(file_path.c_str(), magic_enum::enum_name(option.output.mode).data());
227 auto file_sink = std::make_unique<FairRootFileSink>(root_file.release());
228 run_->SetSink(file_sink.release());
229 }
230
231 // input files:
232 auto file_source = std::make_unique<R3BFileSource2>();
233 if (option_.get().run_id >= 0)
234 {
235 file_source->SetInitRunID(option_.get().run_id);
236 run_->SetRunId(option_.get().run_id);
237 LOGP(info, "Filesource2: Set to run id {}", option_.get().run_id);
238 }
239 add_input_filename(file_source.get());
240 if (not file_source->IsEmpty())
241 {
242 run_->SetSource(file_source.release());
243 }
244 }
245
247 {
248 const auto& input_option = option_.get().input;
249 const auto& output_option = option_.get().output;
250 const auto& input_wd = input_option.working_dir;
251 const auto& output_wd = output_option.working_dir;
252
253 if (not input_option.par.empty())
254 {
255 auto input_par_fileio = ParRootFileIo::Input();
256 for (const auto& par_filename : input_option.par)
257 {
258 if (not par_filename.empty())
259 {
260 auto file_path = [&]() -> fs::path
261 {
262 auto par_file = fs::path{ par_filename };
263
264 if (par_file.is_absolute())
265 {
266 return par_file;
267 }
268 return input_wd.empty() ? par_file : fs::path{ input_wd } / par_file;
269 }();
270 LOGP(info, "Input first parameter file is {:?}", file_path.c_str());
271 input_par_fileio->open(file_path.c_str(), "READ");
272 }
273 }
274 run_->GetRuntimeDb()->setFirstInput(input_par_fileio.release());
275 }
276
277 if (not output_option.par.empty())
278 {
279 const auto& option = option_.get();
280 const auto output_name =
281 option.enable_mpi ? fmt::format("{}.{}", output_option.par, rank_num_) : output_option.par;
282 auto file_path =
283 output_wd.empty() ? fs::path{ output_name } : fs::path{ output_wd } / fs::path{ output_name };
284 LOGP(info, "Output parameter file is {:?}", file_path.string());
285 auto output_par_fileio = ParRootFileIo::Output();
286 output_par_fileio->open(file_path.c_str(), magic_enum::enum_name(option.output.mode));
287 // auto fileio = std::make_unique<FairParRootFileIo>(true);
288 // fileio->open(file_path.c_str(), option.output.mode.c_str());
289 auto* rtdb = run_->GetRuntimeDb();
290 rtdb->setOutput(output_par_fileio.release());
291 }
292 }
293
295 {
296 auto max_event = option_.get().number_of_events;
297 max_event = max_event > 0 ? max_event : 0;
298 if (max_event > 0)
299 {
300 LOGP(info, "{} is set to run with {} events", app_name_, max_event);
301 }
302 run_action(run_.get(), max_event);
303 }
304
305 void CLIApplication::run_action(FairRun* run, int num_of_events) { run->Run(0, num_of_events); }
306
308 {
310 auto input_span = option_.get().enable_mpi ? get_partition_from(input_files_, num_of_procs_, rank_num_)
311 : span{ input_files_ };
312 for (const auto& [filename, is_tree] : input_span)
313 {
314 filesource->AddFile(filename, is_tree);
315 }
316 }
317
319 {
320 auto file_path = fs::path{};
321 const auto& working_dir = option_.get().input.working_dir;
322 for (const auto& filename : option_.get().input.data)
323 {
324 // check if path if relative or full
325 file_path = working_dir.empty() ? fs::path{ filename } : fs::path{ working_dir } / fs::path{ filename };
326 auto fairroot_input_files = R3B::GetFilesFromRegex(file_path.string());
327 for (const auto& fairroot_input_file : fairroot_input_files)
328 {
329 input_files_.emplace_back(fairroot_input_file, false);
330 }
331 }
332 for (const auto& filename : option_.get().input.tree_data)
333 {
334 auto tree_input_files = R3B::GetFilesFromRegex(filename);
335 for (const auto& tree_input_file : tree_input_files)
336 {
337 input_files_.emplace_back(tree_input_file, true);
338 }
339 }
340 }
341
343 {
344 auto is_json_native_string = [](std::string_view string) -> bool
345 {
346 const auto trim_string = trim_space(string);
347 return trim_string.starts_with('{') and trim_string.ends_with('}');
348 };
349
350 auto is_json_string = [](std::string_view string) -> bool { return string.contains('='); };
351 auto is_a_file = [](std::string_view string) -> bool { return std::filesystem::exists(string); };
352
353 if (is_json_native_string(input))
354 {
356 }
357 if (is_json_string(input))
358 {
360 }
361 if (is_a_file(input))
362 {
364 }
366 }
367
368 void CLIApplication::transform_to_json_string(std::string_view input, std::string& buffer)
369 {
370 auto raw_string = std::string{ input };
371 auto equal_pos = raw_string.find('=');
372 auto keys_string = raw_string.substr(0, equal_pos);
373 buffer = raw_string.substr(equal_pos + 1);
374
375 boost::algorithm::trim(keys_string);
376 boost::algorithm::trim(buffer);
377
378 auto keys = std::vector<std::string>{};
379
380 // NOLINTNEXTLINE (clang-analyzer-cplusplus.NewDeleteLeaks)
381 boost::split(keys, keys_string, boost::is_any_of("."));
382
383 for (const auto& key : boost::adaptors::reverse(keys))
384 {
385 buffer = fmt::format("{{ {:?} : {}}}", key, buffer);
386 }
387 }
388} // 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.
virtual void setup_application_options(CLI::App &program_options)
std::vector< std::pair< std::string, bool > > input_files_
static auto check_json_input_type(std::string_view input) -> JSONConfigInputType
virtual void post_init(FairRun *run)
static void transform_to_json_string(std::string_view input, std::string &buffer)
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
auto GetFilesFromRegex(std::string_view filename_regex) -> std::vector< std::string >
Definition R3BShared.h:204