Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ConfigJSON.cc
Go to the documentation of this file.
1/* Copyright (C) 2014, 2016, 2018, 2023, 2024, 2025 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
28namespace 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 */
38static 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 (const 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 */
59static 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 */
84std::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
117template<typename PISMType, typename TMPType>
118static 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
141static 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
162template<typename PISMType, typename TMPType>
163static 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 }
174
175 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "failed to convert %s to a %s", name.c_str(),
176 type_name);
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 */
186static void set_value(json_t *data, const std::string &name, json_t *value) {
187 std::vector<std::string> path = split(name, '.');
188 if (path.empty()) {
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 */
229void 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 */
248void 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 */
266std::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
306void ConfigJSON::set_number_impl(const std::string &name, double value) {
307 set_value(m_data, name, json_pack("f", value));
308}
309
310void 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
315void ConfigJSON::set_flag_impl(const std::string &name, bool value) {
316 set_value(m_data, name, json_pack("b", value));
317}
318
319void 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
323double ConfigJSON::get_number_impl(const std::string &name) const {
324 return get_value<double, double>(m_data, name, "F", "number");
325}
326
327std::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
338std::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
342bool ConfigJSON::get_flag_impl(const std::string &name) const {
343 return get_value<bool, int>(m_data, name, "b", "flag");
344}
345
347 std::string config_string = nc.read_text_attribute("PISM_GLOBAL", "pism_config");
348 this->init_from_string(config_string);
349}
350
351void ConfigJSON::write_impl(const File &nc) const {
352 nc.write_attribute("PISM_GLOBAL", "pism_config", this->dump());
353}
354
355bool ConfigJSON::is_set_impl(const std::string &name) const {
356 return find_json_value(m_data, name) != NULL;
357}
358
359} // end of namespace pism
virtual Flags all_flags_impl() const
virtual std::string get_string_impl(const std::string &name) const
virtual std::vector< double > get_numbers_impl(const std::string &name) const
virtual double get_number_impl(const std::string &name) const
virtual Doubles all_doubles_impl() const
virtual void set_number_impl(const std::string &name, double value)
virtual void read_impl(const File &nc)
void init_from_file(const std::string &filename)
std::string dump() const
virtual void set_flag_impl(const std::string &name, bool value)
void init_from_string(const std::string &string)
virtual void set_string_impl(const std::string &name, const std::string &value)
virtual ~ConfigJSON()
virtual bool is_set_impl(const std::string &name) const
virtual void set_numbers_impl(const std::string &name, const std::vector< double > &values)
ConfigJSON(units::System::Ptr unit_system)
virtual void write_impl(const File &nc) const
virtual Strings all_strings_impl() const
virtual bool get_flag_impl(const std::string &name) const
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:590
std::string read_text_attribute(const std::string &var_name, const std::string &att_name) const
Get a text attribute.
Definition File.cc:645
High-level PISM I/O class.
Definition File.hh:55
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)
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)
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)
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 void set_value(json_t *data, const std::string &name, json_t *value)
static json_t * find_json_value(json_t *root, const std::string &name)
Definition ConfigJSON.cc:38
std::vector< std::string > split(const std::string &input, char separator)
Transform a separator-separated list (a string) into a vector of strings.