OpenPFC  0.1.4
Phase Field Crystal simulation framework
Loading...
Searching...
No Matches
parameter_validator.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2025 VTT Technical Research Centre of Finland Ltd
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
42#ifndef PFC_UI_PARAMETER_VALIDATOR_HPP
43#define PFC_UI_PARAMETER_VALIDATOR_HPP
44
46#include <algorithm>
47#include <cmath>
48#include <iomanip>
49#include <map>
50#include <nlohmann/json.hpp>
51#include <sstream>
52#include <string>
53#include <vector>
54
55namespace pfc {
56namespace ui {
57
58using json = nlohmann::json;
59
66 bool valid = true;
67 std::vector<std::string> errors;
68 std::map<std::string, std::string> validated_params;
69
73 bool is_valid() const { return valid && errors.empty(); }
74
78 std::string format_errors() const {
79 if (errors.empty()) {
80 return "No errors";
81 }
82
83 std::ostringstream msg;
84 msg << "\n" << std::string(80, '=') << "\n";
85 msg << "CONFIGURATION VALIDATION FAILED\n";
86 msg << std::string(80, '=') << "\n\n";
87 msg << "Found " << errors.size() << " error(s):\n\n";
88
89 for (size_t i = 0; i < errors.size(); ++i) {
90 msg << (i + 1) << ". " << errors[i] << "\n\n";
91 }
92
93 msg << "ABORTING: Fix configuration errors before running simulation.\n";
94 msg << std::string(80, '=') << "\n";
95
96 return msg.str();
97 }
98
102 std::string format_summary(const std::string &model_name = "Model") const {
103 std::ostringstream msg;
104 msg << "\n" << std::string(80, '=') << "\n";
105 msg << "Configuration Validation Summary - " << model_name << "\n";
106 msg << std::string(80, '=') << "\n";
107
108 if (validated_params.empty()) {
109 msg << "No parameters validated\n";
110 } else {
111 msg << "Validated " << validated_params.size() << " parameter(s):\n";
112 msg << std::string(80, '-') << "\n";
113
114 // Find max key length for formatting
115 size_t max_key_len = 0;
116 for (const auto &[key, _] : validated_params) {
117 max_key_len = std::max(max_key_len, key.length());
118 }
119
120 for (const auto &[key, value] : validated_params) {
121 msg << " " << std::left << std::setw(max_key_len + 2) << key << " = "
122 << value << "\n";
123 }
124 }
125
126 msg << std::string(80, '=') << "\n";
127
128 return msg.str();
129 }
130};
131
138private:
139 std::vector<ParameterMetadata<double>> double_params_;
140 std::vector<ParameterMetadata<int>> int_params_;
141 std::string model_name_ = "Model";
142
143public:
147 void set_model_name(const std::string &name) { model_name_ = name; }
148
153 double_params_.push_back(meta);
154 }
155
160 int_params_.push_back(meta);
161 }
162
172 ValidationResult validate(const json &config) const {
174
175 // Validate double parameters
176 for (const auto &meta : double_params_) {
177 validate_parameter(config, meta, result);
178 }
179
180 // Validate int parameters
181 for (const auto &meta : int_params_) {
182 validate_parameter(config, meta, result);
183 }
184
185 result.valid = result.errors.empty();
186 return result;
187 }
188
189private:
190 // Retrieve a JSON value by dot-separated path (e.g., "outer.inner.param").
191 // Falls back to direct key lookup if path has no dots.
192 static json get_by_path(const json &j, const std::string &path) {
193 if (path.find('.') == std::string::npos) {
194 return j.contains(path) ? j.at(path) : json(nullptr);
195 }
196 const json *current = &j;
197 size_t start = 0;
198 while (start < path.size()) {
199 size_t dot = path.find('.', start);
200 std::string key = (dot == std::string::npos) ? path.substr(start)
201 : path.substr(start, dot - start);
202 if (!current->is_object() || !current->contains(key)) {
203 return json(nullptr);
204 }
205 current = &current->at(key);
206 if (dot == std::string::npos) break;
207 start = dot + 1;
208 }
209 return *current;
210 }
211
212 static bool has_by_path(const json &j, const std::string &path) {
213 if (path.find('.') == std::string::npos) {
214 return j.contains(path);
215 }
216 const json *current = &j;
217 size_t start = 0;
218 while (start < path.size()) {
219 size_t dot = path.find('.', start);
220 std::string key = (dot == std::string::npos) ? path.substr(start)
221 : path.substr(start, dot - start);
222 if (!current->is_object() || !current->contains(key)) {
223 return false;
224 }
225 current = &current->at(key);
226 if (dot == std::string::npos) break;
227 start = dot + 1;
228 }
229 return true;
230 }
231
232 static bool is_finite_number(const json &v) {
233 if (!v.is_number()) return false;
234 double x = v.get<double>();
235 return std::isfinite(x);
236 }
237
241 void validate_parameter(const json &config, const ParameterMetadata<double> &meta,
242 ValidationResult &result) const {
243 // Resolve value by path (supports nested keys like "a.b.c")
244 bool exists = has_by_path(config, meta.name);
245 json val = exists ? get_by_path(config, meta.name) : json(nullptr);
246 // Check if parameter exists
247 if (!exists) {
248 if (meta.required && !meta.default_value) {
249 std::ostringstream err;
250 err << "Required parameter '" << meta.name << "' is missing\n"
251 << " " << meta.format_info();
252 result.errors.push_back(err.str());
253 return;
254 } else if (meta.default_value) {
255 // Use default value
256 std::ostringstream val_str;
257 val_str << *meta.default_value << " (default)";
258 result.validated_params[meta.name] = val_str.str();
259 return;
260 } else {
261 // Optional parameter, not provided
262 return;
263 }
264 }
265
266 // Check type
267 if (!val.is_number()) {
268 std::ostringstream err;
269 err << "Parameter '" << meta.name << "' has wrong type\n"
270 << " Expected: number\n"
271 << " Got: " << val.type_name() << "\n"
272 << " Value: " << val.dump() << "\n"
273 << " " << meta.format_info();
274 result.errors.push_back(err.str());
275 return;
276 }
277
278 // Get value and validate bounds (reject NaN/Inf)
279 double value = val.get<double>();
280 if (!std::isfinite(value)) {
281 std::ostringstream err;
282 err << "Parameter '" << meta.name << "' is not finite (NaN/Inf)\n"
283 << " " << meta.format_info();
284 result.errors.push_back(err.str());
285 return;
286 }
287 if (auto error = meta.validate(value)) {
288 std::ostringstream err;
289 err << *error << "\n"
290 << " " << meta.format_info();
291 result.errors.push_back(err.str());
292 } else {
293 // Valid parameter
294 std::ostringstream val_str;
295 val_str << value;
296 if (meta.min_value && meta.max_value) {
297 val_str << " [range: " << *meta.min_value << ", " << *meta.max_value << "]";
298 }
299 result.validated_params[meta.name] = val_str.str();
300 }
301 }
302
306 void validate_parameter(const json &config, const ParameterMetadata<int> &meta,
307 ValidationResult &result) const {
308 // Resolve value by path (supports nested keys like "a.b.c")
309 bool exists = has_by_path(config, meta.name);
310 json val = exists ? get_by_path(config, meta.name) : json(nullptr);
311 // Check if parameter exists
312 if (!exists) {
313 if (meta.required && !meta.default_value) {
314 std::ostringstream err;
315 err << "Required parameter '" << meta.name << "' is missing\n"
316 << " " << meta.format_info();
317 result.errors.push_back(err.str());
318 return;
319 } else if (meta.default_value) {
320 // Use default value
321 std::ostringstream val_str;
322 val_str << *meta.default_value << " (default)";
323 result.validated_params[meta.name] = val_str.str();
324 return;
325 } else {
326 // Optional parameter, not provided
327 return;
328 }
329 }
330
331 // Check type
332 if (!val.is_number_integer()) {
333 std::ostringstream err;
334 err << "Parameter '" << meta.name << "' has wrong type\n"
335 << " Expected: integer\n"
336 << " Got: " << val.type_name() << "\n"
337 << " Value: " << val.dump() << "\n"
338 << " " << meta.format_info();
339 result.errors.push_back(err.str());
340 return;
341 }
342
343 // Get value and validate bounds
344 int value = val.get<int>();
345 if (auto error = meta.validate(value)) {
346 std::ostringstream err;
347 err << *error << "\n"
348 << " " << meta.format_info();
349 result.errors.push_back(err.str());
350 } else {
351 // Valid parameter
352 std::ostringstream val_str;
353 val_str << value;
354 if (meta.min_value && meta.max_value) {
355 val_str << " [range: " << *meta.min_value << ", " << *meta.max_value << "]";
356 }
357 result.validated_params[meta.name] = val_str.str();
358 }
359 }
360};
361
362} // namespace ui
363} // namespace pfc
364
365#endif // PFC_UI_PARAMETER_VALIDATOR_HPP
Parameter validator for double-valued parameters.
Definition parameter_validator.hpp:137
void set_model_name(const std::string &name)
Set model name for reporting.
Definition parameter_validator.hpp:147
void add_metadata(const ParameterMetadata< double > &meta)
Add parameter metadata for validation.
Definition parameter_validator.hpp:152
void add_metadata(const ParameterMetadata< int > &meta)
Add integer parameter metadata for validation.
Definition parameter_validator.hpp:159
ValidationResult validate(const json &config) const
Validate parameters from JSON configuration.
Definition parameter_validator.hpp:172
Parameter metadata system for configuration validation.
Result of parameter validation.
Definition parameter_validator.hpp:65
bool is_valid() const
Check if validation passed.
Definition parameter_validator.hpp:73
std::string format_errors() const
Format error report.
Definition parameter_validator.hpp:78
std::string format_summary(const std::string &model_name="Model") const
Format parameter summary.
Definition parameter_validator.hpp:102
Represents the global simulation domain (the "world").
Definition world.hpp:91