QMCPACK
InputSection.cpp
Go to the documentation of this file.
1 //////////////////////////////////////////////////////////////////////////////////////
2 // This file is distributed under the University of Illinois/NCSA Open Source License.
3 // See LICENSE file in top directory for details.
4 //
5 // Copyright (c) 2023 QMCPACK developers.
6 //
7 // File developed by: Jaron T. Krogel, krogeljt@ornl.gov, Oak Ridge National Laboratory
8 // Peter W. Doak, doakpw@ornl.gov, Oak Ridge National Laboratory
9 //
10 //////////////////////////////////////////////////////////////////////////////////////
11 
12 #include "InputSection.h"
14 #include "ModernStringUtils.hpp"
15 #include "Utilities/string_utils.h"
16 
17 namespace qmcplusplus
18 {
19 
20 [[noreturn]] std::any InputSection::assignAnyEnum(const std::string& tag) const
21 {
22  throw UniformCommunicateError("derived class must provide assignAnyEnum method if enum parameters are used");
23 }
24 
25 [[noreturn]] void InputSection::setFromStreamCustom(const std::string& ename,
26  const std::string& name,
27  std::istringstream& svalue)
28 {
29  throw UniformCommunicateError("derived class must provide handleCustom method if custom parameters are used");
30 }
31 
32 
33 void InputSection::readAttributes(xmlNodePtr cur,
34  const std::string& element_name,
35  const std::vector<std::string>& do_not_consume)
36 {
37  xmlAttrPtr att = cur->properties;
38  while (att != NULL)
39  {
40  // unsafe att->name is an xmlChar, xmlChar is a UTF-8 byte
41  std::string name{lowerCase(castXMLCharToChar(att->name))};
42  // issue here is that we don't want to consume the name of the parameter as that has a special status in a parameter tag.
43  // This is due to the <parameter name="parameter_name> == <parameter_name> tag equivalence :(
44  std::string qualified_name{((element_name.size() > 0 && element_name != "parameter") ? (element_name + "::") : "") +
45  name};
46 
47  // Since parameters don't get a qualified name this will still prevent consumption of any of the do_not_consume attributes from them
48  if (std::any_of(do_not_consume.begin(), do_not_consume.end(), [&name](auto& dnc) { return name == dnc; }))
49  {
50  att = att->next;
51  continue;
52  }
53 
54  if (!isAttribute(qualified_name))
55  {
56  std::stringstream error;
57  error << "InputSection::readXML name " << name << " is not an attribute of " << section_name << "\n";
58  throw UniformCommunicateError(error.str());
59  }
60  std::istringstream stream(castXMLCharToChar(att->children->content));
61  if (isCustom(name))
62  setFromStreamCustom(element_name, qualified_name, stream);
63  else
64  setFromStream(qualified_name, stream);
65  att = att->next;
66  }
67 }
68 
69 void InputSection::handleDelegate(const std::string& ename, const xmlNodePtr element)
70 {
71  assert(delegate_factories_.find(ename) != delegate_factories_.end());
72  std::string value_key;
73  std::any value = delegate_factories_[ename](element, value_key);
74 
75  assignValue(value_key, value);
76 }
77 
78 void InputSection::readXML(xmlNodePtr cur)
79 {
80  assert(cur != nullptr);
81  // For historical reasons that actual "type" of the element/input section is expressed in a very inconsistent way.
82  // It could be coded via the element name i.e. the tag, or at minimum a method, type, or name attribute.
83  std::string section_ename{lowerCase(castXMLCharToChar(cur->name))};
84  std::string section_method(lowerCase(getXMLAttributeValue(cur, "method")));
85  std::string section_type(lowerCase(getXMLAttributeValue(cur, "type")));
86  std::string section_name_actual(lowerCase(getXMLAttributeValue(cur, "name")));
87  // at anyrate one of these must match the section_name.
88  std::string lcase_section_name{lowerCase(section_name)};
89 
90  auto checkSectionName = [&section_name = section_name,
91  &section_name_alternates = section_name_alternates](auto& possible_sname) {
92  std::string lcase_section_name{lowerCase(section_name)};
93  if (possible_sname == lcase_section_name)
94  return true;
95  if (section_name_alternates.size() > 0)
96  return std::any_of(section_name_alternates.begin(), section_name_alternates.end(),
97  [&possible_sname](auto& name_alternate) {
98  std::string lcase_alternate{lowerCase(name_alternate)};
99  return possible_sname == lcase_alternate;
100  });
101  return false;
102  };
103 
104  if (!(checkSectionName(section_ename) || checkSectionName(section_method) || checkSectionName(section_type) ||
105  checkSectionName(section_name_actual)))
106  {
107  std::stringstream error;
108  error << "Input is invalid \"" << lcase_section_name << "\" does not match a defined input node!";
109  throw UniformCommunicateError(error.str());
110  }
111 
112  // these attributes don't get an element name passed to them because by convention we save and define them unqualified.
113  readAttributes(cur, "", {"type"});
114  // read parameters
115  xmlNodePtr element = cur->xmlChildrenNode;
116  while (element != NULL)
117  {
118  // ename is the "name" of the XML element i.e. <ename [attributes]>
119  std::string ename{lowerCase(castXMLCharToChar(element->name))};
120  // value of the elements attribute = name
121  std::string name(lowerCase(getXMLAttributeValue(element, "name")));
122  // we need both ename and name to figure out how to handle an element because of the <parameter name="actual_parameter"> pattern.
123  // If the name attribute isn't there it should equal the element name.
124  if (name.size() < 1)
125  name = ename;
126 
127  if (isDelegate(ename))
128  {
129  handleDelegate(ename, element);
130  }
131  else if (isCustom(ename))
132  {
133  std::istringstream stream(XMLNodeString{element});
134  setFromStreamCustom(ename, name, stream);
135  }
136  else if (ename == "parameter" || isParameter(ename))
137  {
138  if (ename == "parameter")
139  ename = name;
140  else
141  // We do this because the semantics of parameters are such that name can not have a unique value because
142  // if it did have one that the equivalence of <parameter_name> and <parameter name="parameter_name"> would
143  // be broken. Input code being ported could depend on this an an invariant.
144  name = ename;
145  if (!isParameter(ename))
146  {
147  std::stringstream error;
148  error << "InputSection::readXML name " << name << " is not a parameter of " << section_name << "\n";
149  throw UniformCommunicateError(error.str());
150  }
151 
152  std::istringstream stream(XMLNodeString{element});
153  setFromStream(name, stream);
154  readAttributes(element, name, {"name"});
155  }
156  else if (ename != "text")
157  {
158  std::stringstream error;
159  error << "InputSection::readXML node name " << ename << " is not handled by InputSection subtype " << section_name
160  << "\n";
161  throw UniformCommunicateError(error.str());
162  }
163  // else can't be an error case because this is how the whitespace text nodes
164  element = element->next;
165  }
166 
167  // assign default values for optional variables
168  setDefaults();
169 
170  // check input validity
171  checkValid();
172  //report();
173 }
174 
175 
176 void InputSection::init(const std::unordered_map<std::string, std::any>& init_values)
177 {
178  // assign inputted values
179  for (auto& [name, value] : init_values)
180  setFromValue(name, value);
181 
182  // assign default values for optional variables
183  setDefaults();
184 
185  // check input validity
186  checkValid();
187  //report();
188 }
189 
190 void InputSection::registerDelegate(const std::string& tag,
191  std::function<std::any(xmlNodePtr cur, std::string& value_key)> factory)
192 {
193  delegate_factories_[tag] = factory;
194 }
195 
197 {
198  for (auto& [name, default_value] : default_values)
199  if (!has(name))
200  setFromValue(name, default_value);
201 }
202 
203 void InputSection::setFromStream(const std::string& name, std::istringstream& svalue)
204 {
205  if (isString(name) || isEnumString(name))
206  {
207  std::string value;
208  svalue >> value;
209  assignValue(name, value);
210  }
211  else if (isMultiString(name))
212  {
213  std::vector<std::string> string_values;
214  for (std::string value; svalue >> value;)
215  string_values.push_back(value);
216  assignValue(name, string_values);
217  }
218  else if (isMultiReal(name))
219  {
220  std::vector<Real> real_values;
221  for (Real value; svalue >> value;)
222  real_values.push_back(static_cast<Real>(value));
223  assignValue(name, real_values);
224  }
225  else if (isBool(name))
226  {
227  std::string sval;
228  svalue >> sval;
229  bool value = sval == "yes" || sval == "true" || sval == "1";
230  assignValue(name, value);
231  }
232  else if (isInteger(name))
233  {
234  int value;
235  svalue >> value;
236  assignValue(name, value);
237  }
238  else if (isReal(name))
239  {
240  Real value;
241  svalue >> value;
242  assignValue(name, Real(value));
243  }
244  else if (isPosition(name))
245  {
246  Position value;
247  svalue >> value;
248  assignValue(name, value);
249  }
250  else
251  {
252  std::stringstream error;
253  error << "InputSection::set_from_stream name " << name << " in " << section_name << " does not have a type\n";
254  throw UniformCommunicateError(error.str());
255  }
256 }
257 
258 template<typename T>
259 void InputSection::assignValue(const std::string& name, const T& value)
260 {
261  if (has(name) && !isMultiple(name))
262  throw UniformCommunicateError("Input is invalid " + section_name + " contains " + name +
263  " node with duplicate name!");
264 
265  if (!isMultiple(name))
266  values_[name] = value;
267  else
268  {
269  if (has(name))
270  std::any_cast<std::vector<T>&>(values_[name]).push_back(value);
271  else
272  values_[name] = std::vector<T>{value};
273  }
274 }
275 
276 void InputSection::setFromValue(const std::string& name, const std::any& value)
277 {
278  try
279  {
280  if (isString(name) || isEnumString(name))
281  assignValue(name, std::any_cast<std::string>(value));
282  else if (isMultiString(name))
283  assignValue(name, std::any_cast<std::vector<std::string>>(value));
284  else if (isMultiReal(name))
285  assignValue(name, std::any_cast<std::vector<std::string>>(value));
286  else if (isBool(name))
287  assignValue(name, std::any_cast<bool>(value));
288  else if (isInteger(name))
289  assignValue(name, std::any_cast<int>(value));
290  else if (isReal(name))
291  assignValue(name, std::any_cast<Real>(value));
292  else if (isPosition(name))
293  assignValue(name, std::any_cast<Position>(value));
294  else
295  {
296  std::stringstream error;
297  error << "InputSection::set_from_value name " << name << " in " << section_name << " does not have a type\n";
298  throw UniformCommunicateError(error.str());
299  }
300  }
301  catch (const std::bad_cast& exc)
302  {
303  std::throw_with_nested(UniformCommunicateError("std::any_cast failed in setFromValue for name:" + name));
304  }
305 }
306 
308 {
309  // check that all required inputs are present
310  for (auto& name : required)
311  if (!has(name))
312  {
313  std::stringstream error;
314  error << "InputSection::check_valid required variable " << name << " in " << section_name
315  << " has not been assigned\n";
316  throw UniformCommunicateError(error.str());
317  }
319 };
320 
321 void InputSection::report(std::ostream& out) const
322 {
323  out << "\n" << section_name;
324  for (auto& [name, value] : values_)
325  {
326  out << "\n " << name << " = ";
327  if (isString(name))
328  out << std::any_cast<std::string>(value);
329  else if (isBool(name))
330  out << std::any_cast<bool>(value);
331  else if (isInteger(name))
332  out << std::any_cast<int>(value);
333  else if (isReal(name))
334  out << std::any_cast<Real>(value);
335  }
336  out << "\n\n";
337 }
338 
339 void InputSection::report() const { report(app_log()); }
340 
341 std::any InputSection::lookupAnyEnum(const std::string& enum_name,
342  const std::string& enum_value,
343  const std::unordered_map<std::string, std::any>& enum_map)
344 {
345  std::string enum_value_str(lowerCase(enum_name + "-" + enum_value));
346  try
347  {
348  return enum_map.at(enum_value_str);
349  }
350  catch (std::out_of_range& oor_exc)
351  {
352  std::throw_with_nested(UniformCommunicateError("bad_enum_tag_value: " + enum_value_str));
353  }
354 }
355 
356 } // namespace qmcplusplus
void handleDelegate(const std::string &ename, const xmlNodePtr element)
factor out delegate handling code for sanity.
void setFromValue(const std::string &name, const std::any &svalue)
Coerce input collected via init into types matching the definition of the input types defined in the ...
helper functions for EinsplineSetBuilder
Definition: Configuration.h:43
bool isBool(const std::string &name) const
Definition: InputSection.h:255
std::unordered_map< std::string, std::function< std::any(xmlNodePtr cur, std::string &value_key)> > delegate_factories_
Definition: InputSection.h:80
std::vector< std::string > section_name_alternates
For historical reasons some sections must recognize several different names. Assign them to this vari...
Definition: InputSection.h:60
std::ostream & app_log()
Definition: OutputManager.h:65
bool isMultiple(const std::string &name) const
Definition: InputSection.h:250
bool isCustom(const std::string &name) const
Definition: InputSection.h:259
std::unordered_map< std::string, std::any > default_values
Definition: InputSection.h:79
QMCTraits::FullPrecRealType Real
Definition: InputSection.h:40
void error(char const *m)
Definition: Standard.h:204
void readAttributes(xmlNodePtr cur, const std::string &element_name, const std::vector< std::string > &do_not_consume)
reads attributes for both the root node and parameter/child nodes that aren&#39;t delegated.
bool isEnumString(const std::string &name) const
Definition: InputSection.h:251
bool isMultiReal(const std::string &name) const
Definition: InputSection.h:254
void init(const std::unordered_map< std::string, std::any > &init_values)
virtual void checkParticularValidity()
Do validation for a particular subtype of InputSection Called by check_valid.
Definition: InputSection.h:195
bool isDelegate(const std::string &name) const
Definition: InputSection.h:247
typename QMCTypes< Real, OHMMS_DIM >::PosType Position
Definition: InputSection.h:41
This a subclass for runtime errors that will occur on all ranks.
void readXML(xmlNodePtr cur)
Read variable values (initialize) from XML input, call checkValid.
std::string lowerCase(const std::string_view s)
++17
void checkValid()
Check validity of inputs.
char * castXMLCharToChar(xmlChar *c)
assign a value from a node. Use specialization for classes.
Definition: libxmldefs.h:62
bool isInteger(const std::string &name) const
Definition: InputSection.h:256
static std::any lookupAnyEnum(const std::string &enum_name, const std::string &enum_value, const std::unordered_map< std::string, std::any > &enum_map)
Assign any enum helper for InputSection derived class assumes enum lookup table of this form: inline ...
std::unordered_map< std::string, std::any > values_
Definition: InputSection.h:83
bool isAttribute(const std::string &name) const
Definition: InputSection.h:246
std::string getXMLAttributeValue(const xmlNodePtr cur, const std::string_view name)
get the value string for attribute name if name is unfound in cur you get an empty string back this i...
void setFromStream(const std::string &name, std::istringstream &svalue)
convert xmlNode contents into a std::string
void assignValue(const std::string &name, const T &value)
assign value into unordered map respecting values multiplicity It is a fatal exception to assign to a...
std::unordered_set< std::string > required
Definition: InputSection.h:65
bool isString(const std::string &name) const
Definition: InputSection.h:252
bool isPosition(const std::string &name) const
Definition: InputSection.h:258
bool isMultiString(const std::string &name) const
Definition: InputSection.h:253
void registerDelegate(const std::string &tag, DelegateHandler delegate_handler)
register factory function for delegate input
bool isReal(const std::string &name) const
Definition: InputSection.h:257
bool isParameter(const std::string &name) const
Definition: InputSection.h:248
bool has(const std::string &name) const
Definition: InputSection.h:87
std::string section_name
"Name" of the input section, you must define this in the subtype and the ename, name, type, or method must match.
Definition: InputSection.h:57
virtual void setFromStreamCustom(const std::string &ename, const std::string &name, std::istringstream &svalue)
Derived class can overrides this to do custom parsing of the element values for Custom elements These...
virtual std::any assignAnyEnum(const std::string &tag) const
Derived class overrides this to get proper assignment of scoped enum values.