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
96} // namespace
97
98namespace R3B::Neuland
99{
100 using json = nlohmann::ordered_json;
101 CLIApplication::CLIApplication(std::string_view name,
102 std::unique_ptr<FairRun> run,
103 std::reference_wrapper<Options> option)
104 : app_name_{ name }
105 , dump_json_filename_{ fmt::format("{}_{}", name, DEFAULT_JSON_FILENAME) }
106 , run_(std::move(run))
107 , option_{ option }
108 {
109 setup_logger();
110 timer_.Start();
111 }
112
113 CLIApplication::~CLIApplication() // NOLINT: an unknown place may throw
114 {
115 timer_.Stop();
116
117 if (has_inited())
118 {
119 LOGP(info, "Writting all parameters to files");
120 run_->GetRuntimeDb()->writeContainers();
121 if (auto* runtime_db = run_->GetRuntimeDb(); runtime_db != nullptr)
122 {
123 runtime_db->writeContainers();
124 }
125 if (auto* sink = run_->GetSink(); sink != nullptr)
126 {
127 sink->Close();
128 }
129
130 if (has_failed())
131 {
132 fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
133 "\nNeuland Application finished with a failure!\n\n");
134 }
135 else
136 {
137 fmt::print(fmt::emphasis::bold | fg(fmt::color::green),
138 "\nNeuland Application finished successfully!\n\n");
139 }
140 fmt::println("Real time: {}s, cpu time: {}s", timer_.RealTime(), timer_.CpuTime());
141 }
142 }
143
145 {
146 auto spec1 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
147 fair::VerbositySpec::Info::file_line_function);
148 fair::Logger::DefineVerbosity("user1", spec1);
149
150 auto spec2 = fair::VerbositySpec::Make(fair::VerbositySpec::Info::severity,
151 fair::VerbositySpec::Info::timestamp_s,
152 fair::VerbositySpec::Info::file_line_function);
153 fair::Logger::DefineVerbosity("user2", spec2);
154
155 fair::Logger::SetConsoleColor(true);
156 }
157
159 {
160 fair::Logger::SetConsoleSeverity(option_.get().log_level);
161 fair::Logger::SetVerbosity(option_.get().verbose_level);
162 }
163
165 {
166 LOGP(info, "Initializaing application ...");
167 set_inited(true);
170 pre_init(run_.get());
171 run_->Init();
172 post_init(run_.get());
173 LOGP(info, "Application is initialized.");
174 }
175
176 void CLIApplication::setup_options(CLI::App& program_options)
177 {
178 setup_common_options(program_options);
179 setup_application_options(program_options);
180 }
181
182 void CLIApplication::setup_common_options(CLI::App& program_options)
183 {
184 auto dump_config_callback = [this](const std::string& filename)
185 {
186 is_dump_ = true;
187 dump_json_filename_ = filename;
188 };
189
190 auto use_config_callback = [this](const std::vector<std::string>& filename_or_option)
191 {
192 if (not is_already_parsed_)
193 {
194 ParseApplicationOption(filename_or_option);
195 is_already_parsed_ = true;
196 }
197 };
198
199 auto& options = option_.get();
200 program_options
201 .add_option_function<std::vector<std::string>>(
202 "-c, --use-config", use_config_callback, "Set the json config file")
203 ->default_val(fmt::format("{}_{}", app_name_, DEFAULT_JSON_FILENAME))
204 ->run_callback_for_default()
205 ->allow_extra_args()
206 ->trigger_on_parse();
207 program_options.add_flag("--print-config", has_print_default_options_, "Print default option value");
208 program_options
209 .add_option_function<std::string>("--dump-config", dump_config_callback, "Dump the config into a json file")
210 ->default_val(dump_json_filename_)
211 ->run_callback_for_default()
212 ->expected(0, 1);
213 program_options.add_option("-s, --severity", options.log_level, "Set the severity level");
214 program_options.add_option("-v, --verbose", options.verbose_level, "Set the verbose level");
215 program_options.add_option("-n, --event-num", options.event_num, "Set the event number")->capture_default_str();
216 program_options.add_option("--run-id", options.run_id, "Set the run id")->capture_default_str();
217
218 program_options.add_option("-i, --input-file", options.input.data, "Set the input filenames (regex)")
219 ->capture_default_str()
220 ->group("Input options");
221 program_options
222 .add_option("--input-tree-file",
223 options.input.tree_data,
224 "Set the input filenames (regex) containing only root tree")
225 ->group("Input options");
226 program_options.add_option("--par-in", options.input.par, "Set the filename of the input parameter root file")
227 ->capture_default_str()
228 ->group("Input options");
229
230 program_options.add_option("-o, --output-file", options.output.data, "Set the output filename")
231 ->capture_default_str()
232 ->group("Output options");
233 program_options
234 .add_option("--par-out", options.output.par, "Set the filename of the output parameter root file")
235 ->capture_default_str()
236 ->group("Output options");
237 }
238
240 {
241 const auto& option = option_.get();
242
243 // output files:
244 const auto output_name =
245 option.enable_mpi ? fmt::format("{}.{}", option.output.data, rank_num_) : option.output.data;
246 if (not option_.get().output.data.empty())
247 {
248 // check if path if relative or full
249 auto file_path = option.output.working_dir.empty()
250 ? fs::path{ output_name }
251 : fs::path{ option.output.working_dir } / fs::path{ output_name };
252 auto file_sink = std::make_unique<FairRootFileSink>(file_path.c_str());
253 run_->SetSink(file_sink.release());
254 }
255
256 // input files:
257 auto file_source = std::make_unique<R3BFileSource2>();
258 if (option_.get().run_id >= 0)
259 {
260 file_source->SetInitRunID(option_.get().run_id);
261 run_->SetRunId(option_.get().run_id);
262 LOGP(info, "Filesource2: Set to run id {}", option_.get().run_id);
263 }
264 add_input_filename(file_source.get());
265 if (not file_source->IsEmpty())
266 {
267 run_->SetSource(file_source.release());
268 }
269 }
270
272 {
273 auto file_path = fs::path{};
274 const auto& input_option = option_.get().input;
275 const auto& output_option = option_.get().output;
276 const auto& input_wd = input_option.working_dir;
277 const auto& output_wd = output_option.working_dir;
278
279 if (not input_option.par.empty())
280 {
281 file_path =
282 input_wd.empty() ? fs::path{ input_option.par } : fs::path{ input_wd } / fs::path{ input_option.par };
283 auto fileio = ParRootFileIo::Input();
284 LOGP(info, "Input first parameter file is {:?}", file_path.string());
285 fileio->open(file_path.c_str(), "READ");
286 run_->GetRuntimeDb()->setFirstInput(fileio.release());
287 }
288
289 if (not output_option.par.empty())
290 {
291 const auto& option = option_.get();
292 const auto output_name =
293 option.enable_mpi ? fmt::format("{}.{}", output_option.par, rank_num_) : output_option.par;
294 file_path = output_wd.empty() ? fs::path{ output_name } : fs::path{ output_wd } / fs::path{ output_name };
295 LOGP(info, "Ouptut parameter file is {:?}", file_path.string());
296 auto fileio = ParRootFileIo::Output();
297 fileio->open(file_path.c_str(), option.output.mode);
298 // auto fileio = std::make_unique<FairParRootFileIo>(true);
299 // fileio->open(file_path.c_str(), option.output.mode.c_str());
300 auto* rtdb = run_->GetRuntimeDb();
301 rtdb->setOutput(fileio.release());
302 }
303 }
304
306 {
307 auto max_event = option_.get().event_num;
308 max_event = max_event > 0 ? max_event : 0;
309 if (max_event > 0)
310 {
311 LOGP(info, "{} is set to run with {} events", app_name_, max_event);
312 }
313 run_action(run_.get(), max_event);
314 }
315
316 void CLIApplication::run_action(FairRun* run, int num_of_events) { run->Run(0, num_of_events); }
317
319 {
321 auto input_span = option_.get().enable_mpi ? get_partition_from(input_files_, num_of_procs_, rank_num_)
322 : span{ input_files_ };
323 for (const auto& [filename, is_tree] : input_span)
324 {
325 filesource->AddFile(filename, is_tree);
326 }
327 }
328
330 {
331 auto file_path = fs::path{};
332 const auto& working_dir = option_.get().input.working_dir;
333 for (const auto& filename : option_.get().input.data)
334 {
335 // check if path if relative or full
336 file_path = working_dir.empty() ? fs::path{ filename } : fs::path{ working_dir } / fs::path{ filename };
337 auto fairroot_input_files = R3B::GetFilesFromRegex(file_path.string());
338 for (const auto& fairroot_input_file : fairroot_input_files)
339 {
340 input_files_.emplace_back(fairroot_input_file, false);
341 }
342 }
343 for (const auto& filename : option_.get().input.tree_data)
344 {
345 auto tree_input_files = R3B::GetFilesFromRegex(filename);
346 for (const auto& tree_input_file : tree_input_files)
347 {
348 input_files_.emplace_back(tree_input_file, true);
349 }
350 }
351 }
352
353 void CLIApplication::patch_files_or_strings(nlohmann::ordered_json& json_obj,
354 const std::vector<std::string>& filenames_or_options)
355 {
356 for (const auto& filename_or_option : filenames_or_options)
357 {
358 auto json_file_obj = [&filename_or_option]()
359 {
360 switch (check_json_input_type(filename_or_option))
361 {
362 case JSONConfigInputType::string:
363 {
364 auto json_string = transform_to_json_string(filename_or_option);
365 LOGP(info, "Reading the configuration from the string {:?}.", json_string);
366 return nlohmann::ordered_json::parse(std::move(json_string), nullptr, true, true);
367 }
368 case JSONConfigInputType::file:
369 {
370 auto file = std::ifstream{ filename_or_option };
371 LOGP(info, "Reading the configuration from the json file {:?}.", filename_or_option);
372 return nlohmann::ordered_json::parse(file, nullptr, true, true);
373 }
374 case JSONConfigInputType::invalid:
375 break;
376 }
377 throw R3B::logic_error(fmt::format("Cannot parse the string {:?}", filename_or_option));
378 }();
379 json_obj.merge_patch(json_file_obj);
380 }
381 }
382} // 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:204