OpenPFC  0.1.4
Phase Field Crystal simulation framework
Loading...
Searching...
No Matches
from_json.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
22#ifndef PFC_UI_FROM_JSON_HPP
23#define PFC_UI_FROM_JSON_HPP
24
25#include "errors.hpp"
26#include "json_helpers.hpp"
30#include "openpfc/fft.hpp"
37#include "openpfc/model.hpp"
38#include "openpfc/time.hpp"
39#include <algorithm>
40#include <cctype>
41#include <heffte.h>
42#include <stdexcept>
43
44namespace pfc {
45namespace ui {
46
47template <class T> T from_json(const json &settings);
48
60template <> inline fft::Backend from_json<fft::Backend>(const json &j) {
61 if (!j.contains("backend") || !j["backend"].is_string()) {
62 // Default to FFTW if not specified
63 std::cout << "No FFT backend specified, defaulting to FFTW\n";
64 return fft::Backend::FFTW;
65 }
66
67 std::string backend_str = j["backend"];
68 std::transform(backend_str.begin(), backend_str.end(), backend_str.begin(),
69 [](unsigned char c) { return std::tolower(c); });
70
71 std::cout << "Selected FFT backend: " << backend_str << std::endl;
72
73 if (backend_str == "fftw") {
74 return fft::Backend::FFTW;
75 } else if (backend_str == "cuda") {
76#if defined(OpenPFC_ENABLE_CUDA)
77 return fft::Backend::CUDA;
78#else
79 throw std::runtime_error(
80 "CUDA backend requested but OpenPFC was not compiled with CUDA support. "
81 "Rebuild with -DOpenPFC_ENABLE_CUDA=ON");
82#endif
83 } else {
84 throw std::runtime_error(
85 "Unknown FFT backend: " + j["backend"].get<std::string>() +
86 ". Supported: fftw, cuda");
87 }
88}
89
101template <>
102inline heffte::plan_options from_json<heffte::plan_options>(const json &j) {
103 std::cout << "\nParsing HeFFTe plan options ...\n";
105 if (j.contains("use_reorder")) {
106 std::cout << "Using strided 1d fft operations" << std::endl;
107 options.use_reorder = j["use_reorder"];
108 }
109 if (j.contains("reshape_algorithm")) {
110 if (j["reshape_algorithm"] == "alltoall") {
111 std::cout << "Using alltoall reshape algorithm" << std::endl;
113 } else if (j["reshape_algorithm"] == "alltoallv") {
114 std::cout << "Using alltoallv reshape algorithm" << std::endl;
116 } else if (j["reshape_algorithm"] == "p2p") {
117 std::cout << "Using p2p reshape algorithm" << std::endl;
119 } else if (j["reshape_algorithm"] == "p2p_plined") {
120 std::cout << "Using p2p_plined reshape algorithm" << std::endl;
122 } else {
123 std::cerr << "Unknown reshape algorithm " << j["reshape_algorithm"]
124 << std::endl;
125 }
126 }
127 if (j.contains("use_pencils")) {
128 std::cout << "Using pencil decomposition" << std::endl;
129 options.use_pencils = j["use_pencils"];
130 }
131 if (j.contains("use_gpu_aware")) {
132 std::cout << "Using gpu aware fft" << std::endl;
133 options.use_gpu_aware = j["use_gpu_aware"];
134 }
135 std::cout << "Backend options: " << options << "\n\n";
136 return options;
137}
138
157template <> inline World from_json<World>(const json &j) {
158 int Lx = 0, Ly = 0, Lz = 0;
159 double dx = 0.0, dy = 0.0, dz = 0.0;
160 double x0 = 0.0, y0 = 0.0, z0 = 0.0;
161 std::string origin;
162
163 // Use helper to support both flat and nested structures
164 auto lx_val = get_json_value(j, "Lx", "domain");
165 if (lx_val.is_null() || !lx_val.is_number_integer()) {
166 throw std::invalid_argument(format_config_error(
167 "Lx", "number of grid points in X direction", "positive integer",
168 get_json_value_string(j, "Lx"), {}, "\"Lx\": 256"));
169 }
170 Lx = lx_val;
171
172 auto ly_val = get_json_value(j, "Ly", "domain");
173 if (ly_val.is_null() || !ly_val.is_number_integer()) {
174 std::string ly_str =
175 ly_val.is_null() ? "missing" : get_json_value_string(j, "Ly");
176 throw std::invalid_argument(
177 format_config_error("Ly", "number of grid points in Y direction",
178 "positive integer", ly_str, {}, "\"Ly\": 256"));
179 }
180 Ly = ly_val;
181
182 auto lz_val = get_json_value(j, "Lz", "domain");
183 if (lz_val.is_null() || !lz_val.is_number_integer()) {
184 std::string lz_str =
185 lz_val.is_null() ? "missing" : get_json_value_string(j, "Lz");
186 throw std::invalid_argument(
187 format_config_error("Lz", "number of grid points in Z direction",
188 "positive integer", lz_str, {}, "\"Lz\": 256"));
189 }
190 Lz = lz_val;
191
192 auto dx_val = get_json_value(j, "dx", "domain");
193 if (dx_val.is_null() || !dx_val.is_number_float()) {
194 std::string dx_str =
195 dx_val.is_null() ? "missing" : get_json_value_string(j, "dx");
196 throw std::invalid_argument(
197 format_config_error("dx", "grid spacing in X direction", "positive float",
198 dx_str, {}, "\"dx\": 1.0"));
199 }
200 dx = dx_val;
201
202 auto dy_val = get_json_value(j, "dy", "domain");
203 if (dy_val.is_null() || !dy_val.is_number_float()) {
204 std::string dy_str =
205 dy_val.is_null() ? "missing" : get_json_value_string(j, "dy");
206 throw std::invalid_argument(
207 format_config_error("dy", "grid spacing in Y direction", "positive float",
208 dy_str, {}, "\"dy\": 1.0"));
209 }
210 dy = dy_val;
211
212 auto dz_val = get_json_value(j, "dz", "domain");
213 if (dz_val.is_null() || !dz_val.is_number_float()) {
214 std::string dz_str =
215 dz_val.is_null() ? "missing" : get_json_value_string(j, "dz");
216 throw std::invalid_argument(
217 format_config_error("dz", "grid spacing in Z direction", "positive float",
218 dz_str, {}, "\"dz\": 1.0"));
219 }
220 dz = dz_val;
221
222 // Support both "origin" (new) and "origo" (legacy) for backward compatibility
223 auto origin_val = get_json_value(j, "origin", "domain");
224 if (origin_val.is_null()) {
225 origin_val = get_json_value(j, "origo", "domain");
226 }
227 if (origin_val.is_null() || !origin_val.is_string()) {
228 std::string origin_key = j.contains("origin") ? "origin" : "origo";
229 std::string origin_str =
230 origin_val.is_null() ? "missing" : get_json_value_string(j, origin_key);
231 throw std::invalid_argument(format_config_error(
232 origin_key, "coordinate system origin", "string ('center' or 'corner')",
233 origin_str, {"center", "corner"}, "\"origin\": \"center\""));
234 }
236
237 std::string origin_key = j.contains("origin") ? "origin" : "origo";
238 if (origin != "center" && origin != "corner") {
239 throw std::invalid_argument(format_config_error(
240 origin_key, "coordinate system origin", "string ('center' or 'corner')",
241 "\"" + origin + "\"", {"center", "corner"}, "\"origin\": \"center\""));
242 }
243
244 if (origin == "center") {
245 x0 = -0.5 * dx * Lx;
246 y0 = -0.5 * dy * Ly;
247 z0 = -0.5 * dz * Lz;
248 }
249
251 GridSpacing({dx, dy, dz}));
252
253 return world;
254}
255
256template <> inline Time from_json<Time>(const json &settings) {
257 // Support both flat and nested structures
258 auto t0_val = get_json_value(settings, "t0", "timestepping");
259 auto t1_val = get_json_value(settings, "t1", "timestepping");
260 auto dt_val = get_json_value(settings, "dt", "timestepping");
261 auto saveat_val = get_json_value(settings, "saveat", "timestepping");
262
263 if (t0_val.is_null() || t1_val.is_null() || dt_val.is_null() ||
264 saveat_val.is_null()) {
265 throw std::invalid_argument(
266 "Missing required time stepping parameters (t0, t1, dt, saveat)");
267 }
268
269 double t0 = t0_val;
270 double t1 = t1_val;
271 double dt = dt_val;
272 double saveat = saveat_val;
273 Time time({t0, t1, dt}, saveat);
274 return time;
275}
276
277inline void from_json(const json &j, Constant &ic) {
278 // Check that the JSON input has the correct type field
279 if (!j.contains("type") || j["type"] != "constant") {
280 throw std::invalid_argument(
281 "Invalid JSON input: missing or incorrect 'type' field.");
282 }
283 // Check that the JSON input has the required 'n0' field
284 if (!j.contains("n0") || !j["n0"].is_number()) {
285 throw std::invalid_argument(
286 "Invalid JSON input: missing or invalid 'n0' field.");
287 }
288 ic.set_density(j["n0"]);
289}
290
291inline void from_json(const json &j, SingleSeed &seed) {
292 if (!j.count("type") || j["type"] != "single_seed") {
293 throw std::invalid_argument(
294 "JSON object does not contain a 'single_seed' type.");
295 }
296
297 if (!j.count("amp_eq")) {
298 throw std::invalid_argument("JSON object does not contain an 'amp_eq' key.");
299 }
300
301 if (!j.count("rho_seed")) {
302 throw std::invalid_argument("JSON object does not contain a 'rho_seed' key.");
303 }
304
305 seed.set_amplitude(j["amp_eq"]);
306 seed.set_density(j["rho_seed"]);
307}
308
309inline void from_json(const json &j, RandomSeeds &ic) {
310 // Check that the JSON input has the correct type field
311 if (!j.contains("type") || j["type"] != "random_seeds") {
312 throw std::invalid_argument(
313 "Invalid JSON input: missing or incorrect 'type' field.");
314 }
315
316 // Check that the JSON input has the required 'amplitude' field
317 if (!j.contains("amplitude") || !j["amplitude"].is_number()) {
318 throw std::invalid_argument(
319 "Invalid JSON input: missing or invalid 'amplitude' field.");
320 }
321
322 // Check that the JSON input has the required 'rho' field
323 if (!j.contains("rho") || !j["rho"].is_number()) {
324 throw std::invalid_argument(
325 "Invalid JSON input: missing or invalid 'rho' field.");
326 }
327
328 ic.set_amplitude(j["amplitude"]);
329 ic.set_density(j["rho"]);
330}
331
332inline void from_json(const json &j, SeedGrid &ic) {
333 if (!j.contains("type") || j["type"] != "seed_grid") {
334 throw std::invalid_argument(
335 "Invalid JSON input: missing or incorrect 'type' field.");
336 }
337
338 if (!j.contains("Ny") || !j["Ny"].is_number()) {
339 throw std::invalid_argument(
340 "Invalid JSON input: missing or invalid 'Ny' field.");
341 }
342
343 if (!j.contains("Nz") || !j["Nz"].is_number()) {
344 throw std::invalid_argument(
345 "Invalid JSON input: missing or invalid 'Nz' field.");
346 }
347
348 if (!j.contains("X0") || !j["X0"].is_number()) {
349 throw std::invalid_argument(
350 "Invalid JSON input: missing or invalid 'X0' field.");
351 }
352
353 if (!j.contains("radius") || !j["radius"].is_number()) {
354 throw std::invalid_argument(
355 "Invalid JSON input: missing or invalid 'radius' field.");
356 }
357
358 if (!j.contains("amplitude") || !j["amplitude"].is_number()) {
359 throw std::invalid_argument(
360 "Invalid JSON input: missing or invalid 'amplitude' field.");
361 }
362
363 if (!j.contains("rho") || !j["rho"].is_number()) {
364 throw std::invalid_argument(
365 "Invalid JSON input: missing or invalid 'rho' field.");
366 }
367
368 ic.set_Ny(j["Ny"]);
369 ic.set_Nz(j["Nz"]);
370 ic.set_X0(j["X0"]);
371 ic.set_radius(j["radius"]);
372 ic.set_amplitude(j["amplitude"]);
373 ic.set_density(j["rho"]);
374}
375
376inline void from_json(const json &j, FileReader &ic) {
377 if (!j.contains("type") || j["type"] != "from_file") {
378 throw std::invalid_argument(
379 "Invalid JSON input: missing or incorrect 'type' field.");
380 }
381
382 if (!j.contains("filename") || !j["filename"].is_string()) {
383 throw std::invalid_argument(
384 "Invalid JSON input: missing or invalid 'filename' field.");
385 }
386
387 ic.set_filename(j["filename"]);
388}
389
390inline void from_json(const json &j, FixedBC &bc) {
391 if (!j.contains("type") || j["type"] != "fixed") {
392 throw std::invalid_argument(
393 "Invalid JSON input: missing or incorrect 'type' field.");
394 }
395
396 if (!j.contains("rho_low") || !j["rho_low"].is_number()) {
397 throw std::invalid_argument(
398 "Invalid JSON input: missing or invalid 'rho_low' field.");
399 }
400
401 if (!j.contains("rho_high") || !j["rho_high"].is_number()) {
402 throw std::invalid_argument(
403 "Invalid JSON input: missing or invalid 'rho_high' field.");
404 }
405
406 bc.set_rho_low(j["rho_low"]);
407 bc.set_rho_high(j["rho_high"]);
408}
409
410inline void from_json(const json &j, MovingBC &bc) {
411 if (!j.contains("type") || j["type"] != "moving") {
412 throw std::invalid_argument(
413 "Invalid JSON input: missing or incorrect 'type' field.");
414 }
415
416 if (!j.contains("rho_low") || !j["rho_low"].is_number()) {
417 throw std::invalid_argument(
418 "Invalid JSON input: missing or invalid 'rho_low' field.");
419 }
420
421 if (!j.contains("rho_high") || !j["rho_high"].is_number()) {
422 throw std::invalid_argument(
423 "Invalid JSON input: missing or invalid 'rho_high' field.");
424 }
425
426 if (!j.contains("width") || !j["width"].is_number()) {
427 throw std::invalid_argument(
428 "Invalid JSON input: missing or invalid 'width' field.");
429 }
430
431 if (!j.contains("alpha") || !j["alpha"].is_number()) {
432 throw std::invalid_argument(
433 "Invalid JSON input: missing or invalid 'alpha' field.");
434 }
435
436 if (!j.contains("disp") || !j["disp"].is_number()) {
437 throw std::invalid_argument(
438 "Invalid JSON input: missing or invalid 'disp' field.");
439 }
440
441 if (!j.contains("xpos") || !j["xpos"].is_number()) {
442 throw std::invalid_argument(
443 "Invalid JSON input: missing or invalid 'xpos' field.");
444 }
445
446 bc.set_rho_low(j["rho_low"]);
447 bc.set_rho_high(j["rho_high"]);
448 bc.set_xwidth(j["width"]);
449 bc.set_alpha(j["alpha"]);
450 bc.set_disp(j["disp"]);
451 bc.set_xpos(j["xpos"]);
452}
453
454inline void from_json(const json &, Model &) {
455 std::cout << "Warning: This model does not implement reading parameters from "
456 "json file. In order to read parameters from json file, one needs to "
457 "implement 'void from_json(const json &, Model &)'"
458 << std::endl;
459}
460
461} // namespace ui
462} // namespace pfc
463
464#endif // PFC_UI_FROM_JSON_HPP
Definition time.hpp:233
Constant value initial condition.
Helpful error message formatting for JSON configuration validation.
std::string format_config_error(const std::string &field_name, const std::string &description, const std::string &expected_type, const std::string &actual_value, const std::vector< std::string > &valid_options={}, const std::string &example="")
Format a helpful configuration error message.
Definition errors.hpp:118
std::string get_json_value_string(const nlohmann::json &j, const std::string &field_name)
Get JSON value as string for error messages.
Definition errors.hpp:190
Fast Fourier Transform interface for spectral methods.
Backend
FFT backend selection.
Definition fft.hpp:128
Base class for initial conditions and boundary conditions.
Read initial conditions from binary file.
Fixed boundary condition with smooth transition.
JSON utility functions for configuration parsing.
json get_json_value(const json &j, const std::string &key, const std::string &section="")
Helper function to get a JSON value from either flat or nested structure.
Definition json_helpers.hpp:38
Physics model abstraction for phase-field simulations.
Moving boundary condition that tracks solidification front.
auto create(const World< T > &world, const heffte::box3d< int > &box)
Construct a new World object from an existing one and a box.
Definition decomposition.hpp:62
Random distribution of crystalline seeds initial condition.
Regular grid of crystalline seeds initial condition.
Single spherical crystalline seed initial condition.
Grid dimensions (number of grid points per dimension)
Definition strong_types.hpp:176
Physical spacing between grid points.
Definition strong_types.hpp:370
Physical origin of coordinate system.
Definition strong_types.hpp:424
Represents the global simulation domain (the "world").
Definition world.hpp:91
World(const Int3 &lower, const Int3 &upper, const CoordinateSystem< T > &cs)
Constructs a World object.
Time state management for simulation time integration.
World class definition and unified interface.