prune_configuration.cc
Go to the documentation of this file.
5 #include "boost/algorithm/string.hpp"
9 
10 #include <initializer_list>
11 #include <iostream>
12 #include <regex>
13 #include <set>
14 
15 using namespace fhicl;
16 using namespace std::string_literals;
17 using namespace art::detail;
18 
21 using modules_t = std::map<std::string, std::string>;
22 
23 namespace {
24 
25  std::string const at_nil{"@nil"};
26  std::string const trigger_paths_str{"trigger_paths"};
27  std::string const end_paths_str{"end_paths"};
28  bool
29  matches(std::string const& path_spec_str, std::string const& path_name)
30  {
31  std::regex const path_spec_re{R"((\d+:)?)" + path_name};
32  return std::regex_match(path_spec_str, path_spec_re);
33  }
34 
35  auto module_tables = {"physics.producers",
36  "physics.filters",
37  "physics.analyzers",
38  "outputs"};
39  auto modifier_tables = {"physics.producers", "physics.filters"};
40  auto observer_tables = {"physics.analyzers", "outputs"};
41 
42  auto allowed_physics_tables = {"producers", "filters", "analyzers"};
43 
44  enum class ModuleCategory { modifier, observer, unset };
45 
46  auto
47  config_exception(std::string const& context)
48  {
49  return art::Exception{art::errors::Configuration, context + "\n"};
50  }
51 
52  auto
53  path_exception(std::string const& selection_override,
54  std::size_t const i,
55  std::string const& suffix)
56  {
57  std::string msg{"The following error occurred while processing " +
58  selection_override + "[" + std::to_string(i) + "]"};
59  msg += " (i.e. '" + suffix + "'):";
60  return config_exception(msg);
61  }
62 
63  std::ostream&
64  operator<<(std::ostream& os, ModuleCategory const cat)
65  {
66  switch (cat) {
67  case ModuleCategory::modifier:
68  os << "modifier";
69  break;
70  case ModuleCategory::observer:
71  os << "observer";
72  break;
73  case ModuleCategory::unset:
74  os << "unset";
75  }
76  return os;
77  }
78 
80  opposite(ModuleCategory const cat)
81  {
82  auto result = ModuleCategory::unset;
83  switch (cat) {
84  case ModuleCategory::modifier:
85  result = ModuleCategory::observer;
86  break;
87  case ModuleCategory::observer:
88  result = ModuleCategory::modifier;
89  break;
90  case ModuleCategory::unset: {
92  << "The " << cat << " category has no opposite.\n";
93  }
94  }
95  return result;
96  }
97 
98  auto
99  module_category(std::string const& full_module_key)
100  {
101  // For a full module key (e.g. physics.analyzers.a), we strip off
102  // the module name ('a') to determine its module category.
103  auto const table =
104  full_module_key.substr(0, full_module_key.find_last_of('.'));
105  if (cet::search_all(modifier_tables, table)) {
106  return ModuleCategory::modifier;
107  }
108  if (cet::search_all(observer_tables, table)) {
109  return ModuleCategory::observer;
110  }
111  return ModuleCategory::unset;
112  }
113 
114  bool
115  is_path_selection_override(std::string const& path_name)
116  {
117  return path_name == trigger_paths_str or path_name == end_paths_str;
118  }
119 
121  path_selection_override(ModuleCategory const category)
122  {
123  switch (category) {
124  case ModuleCategory::modifier:
125  return trigger_paths_str;
126  case ModuleCategory::observer:
127  return end_paths_str;
128  default:
129  assert(false); // Unreachable
130  return {};
131  }
132  }
133 
134  // module name => full module key
135  modules_t
136  declared_modules(intermediate_table const& config,
137  std::initializer_list<char const*> tables)
138  {
140  for (auto const tbl : tables) {
141  if (!exists_outside_prolog(config, tbl))
142  continue;
143  table_t const& table = config.find(tbl);
144  for (auto const& [modname, value] : table) {
145  // Record only tables, which are the allowed FHiCL values for
146  // module configurations.
147  if (!value.is_a(TABLE)) {
148  continue;
149  }
150 
151  auto const key = fhicl_key(tbl, modname);
152  auto const [it, success] = result.try_emplace(modname, fhicl_key(key));
153  if (!success && it->second != key) {
154  auto const& cached_key = it->second;
155  auto const parent =
156  cached_key.substr(0, cached_key.rfind(modname) - 1);
157  throw config_exception("An error occurred while processing "
158  "module configurations.")
159  << "Module label '" << modname << "' has been used in '" << tbl
160  << "' and '" << parent << "'.\n"
161  << "Module labels must be unique across an art process.\n";
162  }
163  }
164  }
165  return result;
166  }
167 
168 }
169 
170 namespace art::detail {
171  std::vector<ModuleSpec>
172  sequence_to_entries(sequence_t const& seq, bool const allow_nil_entries)
173  {
174  std::vector<ModuleSpec> result;
175  for (auto const& ev : seq) {
176  if (allow_nil_entries and ev.is_a(NIL)) {
177  result.push_back({at_nil, FilterAction::Normal});
178  continue;
179  }
180  if (!ev.is_a(STRING)) {
181  continue;
182  }
183  auto mod_spec = ev.to_string();
184  if (empty(mod_spec)) {
185  continue;
186  }
187  boost::replace_all(mod_spec, "\"", "");
189  if (mod_spec[0] == '!') {
190  action = FilterAction::Veto;
191  mod_spec = mod_spec.substr(1);
192  } else if (mod_spec[0] == '-') {
193  action = FilterAction::Ignore;
194  mod_spec = mod_spec.substr(1);
195  }
196 
197  // Handle remaining '!' or '-' characters
198  if (mod_spec.find_first_of("!-") != std::string::npos) {
199  throw config_exception("There was an error parsing the entry "s +
200  ev.to_string() + "in a FHiCL sequence.")
201  << "The '!' or '-' character may appear as only the first character "
202  "in the path entry.\n";
203  }
204  result.push_back({mod_spec, action});
205  }
206 
207  if (result.size() != seq.size()) {
208  throw config_exception("There was an error parsing the specified entries "
209  "in a FHiCL sequence.")
210  << "One of the presented elements is either an empty string or not a "
211  "string at all.\n";
212  }
213  return result;
214  }
215 
216  std::vector<art::PathSpec>
217  path_specs(std::vector<ModuleSpec> const& selection_override_entries,
218  std::string const& path_selection_override)
219  {
220  auto guidance = [](std::string const& name,
221  std::string const& path_selection_override,
222  std::string const& id_str) {
223  std::ostringstream oss;
224  oss << "If you would like to repeat the path specification, all "
225  "path specifications\n"
226  "with the name '"
227  << name << "' must be prepended with the same path ID (e.g.):\n\n"
228  << " " << path_selection_override << ": ['" << id_str << ':' << name
229  << "', '" << id_str << ':' << name << "', ...]\n\n";
230  return oss.str();
231  };
232 
233  std::map<PathID, std::string> id_to_name;
234  std::map<std::string, PathID> name_to_id;
235  std::vector<art::PathSpec> result;
236 
237  size_t i = 0;
238  for (auto it = cbegin(selection_override_entries),
239  e = cend(selection_override_entries);
240  it != e;
241  ++it, ++i) {
242  auto const& path = *it;
243  auto spec = art::path_spec(path.name);
244  if (spec.name == at_nil) {
245  continue;
246  }
247 
248  // Path names with unspecified IDs cannot be reused
249  auto const emplacement_result =
250  name_to_id.try_emplace(spec.name, spec.path_id);
251  bool const name_already_present = not emplacement_result.second;
252  auto const emplaced_path_id = emplacement_result.first->second;
253 
254  if (name_already_present) {
255  if (spec.path_id == art::PathID::invalid()) {
256  throw path_exception(path_selection_override, i, path.name)
257  << "The path name '" << spec.name
258  << "' has already been specified in the " << path_selection_override
259  << " sequence.\n"
260  << guidance(spec.name,
261  path_selection_override,
262  to_string(emplaced_path_id));
263  }
264  if (spec.path_id != emplaced_path_id) {
265  throw path_exception(path_selection_override, i, path.name)
266  << "The path name '" << spec.name
267  << "' has already been specified (perhaps implicitly) with a\n"
268  "path ID of "
269  << to_string(emplaced_path_id) << " (not "
270  << to_string(spec.path_id) << ") in the " << path_selection_override
271  << " sequence.\n\n"
272  << guidance(spec.name,
273  path_selection_override,
274  to_string(emplaced_path_id));
275  }
276  // Name is already present and the PathID has been explicitly
277  // listed and matches what has already been seen.
278  continue;
279  }
280 
281  if (spec.path_id == art::PathID::invalid()) {
282  spec.path_id =
283  art::PathID{i}; // Use calculated bit number if not specified
284  emplacement_result.first->second = spec.path_id;
285  }
286 
287  // Each ID must have only one name
288  if (auto const [it, inserted] =
289  id_to_name.try_emplace(spec.path_id, spec.name);
290  not inserted) {
291  throw path_exception(path_selection_override, i, path.name)
292  << "Path ID " << to_string(spec.path_id)
293  << " cannot be assigned to path name '" << spec.name
294  << "' as it has already been assigned to path name '" << it->second
295  << "'.\n";
296  }
297 
298  result.push_back(std::move(spec));
299  }
300  return result;
301  }
302 }
303 
304 namespace {
305  void
306  verify_supported_names(table_t const& physics_table)
307  {
308  std::string bad_names{};
309  for (auto const& [name, value] : physics_table) {
310  if (value.is_a(SEQUENCE)) {
311  continue;
312  }
313 
314  bool const is_table = value.is_a(TABLE);
315  if (is_table and cet::search_all(allowed_physics_tables, name)) {
316  continue;
317  }
318  std::string const type = is_table ? "table" : "atom";
319  bad_names += " \"physics." + name + "\" (" + type + ")\n";
320  }
321 
322  if (empty(bad_names)) {
323  return;
324  }
325 
326  throw config_exception(
327  "\nYou have specified the following unsupported parameters in the\n"
328  "\"physics\" block of your configuration:\n")
329  << bad_names
330  << "\nSupported parameters include the following tables:\n"
331  " \"physics.producers\"\n"
332  " \"physics.filters\"\n"
333  " \"physics.analyzers\"\n"
334  "and sequences. Atomic configuration parameters are not "
335  "allowed.\n\n";
336  }
337 
338  void
339  replace_empty_paths(intermediate_table& config,
340  std::set<std::string> const& empty_paths,
341  std::string const& path_selection_override)
342  {
343  if (not exists_outside_prolog(config, path_selection_override)) {
344  return;
345  }
346  sequence_t result = config.find(path_selection_override);
347  for (auto const& name : empty_paths) {
348  std::replace_if(begin(result),
349  end(result),
350  [&name](auto const& ex_val) {
351  if (not ex_val.is_a(STRING)) {
352  return false;
353  }
354  std::string path_spec_str;
355  fhicl::detail::decode(ex_val.value, path_spec_str);
356  return matches(path_spec_str, name);
357  },
358  fhicl::extended_value{false, NIL, at_nil});
359  }
360  config.get<sequence_t&>(path_selection_override) = result;
361  }
362 
364  all_paths(intermediate_table& config)
365  {
366  std::string const physics{"physics"};
367  if (!exists_outside_prolog(config, physics))
368  return {};
369 
370  std::set<std::string> empty_paths;
371  std::map<std::string, std::vector<ModuleSpec>> paths;
372  table_t const& table = config.find(physics);
373  verify_supported_names(table);
374  for (auto const& [path_name, module_names] : table) {
375  if (!module_names.is_a(SEQUENCE)) {
376  continue;
377  }
378  sequence_t const entries = module_names;
379  if (empty(entries)) {
380  empty_paths.insert(path_name);
381  }
382  paths[path_name] = sequence_to_entries(
383  module_names, is_path_selection_override(path_name));
384  }
385 
386  // Replace empty paths from trigger_paths and end_paths with @nil
387  replace_empty_paths(
388  config, empty_paths, fhicl_key(physics, trigger_paths_str));
389  replace_empty_paths(config, empty_paths, fhicl_key(physics, end_paths_str));
390 
391  return paths;
392  }
393 
394  // The return type of 'paths_for_category' is a vector of pairs -
395  // i.e. hence the "ordered" component of the return type. By
396  // choosing this return type, we are able to preserve the
397  // user-specified order in 'trigger_paths'. In this case, art
398  // specified the ordering for the user, but we use the return type
399  // that matches that of the 'explicitly_declared_paths' function.
400 
402  paths_for_category(module_entries_for_path_t const& all_paths,
403  modules_t const& modules,
404  ModuleCategory const category)
405  {
407  module_entries_for_path_t sorted_result;
408  for (auto const& [path_name, entries] : all_paths) {
409  // Skip over special path names, which are handled later.
410  if (is_path_selection_override(path_name)) {
411  continue;
412  }
413  std::vector<ModuleSpec> right_modules;
414  std::vector<std::string> wrong_modules;
415  for (auto const& mod_spec : entries) {
416  auto const& name = mod_spec.name;
417  auto full_module_key_it = modules.find(name);
418  if (full_module_key_it == cend(modules)) {
419  throw config_exception("The following error occurred while "
420  "processing a path configuration:")
421  << "Entry with name " << name << " in path " << path_name
422  << " does not have a module configuration.\n";
423  }
424  auto const& full_module_key = full_module_key_it->second;
425  auto const module_cat = module_category(full_module_key);
426  assert(module_cat != ModuleCategory::unset);
427  if (module_cat == category) {
428  right_modules.push_back(mod_spec);
429  } else {
430  wrong_modules.push_back(name);
431  }
432  }
433 
434  if (right_modules.empty()) {
435  // None of the modules in the path was of the correct
436  // category. This means that all modules were of the opposite
437  // category--we can safely skip it.
438  continue;
439  }
440 
441  if (right_modules.size() == entries.size()) {
442  sorted_result.try_emplace(path_name, move(right_modules));
443  } else {
444  // This is the case where a path contains a mixture of
445  // modifiers and observers.
446  auto e = config_exception("An error occurred while "
447  "processing a path configuration.");
448  e << "The following modules specified in path " << path_name << " are "
449  << opposite(category)
450  << "s when all\n"
451  "other modules are "
452  << category << "s:\n";
453  for (auto const& modname : wrong_modules) {
454  e << " '" << modname << "'\n";
455  }
456  throw e;
457  }
458  }
459 
460  // Convert to correct type
462  size_t i = 0;
463  for (auto const& [path_name, modules] : sorted_result) {
464  result.emplace_back(art::PathSpec{path_name, art::PathID{i++}}, modules);
465  }
466  return result;
467  }
468 
470  explicitly_declared_paths(module_entries_for_path_t const& modules_for_path,
471  std::vector<ModuleSpec> const& override_entries,
472  modules_t const& modules,
473  std::string const& path_selection_override)
474  {
475  auto specs =
476  art::detail::path_specs(override_entries, path_selection_override);
477 
479 
480  std::ostringstream os;
481  for (auto& spec : specs) {
482  auto res = modules_for_path.find(spec.name);
483  if (res == cend(modules_for_path)) {
484  os << "Unknown path " << spec.name << " has been specified in '"
485  << path_selection_override << "'.\n";
486  continue;
487  }
488 
489  // Skip empty paths
490  if (empty(res->second)) {
491  continue;
492  }
493 
494  // Check that module names in paths are supported
495  for (auto const& entry : res->second) {
496  auto const& name = entry.name;
497  auto full_module_key_it = modules.find(name);
498  if (full_module_key_it == cend(modules)) {
499  throw config_exception("The following error occurred while "
500  "processing a path configuration:")
501  << "Entry with name " << name << " in path " << spec.name
502  << " does not have a module configuration.\n";
503  }
504  }
505  result.emplace_back(std::move(spec), res->second);
506  }
507 
508  auto const err = os.str();
509  if (!err.empty()) {
510  throw config_exception(
511  "The following error occurred while processing path configurations:")
512  << err;
513  }
514 
515  return result;
516  }
517 
519  get_enabled_modules(modules_t const& modules,
520  module_entries_for_ordered_path_t const& enabled_paths,
521  ModuleCategory const category)
522  {
524  for (auto const& [path_spec, entries] : enabled_paths) {
525  for (auto const& [module_name, action] : entries) {
526  auto const& module_key = modules.at(module_name);
527  auto const actual_category = module_category(module_key);
528  auto const type = module_type(module_key);
529  if (actual_category != category) {
530  throw config_exception("The following error occurred while "
531  "processing a path configuration:")
532  << "The '" << path_selection_override(category)
533  << "' override parameter contains the path " << path_spec.name
534  << ", which has"
535  << (actual_category == ModuleCategory::observer ? " an\n" : " a\n")
536  << to_string(type) << " with the name " << module_name << ".\n\n"
537  << "Path " << path_spec.name
538  << " should instead be included as part of the '"
539  << path_selection_override(opposite(category)) << "' parameter.\n"
540  << "Contact artists@fnal.gov for guidance.\n";
541  }
544  throw config_exception("The following error occurred while "
545  "processing a path configuration:")
546  << "Entry with name " << module_name << " in path "
547  << path_spec.name << " is"
548  << (category == ModuleCategory::observer ? " an " : " a ")
549  << to_string(type) << " and cannot have a '!' or '-' prefix.\n";
550  }
551  result.try_emplace(module_name, ModuleKeyAndType{module_key, type});
552  }
553  }
554  return result;
555  }
556 
557  std::pair<module_entries_for_ordered_path_t, bool>
558  enabled_paths(module_entries_for_path_t const& paths,
559  modules_t const& modules,
560  ModuleCategory const category)
561  {
562  auto const selection_override = path_selection_override(category);
563  auto const it = paths.find(selection_override);
564  if (it == cend(paths)) {
565  return {paths_for_category(paths, modules, category), false};
566  }
567 
568  return {
569  explicitly_declared_paths(paths, it->second, modules, selection_override),
570  true};
571  }
572 }
573 
575 art::detail::prune_config_if_enabled(bool const prune_config,
576  bool const report_enabled,
578 {
579  auto const modules = declared_modules(config, module_tables);
580 
581  auto paths = all_paths(config);
582 
583  auto [trigger_paths, trigger_paths_override] =
584  enabled_paths(paths, modules, ModuleCategory::modifier);
585  auto [end_paths, end_paths_override] =
586  enabled_paths(paths, modules, ModuleCategory::observer);
587 
588  auto enabled_modules =
589  get_enabled_modules(modules, trigger_paths, ModuleCategory::modifier);
590  // C++17 provides the std::map::merge member function, but Clang 7
591  // and older does not support it. Will do it by hand for now, until
592  // we have time to handle this properly.
593  auto end_path_enabled_modules =
594  get_enabled_modules(modules, end_paths, ModuleCategory::observer);
595  enabled_modules.insert(begin(end_path_enabled_modules),
596  end(end_path_enabled_modules));
597 
598  modules_t unused_modules;
599  for (auto const& pr : modules) {
600  if (enabled_modules.find(pr.first) == cend(enabled_modules)) {
601  unused_modules.insert(pr);
602  }
603  }
604 
605  // Find unused paths
606  paths.erase("trigger_paths");
607  paths.erase("end_paths");
608  for (auto const& pr : trigger_paths) {
609  paths.erase(pr.first.name);
610  }
611  for (auto const& pr : end_paths) {
612  paths.erase(pr.first.name);
613  }
614 
615  // The only paths left are those that are not enabled for execution.
616  if (report_enabled && !empty(paths)) {
617  std::cerr << "The following paths have not been enabled for execution and "
618  "will be ignored:\n";
619  for (auto const& pr : paths) {
620  std::cerr << " " << pr.first << '\n';
621  }
622  }
623 
624  if (report_enabled && !empty(unused_modules)) {
625  std::ostringstream os;
626  os << "The following module label"
627  << ((unused_modules.size() == 1) ? " is" : "s are")
628  << " either not assigned to any path,\n"
629  << "or " << ((unused_modules.size() == 1ull) ? "it has" : "they have")
630  << " been assigned to ignored path(s):\n";
631  for (auto const& pr : unused_modules) {
632  os << " " << pr.first << '\n';
633  }
634  std::cerr << os.str();
635  }
636 
637  if (prune_config) {
638  for (auto const& pr : paths) {
639  config.erase(fhicl_key("physics", pr.first));
640  }
641  for (auto const& pr : unused_modules) {
642  config.erase(pr.second);
643  }
644 
645  // Check if module tables can be removed
646  for (auto const& table_name : module_tables) {
647  if (not exists_outside_prolog(config, table_name)) {
648  continue;
649  }
650  if (table_t const value = config.find(table_name); empty(value)) {
651  config.erase(table_name);
652  }
653  }
654 
655  // Check if top-level physics table can be removed
656  if (exists_outside_prolog(config, "physics")) {
657  if (table_t const value = config.find("physics"); empty(value)) {
658  config.erase("physics");
659  }
660  }
661  }
662 
663  // Place 'trigger_paths' as top-level configuration table
664  if (trigger_paths_override) {
665  std::vector<std::string> explicit_entries;
666  cet::transform_all(trigger_paths,
667  back_inserter(explicit_entries),
668  [](auto const& pr) { return to_string(pr.first); });
669  config.put("trigger_paths.trigger_paths", explicit_entries);
670  }
671 
672  return EnabledModules{std::move(enabled_modules),
673  std::move(trigger_paths),
674  std::move(end_paths),
675  trigger_paths_override,
676  end_paths_override};
677 }
static QCString name
Definition: declinfo.cpp:673
end
while True: pbar.update(maxval-len(onlies[E][S])) #print iS, "/", len(onlies[E][S]) found = False for...
static void replace_all(std::string &str, const std::string &old, const std::string &repl)
QList< Entry > entry
ModuleCategory
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:87
static QCString result
bool exists_outside_prolog(fhicl::intermediate_table const &config, std::string const &key)
void msg(const char *fmt,...)
Definition: message.cpp:107
std::vector< ModuleSpec > sequence_to_entries(sequence_t const &seq, bool const allow_nil_entries)
std::string string
Definition: nybbler.cc:12
char & at(uint i) const
Definition: qcstring.h:326
std::map< std::string, ModuleKeyAndType > keytype_for_name_t
ModuleType module_type(std::string const &full_key)
std::map< std::string, std::vector< ModuleSpec >> module_entries_for_path_t
int find(char c, int index=0, bool cs=TRUE) const
Definition: qcstring.cpp:41
int find(const type *d) const
Definition: qlist.h:88
std::string to_string(Protection p)
Definition: Protection.cc:4
Definition: types.h:29
void decode(std::any const &, std::string &)
std::map< std::string, std::string > modules_t
EnabledModules prune_config_if_enabled(bool prune_config, bool report_enabled, fhicl::intermediate_table &config)
bool search_all(FwdCont const &, Datum const &)
const double e
def key(type, name=None)
Definition: graph.py:13
static Config * config
Definition: config.cpp:1054
def move(depos, offset)
Definition: depos.py:107
std::enable_if_t< std::is_convertible_v< T, std::string >, std::string > fhicl_key(T const &name)
Definition: fhicl_key.h:12
std::vector< extended_value > sequence_t
shims::map< std::string, extended_value > table_t
std::string name
Definition: PathSpec.h:48
bool put(std::string const &name, std::string const &value, bool in_prolog=false)
auto transform_all(Container &, OutputIt, UnaryOp)
void err(const char *fmt,...)
Definition: message.cpp:226
fhicl::extended_value::sequence_t sequence_t
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
void erase(std::string const &key, bool in_prolog=false)
extended_value const & find(std::string const &key) const
constexpr static auto invalid() noexcept
Definition: PathSpec.h:20
T get(std::string const &name)
decltype(auto) constexpr cbegin(T &&obj)
ADL-aware version of std::cbegin.
Definition: StdUtils.h:82
bool is_table(par_type const pt)
PathSpec path_spec(std::string const &path_spec)
Definition: PathSpec.cc:22
std::vector< art::PathSpec > path_specs(std::vector< ModuleSpec > const &selection_override_entries, std::string const &path_selection_override)
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:72
std::ostream & operator<<(std::ostream &, ParameterSetID const &)
std::string to_string(ModuleType const mt)
Definition: ModuleType.h:34
static QCString * s
Definition: config.cpp:1042
def parent(G, child, parent_type)
Definition: graph.py:67
decltype(auto) constexpr empty(T &&obj)
ADL-aware version of std::empty.
Definition: StdUtils.h:97
std::vector< std::pair< PathSpec, std::vector< ModuleSpec >>> module_entries_for_ordered_path_t