PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
ConfigJSON.cc
Go to the documentation of this file.
1 /* Copyright (C) 2014, 2016, 2018, 2023 PISM Authors
2  *
3  * This file is part of PISM.
4  *
5  * PISM is free software; you can redistribute it and/or modify it under the
6  * terms of the GNU General Public License as published by the Free Software
7  * Foundation; either version 3 of the License, or (at your option) any later
8  * version.
9  *
10  * PISM is distributed in the hope that it will be useful, but WITHOUT ANY
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with PISM; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18  */
19 
20 #include <vector>
21 #include <cstdlib> // free
22 
23 #include "pism/util/ConfigJSON.hh"
24 #include "pism/util/error_handling.hh"
25 #include "pism/util/pism_utilities.hh"
26 #include "pism/util/io/File.hh"
27 
28 namespace pism {
29 
30 /*! Given a 'path' "alice.bob.charlie", look for the JSON object 'bob'
31  * containing a key 'charlie' in the object 'alice'. Use the 'object'
32  * argument as the root.
33  *
34  * In other words, 'path' describes a node of a tree with 'object' as
35  * the root, and this function returns the pointer to the node if
36  * found, and NULL otherwise.
37  */
38 static json_t* find_json_value(json_t *root, const std::string &name) {
39  if (root == NULL) {
40  return NULL;
41  }
42 
43  json_t *object = root;
44  for (auto object_name : split(name, '.')) {
45 
46  object = json_object_get(object, object_name.c_str());
47 
48  if (object == NULL) {
49  break;
50  }
51  }
52 
53  return object;
54 }
55 
56 /*!
57  * Convert an STL vector to a JSON array.
58  */
59 static json_t* pack_json_array(const std::vector<double> &data) {
60  json_t *array = json_array();
61  if (array == NULL) {
63  "failed to create an empty JSON array");
64  }
65 
66  for (const auto &v : data) {
67  json_t *value = json_pack("f", v);
68  if (value == NULL) {
70  "failed to pack a JSON number");
71  }
72  if (json_array_append_new(array, value) != 0) {
74  "failed to add an element to a JSON array");
75  }
76  }
77 
78  return array;
79 }
80 
81 /*!
82  * Convert a JSON array to an STL vector.
83  */
84 std::vector<double> unpack_json_array(const char *name,
85  const json_t *input) {
86  std::vector<double> result;
87 
88  if (json_typeof(input) != JSON_ARRAY) {
90  "%s is not an array", name);
91  }
92 
93  size_t N = json_array_size(input);
94 
95  for (size_t k = 0; k < N; ++k) {
96  json_t *value = json_array_get(input, k);
97  if (value == NULL) {
99  "failed to get an element of %s",
100  name);
101  }
102 
103  double v = 0.0;
104  if (json_unpack(value, "F", &v) == 0) {
105  result.push_back(v);
106  } else {
108  "failed to convert an element of %s to double",
109  name);
110  }
111  }
112 
113  return result;
114 }
115 
116 
117 template<typename PISMType, typename TMPType>
118 static void get_all_values(json_t *root, const std::string &path,
119  int type, const char *fmt, std::map<std::string,PISMType> &accum) {
120  const char *key;
121  json_t *value;
122 
123  json_object_foreach(root, key, value) {
124  std::string parameter = path + key;
125  int value_type = json_typeof(value);
126  if (value_type == type) {
127  TMPType tmp;
128  if (json_unpack(value, fmt, &tmp) == 0) {
129  accum[parameter] = tmp;
130  } else {
132  "failed to json_unpack %s using format %s",
133  parameter.c_str(), fmt);
134  }
135  } else if (value_type == JSON_OBJECT) {
136  get_all_values<PISMType, TMPType>(value, parameter + ".", type, fmt, accum);
137  }
138  }
139 }
140 
141 static void get_all_arrays(json_t *root, const std::string &path,
142  std::map<std::string, std::vector<double> > &accum) {
143  const char *key;
144  json_t *value;
145 
146  json_object_foreach(root, key, value) {
147  std::string parameter = path + key;
148 
149  switch (json_typeof(value)) {
150  case JSON_ARRAY:
151  accum[parameter] = unpack_json_array(parameter.c_str(), value);
152  break;
153  case JSON_OBJECT:
154  get_all_arrays(value, parameter + ".", accum);
155  break;
156  default:
157  break;
158  }
159  }
160 }
161 
162 template<typename PISMType, typename TMPType>
163 static PISMType get_value(json_t *object, const std::string &name,
164  const char *fmt, const char *type_name) {
165  json_t *value = find_json_value(object, name);
166  if (value == NULL) {
167  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "%s was not found", name.c_str());
168  }
169 
170  TMPType tmp;
171  if (json_unpack(value, fmt, &tmp) == 0) {
172  return tmp;
173  } else {
175  "failed to convert %s to a %s", name.c_str(), type_name);
176  }
177 }
178 
179 /*! Store a 'value' corresponding to the key 'name' in the database 'data'.
180  *
181  * If a name refers to an object "alice.bob", the object "alice" must
182  * exist in the tree already, but "bob" may not exist before this call
183  * and will be created. In other words, this method allows adding new
184  * leaves only.
185  */
186 static void set_value(json_t *data, const std::string &name, json_t *value) {
187  std::vector<std::string> path = split(name, '.');
188  if (path.size() == 0) {
189  // stop if 'name' is empty
190  return;
191  }
192 
193  std::string key = path.back();
194  path.pop_back();
195 
196  json_t *object = NULL;
197  if (path.empty()) {
198  object = data;
199  } else {
200  object = find_json_value(data, join(path, "."));
201  }
202 
203  if (object != NULL) {
204  if (json_is_object(object)) {
205  json_object_set_new(object, key.c_str(), value);
206  } else {
207  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "cannot set %s: %s is not an object",
208  name.c_str(), join(path, ".").c_str());
209  }
210  } else {
211  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "cannot set %s: %s is not found",
212  name.c_str(), join(path, ".").c_str());
213  }
214 }
215 
216 
218  : Config(unit_system) {
219  m_data = NULL;
220  init_from_string("{}");
221 }
222 
224  json_decref(m_data);
225 }
226 
227 /*! Initialize the database by reading from a file 'filename'.
228  */
229 void ConfigJSON::init_from_file(const std::string &filename) {
230 
231  // free existing data if present
232  if (m_data != NULL) {
233  json_decref(m_data);
234  }
235 
236  json_error_t error;
237  m_data = json_load_file(filename.c_str(), JSON_DECODE_INT_AS_REAL, &error);
238 
239  if (m_data == NULL) {
240  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Error loading config from '%s'"
241  " at line %d, column %d.",
242  filename.c_str(), error.line, error.column);
243  }
244 }
245 
246 /*! Initialize the database using a string 'string'.
247  */
248 void ConfigJSON::init_from_string(const std::string &string) {
249  // free existing data if present
250  if (m_data != NULL) {
251  json_decref(m_data);
252  }
253 
254  json_error_t error;
255  m_data = json_loads(string.c_str(), JSON_DECODE_INT_AS_REAL, &error);
256 
257  if (m_data == NULL) {
258  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "Error loading config from '%s'"
259  " at line %d, column %d.",
260  string.c_str(), error.line, error.column);
261  }
262 }
263 
264 /*! Return the JSON string representation of the configuration database.
265  */
266 std::string ConfigJSON::dump() const {
267  std::string result;
268 
269  char *tmp = json_dumps(m_data, JSON_INDENT(2) | JSON_ENSURE_ASCII | JSON_SORT_KEYS);
270  if (tmp != NULL) {
271  result = tmp;
272  free(tmp);
273  }
274 
275  return result;
276 }
277 
279  Config::Doubles result;
280 
281  std::map<std::string, double> scalars;
282  get_all_values<double, double>(m_data, "", JSON_REAL, "F", scalars);
283 
284  for (const auto &p : scalars) {
285  result[p.first] = {p.second};
286  }
287 
288  get_all_arrays(m_data, "", result);
289 
290  return result;
291 }
292 
294  Config::Strings result;
295  get_all_values<std::string, const char*>(m_data, "", JSON_STRING, "s", result);
296  return result;
297 }
298 
300  Config::Flags result;
301  get_all_values<bool, int>(m_data, "", JSON_TRUE, "b", result);
302  get_all_values<bool, int>(m_data, "", JSON_FALSE, "b", result);
303  return result;
304 }
305 
306 void ConfigJSON::set_number_impl(const std::string &name, double value) {
307  set_value(m_data, name, json_pack("f", value));
308 }
309 
310 void ConfigJSON::set_numbers_impl(const std::string &name,
311  const std::vector<double> &values) {
312  set_value(m_data, name, pack_json_array(values));
313 }
314 
315 void ConfigJSON::set_flag_impl(const std::string &name, bool value) {
316  set_value(m_data, name, json_pack("b", value));
317 }
318 
319 void ConfigJSON::set_string_impl(const std::string &name, const std::string &value) {
320  set_value(m_data, name, json_pack("s", value.c_str()));
321 }
322 
323 double ConfigJSON::get_number_impl(const std::string &name) const {
324  return get_value<double, double>(m_data, name, "F", "number");
325 }
326 
327 std::vector<double> ConfigJSON::get_numbers_impl(const std::string &name) const {
328  json_t *value = find_json_value(m_data, name);
329 
330  if (value == NULL) {
332  "%s was not found", name.c_str());
333  }
334 
335  return unpack_json_array(name.c_str(), value);
336 }
337 
338 std::string ConfigJSON::get_string_impl(const std::string &name) const {
339  return get_value<std::string, const char *>(m_data, name, "s", "string");
340 }
341 
342 bool ConfigJSON::get_flag_impl(const std::string &name) const {
343  return get_value<bool, int>(m_data, name, "b", "flag");
344 }
345 
346 void ConfigJSON::read_impl(const File &nc) {
347  std::string config_string = nc.read_text_attribute("PISM_GLOBAL", "pism_config");
348  this->init_from_string(config_string);
349 }
350 
351 void ConfigJSON::write_impl(const File &nc) const {
352  nc.write_attribute("PISM_GLOBAL", "pism_config", this->dump());
353 }
354 
355 bool ConfigJSON::is_set_impl(const std::string &name) const {
356  if (find_json_value(m_data, name) == NULL) {
357  return false;
358  } else {
359  return true;
360  }
361 }
362 
363 } // end of namespace pism
virtual Flags all_flags_impl() const
Definition: ConfigJSON.cc:299
virtual std::string get_string_impl(const std::string &name) const
Definition: ConfigJSON.cc:338
virtual std::vector< double > get_numbers_impl(const std::string &name) const
Definition: ConfigJSON.cc:327
virtual double get_number_impl(const std::string &name) const
Definition: ConfigJSON.cc:323
virtual Doubles all_doubles_impl() const
Definition: ConfigJSON.cc:278
virtual void set_number_impl(const std::string &name, double value)
Definition: ConfigJSON.cc:306
virtual void read_impl(const File &nc)
Definition: ConfigJSON.cc:346
void init_from_file(const std::string &filename)
Definition: ConfigJSON.cc:229
std::string dump() const
Definition: ConfigJSON.cc:266
json_t * m_data
Definition: ConfigJSON.hh:64
virtual void set_flag_impl(const std::string &name, bool value)
Definition: ConfigJSON.cc:315
void init_from_string(const std::string &string)
Definition: ConfigJSON.cc:248
virtual void set_string_impl(const std::string &name, const std::string &value)
Definition: ConfigJSON.cc:319
virtual ~ConfigJSON()
Definition: ConfigJSON.cc:223
virtual bool is_set_impl(const std::string &name) const
Definition: ConfigJSON.cc:355
virtual void set_numbers_impl(const std::string &name, const std::vector< double > &values)
Definition: ConfigJSON.cc:310
ConfigJSON(units::System::Ptr unit_system)
Definition: ConfigJSON.cc:217
virtual void write_impl(const File &nc) const
Definition: ConfigJSON.cc:351
virtual Strings all_strings_impl() const
Definition: ConfigJSON.cc:293
virtual bool get_flag_impl(const std::string &name) const
Definition: ConfigJSON.cc:342
std::map< std::string, std::string > Strings
std::map< std::string, std::vector< double > > Doubles
std::map< std::string, bool > Flags
std::string filename() const
Returns the name of the file used to initialize the database.
A class for storing and accessing PISM configuration flags and parameters.
void write_attribute(const std::string &var_name, const std::string &att_name, io::Type nctype, const std::vector< double > &values) const
Write a multiple-valued double attribute.
Definition: File.cc:638
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition: File.cc:693
High-level PISM I/O class.
Definition: File.hh:56
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
std::shared_ptr< System > Ptr
Definition: Units.hh:47
#define PISM_ERROR_LOCATION
static void get_all_values(json_t *root, const std::string &path, int type, const char *fmt, std::map< std::string, PISMType > &accum)
Definition: ConfigJSON.cc:118
static const double k
Definition: exactTestP.cc:42
static PISMType get_value(json_t *object, const std::string &name, const char *fmt, const char *type_name)
Definition: ConfigJSON.cc:163
static json_t * pack_json_array(const std::vector< double > &data)
Definition: ConfigJSON.cc:59
static void get_all_arrays(json_t *root, const std::string &path, std::map< std::string, std::vector< double > > &accum)
Definition: ConfigJSON.cc:141
std::string join(const std::vector< std::string > &strings, const std::string &separator)
Concatenate strings, inserting separator between elements.
std::vector< double > unpack_json_array(const char *name, const json_t *input)
Definition: ConfigJSON.cc:84
static json_t * find_json_value(json_t *root, const std::string &name)
Definition: ConfigJSON.cc:38
static void set_value(json_t *data, const std::string &name, json_t *value)
Definition: ConfigJSON.cc:186
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.