ALPSCore reference
params.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1998-2018 ALPS Collaboration. See COPYRIGHT.TXT
3  * All rights reserved. Use is subject to license terms. See LICENSE.TXT
4  * For use in publications, see ACKNOWLEDGE.TXT
5  */
6 
11 #include <alps/params.hpp>
12 #include <algorithm>
13 #include <sstream>
14 #include <iomanip> // for help pretty-printing
15 #include <iterator> // for ostream_iterator
16 #include <cstring> // for memcmp()
17 #include <boost/optional.hpp>
18 
20 
22 
23 #include <alps/utilities/temporary_filename.hpp> // FIXME!!! Temporary!
24 #include <fstream>
25 
26 #include <alps/hdf5/map.hpp>
27 #include <alps/hdf5/vector.hpp>
28 
29 
30 #ifdef ALPS_HAVE_MPI
33 #endif
34 
35 
36 namespace alps {
37  namespace params_ns {
38 
39  namespace {
40  // Helper function to try to open an HDF5 archive, return "none" if it fails
41  boost::optional<alps::hdf5::archive> try_open_ar(const std::string& fname, const char* mode)
42  {
43  try {
44  //read in hdf5 checksum of file and verify it's a hdf5 file
45  {
46  std::ifstream f(fname.c_str(),std::ios::binary);
47  if(!f.good()) return boost::none;
48  static const char hdf5_checksum[]={char(137),72,68,70,13,10,26,10};
49  char firstbytes[sizeof(hdf5_checksum)];
50  f.read(firstbytes, sizeof(firstbytes));
51  if(!f.good() || memcmp(hdf5_checksum, firstbytes, sizeof(firstbytes))!=0) return boost::none;
52  }
53  return alps::hdf5::archive(fname, mode);
54  } catch (alps::hdf5::archive_error& ) {
55  return boost::none;
56  }
57  };
58 
59 
60  // Helper function to read INI file into the provided map
61  template <typename MAP_T>
62  void ini_file_to_map(const std::string& ini_name, MAP_T& map)
63  {
64  detail::iniparser parser(ini_name);
65  for(const detail::iniparser::kv_pair& kv: parser()) {
66  // FIXME!!! Check for duplicates and optionally warn!
67  std::string key=kv.first;
68  if (!key.empty() && key[0]=='.') key.erase(0,1);
69  map[key]=kv.second;
70  }
71  }
72 
73  // Helper functor to convert a string to a value of the
74  // same type as a dict_value and assign it to the dict_value
75  class parse_visitor {
76  const std::string& strparam_val_;
77  dictionary::value_type& dictval_;
78  public:
79  typedef bool result_type;
80 
81  parse_visitor(const std::string& strparam_val, dictionary::value_type& dval):
82  strparam_val_(strparam_val), dictval_(dval)
83  {}
84 
85  template <typename BOUND_T>
86  result_type operator()(const BOUND_T& bound_val) const
87  {
88  boost::optional<BOUND_T> maybe_val=detail::parse_string<BOUND_T>::apply(strparam_val_);
89  if (!maybe_val) return false;
90  dictval_=*maybe_val;
91  return true;
92  }
93 
94  result_type operator()(const dictionary::value_type::None&) const
95  {
96  return true;
97  }
98  };
99 
100 
102  static const char Default_help_description[]="Print help message";
103  }
104 
105  params::params(const std::string& inifile)
106  : dictionary(), raw_kv_content_(), td_map_(), err_status_(), origins_(), help_header_()
107  {
108  read_ini_file_(inifile);
109  if (!defined("help")) define("help", Default_help_description);
110  }
111 
112 
113 
114  params::params(int argc, const char* const* argv, const char* hdf5_path)
115  : dictionary(),
116  raw_kv_content_(),
117  td_map_(),
118  err_status_(),
119  origins_(),
120  help_header_()
121  {
122  initialize_(argc, argv, hdf5_path);
123  if (!defined("help")) define("help", Default_help_description);
124  }
125 
126 
128  {
129  return origins_.data().size()-origins_type::INIFILES;
130  }
131 
132  std::string params::get_ini_name(int n) const
133  {
134  if (n<0 || n>=get_ini_name_count()) return std::string();
135  return origins_.data()[origins_type::INIFILES+n];
136  }
137 
138  std::string params::get_argv0() const
139  {
140  return origins_.data()[origins_type::ARGV0];
141  }
142 
143  std::string params::get_archive_name() const
144  {
145  if (!this->is_restored()) throw std::runtime_error("The parameters object is not restored from an archive");
146  return origins_.data()[origins_type::ARCHNAME];
147  }
148 
149  params& params::description(const std::string &message)
150  {
151  help_header_=message;
152  if (!defined("help")) define("help", Default_help_description);
153  return *this;
154  }
155 
156  bool params::has_unused_(std::ostream& out, const std::string* prefix_ptr) const
157  {
158  strvec unused;
159  for(const strmap::value_type& kv: raw_kv_content_) {
160  bool relevant = !prefix_ptr // no specific prefix?
161  || (prefix_ptr->empty() ? kv.first.find('.')==std::string::npos // top-level section?
162  : kv.first.find(*prefix_ptr+".")==0); // starts with sec name?
163  if (relevant && !this->exists(kv.first)) {
164  unused.push_back(kv.first+" = "+kv.second);
165  }
166  }
167  if (!unused.empty()) {
168  out << "The following arguments are supplied, but never referenced:\n";
169  std::copy(unused.begin(), unused.end(), std::ostream_iterator<std::string>(out,"\n"));
170  }
171  return !unused.empty();
172  }
173 
174  bool params::has_unused(std::ostream& out, const std::string& subsection) const
175  {
176  return has_unused_(out, &subsection);
177  }
178 
179  bool params::has_unused(std::ostream& out) const
180  {
181  return has_unused_(out, 0);
182  }
183 
184  std::ostream& params::print_help(std::ostream& out) const
185  {
186  out << help_header_ << "\nAvailable options:\n";
187 
188  typedef std::pair<std::string, std::string> nd_pair; // name and description
189  typedef std::vector<nd_pair> nd_vector;
190  nd_vector name_and_description;
191  name_and_description.resize(td_map_.size());
192  std::string::size_type names_column_width=0;
193 
194  // prepare 2 columns: parameters and their description
195  for(const td_map_type::value_type& tdp: td_map_) {
196  const std::string name_and_type=tdp.first + " (" + tdp.second.typestr() + "):";
197  if (names_column_width<name_and_type.size()) names_column_width=name_and_type.size();
198 
199  std::ostringstream ostr;
200  boolalpha(ostr);
201  ostr << tdp.second.descr();
202  if (this->exists(tdp.first) && tdp.first!="help") {
203  ostr << " (default value: ";
204  print(ostr, (*this)[tdp.first], true) << ")";
205  }
206  // place the output on the line corresponding to definiton order
207  int defnum=tdp.second.defnumber();
208  if (defnum<0 || static_cast<unsigned int>(defnum)>=name_and_description.size()) {
209  std::ostringstream errmsg;
210  errmsg << "Invalid entry in parameters object.\n"
211  << "name='" << tdp.first
212  << "' defnumber=" << defnum;
213  throw std::logic_error(errmsg.str());
214  }
215  name_and_description[defnum]=std::make_pair(name_and_type, ostr.str());
216  }
217 
218  // print the columns
219  std::ostream::fmtflags oldfmt=out.flags();
220  left(out);
221  names_column_width += 4;
222  for(const nd_pair& ndp: name_and_description) {
223  out << std::left << std::setw(names_column_width) << ndp.first << ndp.second << "\n";
224  }
225  out.flags(oldfmt);
226 
227  return out;
228  }
229 
231  {
232  return this->exists<bool>("help") && (*this)["help"].as<bool>();
233  }
234 
235  bool params::help_requested(std::ostream& out) const
236  {
237  if (!this->help_requested()) return false;
238  print_help(out);
239  return true;
240  }
241 
242  bool params::has_missing(std::ostream& out) const
243  {
244  if (this->ok()) return false;
245  std::copy(err_status_.begin(), err_status_.end(), std::ostream_iterator<std::string>(out,"\n"));
246  return true;
247  }
248 
249  void params::initialize_(int argc, const char* const * argv, const char* hdf5_path)
250  {
251  // shortcuts:
252  typedef std::string::size_type size_type;
253  const size_type& npos=std::string::npos;
254  using std::string;
255  using boost::optional;
256 
257  if (argc==0) return;
258  origins_.data()[origins_type::ARGV0].assign(argv[0]);
259  if (argc<2) return;
260 
261  std::vector<string> all_args(argv+1,argv+argc);
262  std::stringstream cmd_options;
263  std::vector<string> ini_files;
264  optional<string> restored_from_archive;
265  bool file_args_mode=false;
266  for(const string& arg: all_args) {
267  if (file_args_mode) {
268  ini_files.push_back(arg);
269  continue;
270  }
271  size_type key_end=arg.find('=');
272  size_type key_begin=0;
273  if (arg.substr(0,2)=="--") {
274  if (arg.size()==2) {
275  file_args_mode=true;
276  continue;
277  }
278  key_begin=2;
279  } else if (arg.substr(0,1)=="-") {
280  key_begin=1;
281  }
282  if (0==key_begin && npos==key_end) {
283  if (hdf5_path) {
284  optional<alps::hdf5::archive> maybe_ar=try_open_ar(arg, "r");
285  if (maybe_ar) {
286  if (restored_from_archive) {
287  throw archive_conflict("More than one archive is specified in command line",
288  *restored_from_archive, arg);
289  }
290  maybe_ar->set_context(hdf5_path);
291  this->load(*maybe_ar);
292  origins_.data()[origins_type::ARCHNAME]=arg;
293  restored_from_archive=arg;
294  continue;
295  }
296  }
297 
298  ini_files.push_back(arg);
299  continue;
300  }
301  if (npos==key_end) {
302  cmd_options << arg.substr(key_begin) << "=true\n";
303  } else {
304  cmd_options << arg.substr(key_begin) << "\n";
305  }
306  }
307  for (auto fname: ini_files) {
308  read_ini_file_(fname);
309  }
310 
311  // FIXME!!!
312  // This is very inefficient and is done only for testing.
313  std::string tmpfile_name=alps::temporary_filename("tmp_ini_file");
314  std::ofstream tmpstream(tmpfile_name.c_str());
315  tmpstream << cmd_options.rdbuf();
316  tmpstream.close();
317  ini_file_to_map(tmpfile_name, raw_kv_content_);
318 
319  if (restored_from_archive) {
320  // The parameter object was restored from archive, and
321  // some key-values may have been supplied. We need to
322  // go through the already `define<T>()`-ed map values
323  // and try to parse the supplied string values as the
324  // corresponding types.
325 
326  // It's a bit of a mess:
327  // 1) We rely on that we can iterate over dictionary as a map
328  // 2) Although it's const-iterator, we know that underlying map can be modified
329  for (auto& kv: *this) {
330  const auto& key=kv.first;
331  auto raw_kv_it=raw_kv_content_.find(key);
332  if (raw_kv_it != raw_kv_content_.end()) {
333  bool ok=apply_visitor(parse_visitor(raw_kv_it->second, const_cast<dictionary::value_type&>(kv.second)), kv.second);
334  if (!ok) {
335  const auto typestr=td_map_[key].typestr();
336  throw exception::value_mismatch(key, "String '"+raw_kv_it->second+"' can't be parsed as type '"+typestr+"'");
337  }
338  }
339  }
340  }
341  }
342 
343  void params::read_ini_file_(const std::string& inifile)
344  {
345  ini_file_to_map(inifile, raw_kv_content_);
346  origins_.data().push_back(inifile);
347  }
348 
349  const std::string params::get_descr(const std::string& name) const
350  {
351  td_map_type::const_iterator it=td_map_.find(name);
352  return (td_map_.end()==it)? std::string() : it->second.descr();
353  }
354 
356  {
357  const params& lhs=*this;
358  const dictionary& lhs_dict=*this;
359  const dictionary& rhs_dict=rhs;
360  return
361  (lhs.raw_kv_content_ == rhs.raw_kv_content_) &&
362  (lhs.td_map_ == rhs.td_map_) &&
363  (lhs.err_status_ == rhs.err_status_) &&
364  (lhs.help_header_ == rhs.help_header_) &&
365  (lhs_dict==rhs_dict);
366  /* origins_ is excluded from comparison */
367  }
368 
369 
371  dictionary::save(ar);
372  const std::string context=ar.get_context();
373  // Convert the inifile map to vectors of keys, values
374  std::vector<std::string> raw_keys, raw_vals;
375  raw_keys.reserve(raw_kv_content_.size());
376  raw_vals.reserve(raw_kv_content_.size());
377  for(const strmap::value_type& kv: raw_kv_content_) {
378  raw_keys.push_back(kv.first);
379  raw_vals.push_back(kv.second);
380  }
381  ar[context+"@ini_keys"] << raw_keys;
382  ar[context+"@ini_values"] << raw_vals;
383  ar[context+"@status"] << err_status_;
384  ar[context+"@origins"] << origins_.data();
385  ar[context+"@help_header"] << help_header_;
386 
387  std::vector<std::string> keys=ar.list_children(context);
388  for(const std::string& key: keys) {
389  td_map_type::const_iterator it=td_map_.find(key);
390 
391  if (it!=td_map_.end()) {
392  ar[key+"@description"] << it->second.descr();
393  ar[key+"@defnumber"] << it->second.defnumber();
394  }
395  }
396  }
397 
399  params newpar;
400  newpar.dictionary::load(ar);
401 
402  const std::string context=ar.get_context();
403 
404  ar[context+"@status"] >> newpar.err_status_;
405  ar[context+"@origins"] >> newpar.origins_.data();
406  newpar.origins_.check();
407  ar[context+"@help_header"] >> newpar.help_header_;
408 
409  // Get the vectors of keys, values and convert them back to a map
410  {
411  typedef std::vector<std::string> stringvec;
412  stringvec raw_keys, raw_vals;
413  ar[context+"@ini_keys"] >> raw_keys;
414  ar[context+"@ini_values"] >> raw_vals;
415  if (raw_keys.size()!=raw_vals.size()) {
416  throw std::invalid_argument("params::load(): invalid ini-file data in HDF5 (size mismatch)");
417  }
418  stringvec::const_iterator key_it=raw_keys.begin();
419  stringvec::const_iterator val_it=raw_vals.begin();
420  for (; key_it!=raw_keys.end(); ++key_it, ++val_it) {
421  strmap::const_iterator insloc=newpar.raw_kv_content_.insert(newpar.raw_kv_content_.end(), std::make_pair(*key_it, *val_it));
422  if (insloc->second!=*val_it) {
423  throw std::invalid_argument("params::load(): invalid ini-file data in HDF5 (repeated key '"+insloc->first+"')");
424  }
425  }
426  }
427  std::vector<std::string> keys=ar.list_children(context);
428  for(const std::string& key: keys) {
429  const std::string d_attr=key+"@description";
430  const std::string num_attr=key+"@defnumber";
431  if (ar.is_attribute(d_attr)) {
432  std::string descr;
433  ar[d_attr] >> descr;
434 
435  int dn=-1;
436  if (ar.is_attribute(num_attr)) {
437  ar[num_attr] >> dn;
438  } else {
439  // FIXME? Issue a warning instead? How?
440  throw std::runtime_error("Invalid HDF5 format: missing attribute "+ num_attr);
441  }
442 
443  const_iterator it=newpar.find(key);
444  if (newpar.end()==it) {
445  throw std::logic_error("params::load(): loading the dictionary"
446  " missed key '"+key+"'??");
447  }
448  std::string typestr=apply_visitor(detail::make_typestr(), it);
449  newpar.td_map_.insert(std::make_pair(key, detail::td_type(typestr, descr, dn)));
450  }
451  }
452 
453  using std::swap;
454  swap(*this, newpar);
455  }
456 
457  namespace {
458  // Printing of a vector
459  // FIXME!!! Consolidate with other definitions and move to alps::utilities
460  template <typename T>
461  inline std::ostream& operator<<(std::ostream& strm, const std::vector<T>& vec)
462  {
463  typedef std::vector<T> vtype;
464  typedef typename vtype::const_iterator itype;
465 
466  strm << "[";
467  itype it=vec.begin();
468  const itype end=vec.end();
469 
470  if (end!=it) {
471  strm << *it;
472  for (++it; end!=it; ++it) {
473  strm << ", " << *it;
474  }
475  }
476  strm << "]";
477 
478  return strm;
479  }
480  }
481 
482  std::ostream& operator<<(std::ostream& s, const params& p) {
483  s << "[alps::params]"
484  << " origins=" << p.origins_.data() << " status=" << p.err_status_
485  << "\nRaw kv:\n";
486  for(const params::strmap::value_type& kv: p.raw_kv_content_) {
487  s << kv.first << "=" << kv.second << "\n";
488  }
489  s << "[alps::params] Dictionary:\n";
490  for (params::const_iterator it=p.begin(); it!=p.end(); ++it) {
491  const std::string& key=it->first;
492  const dict_value& val=it->second;
493  s << key << " = " << val;
494  params::td_map_type::const_iterator tdit = p.td_map_.find(key);
495  if (tdit!=p.td_map_.end()) {
496  s << " descr='" << tdit->second.descr()
497  << "' typestring='" << tdit->second.typestr() << "'"
498  << "' defnum=" << tdit->second.defnumber();
499  }
500  s << std::endl;
501  }
502  return s;
503  }
504 
505  std::string origin_name(const params& p)
506  {
507  std::string origin;
508  if (p.is_restored()) origin=p.get_archive_name();
509  else if (p.get_ini_name_count()>0) origin=p.get_ini_name(0);
510  else origin=alps::fs::get_basename(p.get_argv0());
511  return origin;
512  }
513 
514 #ifdef ALPS_HAVE_MPI
515  void params::broadcast(const alps::mpi::communicator& comm, int rank) {
516  this->dictionary::broadcast(comm, rank);
517  using alps::mpi::broadcast;
518  broadcast(comm, raw_kv_content_, rank);
519  broadcast(comm, td_map_, rank);
520  broadcast(comm, err_status_, rank);
521  broadcast(comm, origins_.data(), rank);
522  origins_.check();
523  }
524 #endif
525  } // ::params_ns
526 }// alps::
bool ok() const
No-errors status.
Definition: params.hpp:240
bool has_unused(std::ostream &out) const
True if there are parameters supplied but not defined; prints them out.
Definition: params.cpp:179
bool operator==(const params &rhs) const
Returns true if the objects are identical.
Definition: params.cpp:355
Parse sectioned INI file or HDF5 or command line, provide the results as dictionary.
Definition: params.hpp:84
friend std::ostream & operator<<(std::ostream &, const params &)
Prints parameters to a stream in an unspecified format.
Definition: params.cpp:482
void save(alps::hdf5::archive &) const
Saves parameter object to an archive.
Definition: params.cpp:370
bool defined(const std::string &name) const
Check whether a parameter was ever defined.
bool is_attribute(std::string path) const
Definition: archive.cpp:180
params & define(const std::string &name, const std::string &descr)
Defines a parameter; returns false on error, and records the error in the object. ...
const_iterator begin() const
Const-iterator to the beginning of the contained map.
Definition: dictionary.hpp:30
std::ostream & print_help(std::ostream &) const
Print help to the given stream.
Definition: params.cpp:184
void broadcast(C const &c, P &p, int r=0)
Definition: api.hpp:56
void swap(params &p1, params &p2)
std::string get_ini_name(int n) const
Access to ini file names (if any); returns empty string if out of range.
Definition: params.cpp:132
Encapsulation of an MPI communicator and some communicator-related operations.
Definition: mpi.hpp:111
void load(alps::hdf5::archive &)
Loads parameter object form an archive.
Definition: params.cpp:398
F::result_type apply_visitor(F &visitor, dictionary::const_iterator it)
Const-access visitor to a value by an iterator.
Definition: dictionary.hpp:110
int get_ini_name_count() const
Returns the number of ini file names.
Definition: params.cpp:127
params()
Default ctor: creates an empty parameters object.
Definition: params.hpp:129
const std::string get_descr(const std::string &name) const
Returns a string describing the parameter (or an empty string)
Definition: params.cpp:349
std::string origin_name(const params &p)
Convenience function to obtain the "origin" filename associated with the parameters object...
Definition: params.cpp:505
std::string get_archive_name() const
Convenience method: returns the archive name the object has been restored from, or throws...
Definition: params.cpp:143
Exception type: attempt to restore from 2 archives.
Definition: params.hpp:216
bool has_missing() const
True if there are missing or wrong-type parameters.
Definition: params.hpp:243
params & description(const std::string &message)
Sets a description for the help message and introduces "--help" flag (if not already defined) ...
Definition: params.cpp:149
void save(alps::hdf5::archive &ar) const
Save the dictionary to an archive.
Definition: dictionary.cpp:70
std::string get_basename(const std::string &path)
Returns the base name of the file (removing leading directories)
bool is_restored() const
Conveninece method: true if the object was restored from an archive.
Definition: params.hpp:234
std::ostream & print(std::ostream &s, const dict_value &dv, bool terse)
Definition: dict_value.cpp:186
std::string get_context() const
Definition: archive.cpp:144
Header for object-oriented interface to MPI for std::pair.
std::vector< std::string > list_children(std::string path) const
Definition: archive.cpp:259
std::string temporary_filename(const std::string &prefix)
Generate a reasonably unique file name with a given prefix.
const_iterator end() const
Const-iterator to the end of the contained map.
Definition: dictionary.hpp:33
void broadcast(const communicator &comm, T *vals, std::size_t count, int root)
Broadcasts array vals of a primitive type T, length count on communicator comm with root root ...
Definition: mpi.hpp:270
bool exists(const std::string &key) const
Check if a key exists and has a value (without creating the key)
Definition: dictionary.hpp:65
std::string get_argv0() const
Access to argv[0] (returns empty string if unknown)
Definition: params.cpp:138
Python-like dictionary.
Definition: dictionary.hpp:18
friend void swap(params &p1, params &p2)
const_iterator find(const std::string &key) const
Obtain read-only iterator to a name.
Definition: dictionary.hpp:54
detail::None None
"Empty value" type
Definition: dict_value.hpp:66
map_type::const_iterator const_iterator
Definition: dictionary.hpp:27
Header for object-oriented interface to MPI for std::map.
bool help_requested() const
True if user requested help.
Definition: params.cpp:230