Utopia  2
Framework for studying models of complex & adaptive systems.
cfg_utils.hh
Go to the documentation of this file.
1 #ifndef DATAIO_CFG_UTILS_HH
2 #define DATAIO_CFG_UTILS_HH
3 
4 #include <list>
5 #include <sstream>
6 
7 #include <boost/core/demangle.hpp>
8 #include <armadillo>
9 #include <yaml-cpp/yaml.h>
10 
11 #include "../core/exceptions.hh"
12 #include "../core/string.hh"
13 #include "../core/types.hh"
14 
15 namespace Utopia
16 {
17 namespace DataIO
18 {
19 
62 // end group ConfigUtilities
67 // .. Config reading helper functions .........................................
68 
69 // TODO Make this an actual Utopia exception
71 template < class Exc >
72 YAML::Exception
74  const Config& node,
75  std::string prefix = {})
76 {
77  // The string stream for the new, improved error message
78  std::stringstream e_msg;
79  e_msg << prefix << " ";
80  e_msg << "Got " << boost::core::demangle(typeid(e).name()) << ". ";
81 
82  // Create a custom error message depending on whether the node is a
83  // zombie or a mark is available
84  if (not node)
85  {
86  // Was a zombie
87  e_msg << "The given node was a Zombie! Check that the key you are "
88  "trying to read from actually exists. ";
89  }
90  else if (not node.Mark().is_null())
91  {
92  // A mark is available to use as a hint
93  // NOTE Mark() provides the line and column in the config file, if
94  // available, i.e.: if not a zombie node
95  e_msg << "Check that the corresponding line of the config file "
96  "matches the desired read operation or type conversion. ";
97  }
98 
99  // Give some information on the node's content:
100  e_msg << "The content of the node is:" << std::endl << YAML::Dump(node);
101 
102  // Return the custom exception object; can be thrown on other side
103  return YAML::Exception(node.Mark(), e_msg.str());
104 }
105 
107 
109 std::string
110 to_string(const Config& node)
111 {
112  std::stringstream s;
113  s << YAML::Dump(node);
114  return s.str();
115 }
116 
117 } // namespace DataIO
118 
124 // NOTE All code below is not inside the Utopia::DataIO namespace to make
125 // includes into models more convenient.
126 
128 
156 template < typename ReturnType >
157 ReturnType
158 get_as(const std::string& key, const DataIO::Config& node)
159 {
160  try
161  {
162  return node[key].template as< ReturnType >();
163  }
164  catch (YAML::Exception& e)
165  {
166  if (not node[key])
167  {
168  throw KeyError(key, node);
169  }
170  // else: throw an improved error message
171  throw DataIO::improve_yaml_exception(e, node,
172  "Could not read key '" + key +
173  "' from given config node!");
174  }
175  catch (std::exception& e)
176  {
177  // Some other exception; provide at least some info and context
178  std::cerr << boost::core::demangle(typeid(e).name())
179  << " occurred during reading key '" << key
180  << "' from config!" << std::endl;
181 
182  // Re-throw the original exception
183  throw;
184  }
185  catch (...)
186  {
187  throw std::runtime_error("Unexpected exception occurred during "
188  "reading key '" + key + "'' from config!");
189  }
190 }
191 
193 template < typename ReturnType >
194 ReturnType
195 get_as(const std::string& key, const DataIO::Config& node, ReturnType fallback)
196 {
197  try
198  {
199  return get_as< ReturnType >(key, node);
200  }
201  catch (KeyError&)
202  {
203  return fallback;
204  }
205  // All other errors will (rightly) be thrown
206 }
207 
208 // Armadillo-related specialization
209 // TODO These should be implemented specializations of the get_as function!
210 namespace DataIO
211 {
212 
214 
221 template < typename CVecT, DimType dim = 0 >
222 CVecT
223 get_as_arma_vec(const std::string& key, const DataIO::Config& node)
224 {
225  // Extract the field vector element type; assuming Armadillo interface
226  using element_t = typename CVecT::elem_type;
227 
228  // Check if it can be constructed from a vector
229  if constexpr (std::is_constructible< CVecT, std::vector< element_t > >())
230  {
231  return get_as< std::vector< element_t > >(key, node);
232  }
233  else
234  {
235  static_assert(dim > 0,
236  "Need template argument dim given if target type is not "
237  "constructible from std::vector.");
238 
239  // Needs to be constructed element-wise
240  CVecT cvec;
241  const auto vec = get_as< std::array< element_t, dim > >(key, node);
242 
243  for (DimType i = 0; i < dim; i++)
244  {
245  cvec[i] = vec[i];
246  }
247 
248  return cvec;
249  }
250 }
251 } // namespace DataIO
252 
254 
256 template < DimType dim >
257 SpaceVecType< dim >
258 get_as_SpaceVec(const std::string& key, const DataIO::Config& node)
259 {
260  return DataIO::get_as_arma_vec< SpaceVecType< dim >, dim >(key, node);
261 }
262 
264 
266 template < DimType dim >
267 MultiIndexType< dim >
268 get_as_MultiIndex(const std::string& key, const DataIO::Config& node)
269 {
270  return DataIO::get_as_arma_vec< MultiIndexType< dim >, dim >(key, node);
271 }
272 
273 
274 
275 // end group ConfigUtilities
280 // -- INTERNALLY USED Functions to work with Config trees ---------------------
281 
282 namespace _internal {
283 
285 
292 template<class T, class Keys = std::list<std::string> >
294  Keys&& key_sequence,
295  const T& val)
296 {
297  // Get the next key
298  std::string key;
299  while (key.empty() and not key_sequence.empty()) {
300  key = key_sequence.front();
301  key_sequence.pop_front();
302  }
303  if (key.empty()) {
304  throw std::invalid_argument(
305  "During recursive_setitem, failed to retrieve a valid key for "
306  "continuing recursion. Make sure the given key sequence ("
307  + join(key_sequence, " -> ") + ") contains no empty elements!"
308  );
309  }
310 
311  // Check for end of recursion
312  if (key_sequence.empty()) {
313  if (d.IsScalar()) {
314  // Discard the scalar and replace it with an empty mapping
315  d = Config{};
316  }
317  d[key] = val;
318  return d;
319  }
320 
321  // Continue recursion, creating the intermediate node if it does not exist
322  if (not d[key]) {
323  d[key] = Config{};
324  }
325  d[key] = __recursive_setitem(d[key], std::forward<Keys>(key_sequence),
326  val);
327  return d;
328 }
329 
330 
332 
352  const std::vector<std::string>& key_sequence)
353 {
354  using Utopia::KeyError;
355  using Utopia::join;
356 
357  Config rv = YAML::Clone(d);
358  for (const auto& key : key_sequence ) {
359  try {
360  rv = get_as<Config>(key, rv);
361  }
362  catch (KeyError& err) {
363  throw KeyError(
364  key, rv,
365  "recursive_getitem failed for key or key sequence '"
366  + join(key_sequence, " -> ") + "'!"
367  );
368  }
369  }
370  return rv;
371 }
372 
374 
390  const std::string& key_sequence,
391  const std::string& delims = ".")
392 {
393  return recursive_getitem(d, split(key_sequence, delims));
394 }
395 
396 
398 
412 template<class T>
414  std::list<std::string> key_sequence,
415  const T val)
416 {
417  if (key_sequence.empty()) {
418  throw std::invalid_argument(
419  "Key sequence for recursive_setitem may not be empty!"
420  );
421  }
422  d = __recursive_setitem(YAML::Clone(d), std::move(key_sequence), val);
423 }
424 
426 
439 template<class T>
441  const std::string& key_sequence,
442  const T val,
443  const std::string& delims = ".")
444 {
446  d, split<std::list<std::string>>(key_sequence, delims), val
447  );
448 }
449 
450 } // namespace _internal
451 } // namespace Utopia
452 
453 #endif // DATAIO_CFG_UTILS_HH
For access to a dict-like structure with a bad key.
Definition: exceptions.hh:67
MultiIndexType< dim > get_as_MultiIndex(const std::string &key, const DataIO::Config &node)
Special case of Utopia::get_as to retrieve an entry as MultiIndex.
Definition: cfg_utils.hh:268
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
SpaceVecType< dim > get_as_SpaceVec(const std::string &key, const DataIO::Config &node)
Special case of Utopia::get_as to retrieve an entry as SpaceVec.
Definition: cfg_utils.hh:258
ReturnType get_as(const std::string &key, const DataIO::Config &node)
This function is a wrapper around the yaml-cpp YAML::Node::as function.
Definition: cfg_utils.hh:158
Config __recursive_setitem(Config d, Keys &&key_sequence, const T &val)
Helper function for recursive_setitem.
Definition: cfg_utils.hh:293
Config recursive_getitem(const Config &d, const std::vector< std::string > &key_sequence)
Recursively retrieve an element from the configuration tree.
Definition: cfg_utils.hh:351
YAML::Exception improve_yaml_exception(const Exc &e, const Config &node, std::string prefix={})
Improves yaml-cpp exceptions occurring for a given node.
Definition: cfg_utils.hh:73
void recursive_setitem(Config &d, std::list< std::string > key_sequence, const T val)
Recursively sets an element in a configuration tree.
Definition: cfg_utils.hh:413
std::string to_string(const Config &node)
Given a config node, returns a string representation of it.
Definition: cfg_utils.hh:110
CVecT get_as_arma_vec(const std::string &key, const DataIO::Config &node)
Retrieve a config entry as Armadillo column vector using get_.
Definition: cfg_utils.hh:223
Definition: agent.hh:11
DataIO::Config Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:80
SeqCont split(const std::string &s, const std::string &delims=" ")
Splits a string and returns a container of string segments.
Definition: string.hh:45
std::string join(const Cont &cont, const std::string &delim=", ")
Joins together the strings in a container.
Definition: string.hh:18
unsigned short DimType
Type for dimensions, i.e. very small unsigned integers.
Definition: types.hh:34