Line data Source code
1 : // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : #include "configfile.hpp"
5 :
6 : #include <algorithm>
7 : #include <iterator>
8 : #include <string_view>
9 : #include <fmt/format.h>
10 : #include <fmt/ranges.h>
11 : #include <mfem.hpp>
12 : #include <nlohmann/json.hpp>
13 :
14 : // This is similar to NLOHMANN_JSON_SERIALIZE_ENUM, but results in an error if an enum
15 : // value corresponding to the string cannot be found. Also adds an overload for stream
16 : // printing enum values.
17 : #define PALACE_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \
18 : template <typename BasicJsonType> \
19 : inline void to_json(BasicJsonType &j, const ENUM_TYPE &e) \
20 : { \
21 : static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
22 : static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
23 : auto it = std::find_if(std::begin(m), std::end(m), \
24 : [e](const std::pair<ENUM_TYPE, BasicJsonType> &ej_pair) \
25 : { return ej_pair.first == e; }); \
26 : MFEM_VERIFY(it != std::end(m), \
27 : "Invalid value for " << #ENUM_TYPE " given when parsing to JSON!"); \
28 : j = it->second; \
29 : } \
30 : template <typename BasicJsonType> \
31 : inline void from_json(const BasicJsonType &j, ENUM_TYPE &e) \
32 : { \
33 : static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE " must be an enum!"); \
34 : static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__; \
35 : auto it = std::find_if(std::begin(m), std::end(m), \
36 : [j](const std::pair<ENUM_TYPE, BasicJsonType> &ej_pair) \
37 : { return ej_pair.second == j; }); \
38 : MFEM_VERIFY(it != std::end(m), \
39 : "Invalid value (" << j << ") for " \
40 : << #ENUM_TYPE \
41 : " given in the configuration file when parsing from JSON!"); \
42 : e = it->first; \
43 : } \
44 : std::ostream &operator<<(std::ostream &os, const ENUM_TYPE &e) \
45 : { \
46 : static const std::pair<ENUM_TYPE, const char *> m[] = __VA_ARGS__; \
47 : os << std::find_if(std::begin(m), std::end(m), \
48 : [e](const std::pair<ENUM_TYPE, const char *> &ej_pair) \
49 : { return ej_pair.first == e; }) \
50 : ->second; \
51 : return os; \
52 : }
53 :
54 : using json = nlohmann::json;
55 : namespace palace
56 : {
57 : // Helpers for converting enums specified in labels.hpp. Must be done in palace scope rather
58 : // than palace::config scope to ensure argument-dependent-lookup succeeds in json.
59 :
60 : // Helper for converting string keys to enum for CoordinateSystem.
61 0 : PALACE_JSON_SERIALIZE_ENUM(CoordinateSystem,
62 : {{CoordinateSystem::CARTESIAN, "Cartesian"},
63 : {CoordinateSystem::CYLINDRICAL, "Cylindrical"}})
64 :
65 : // Helper for converting string keys to enum for ProblemType.
66 66 : PALACE_JSON_SERIALIZE_ENUM(ProblemType, {{ProblemType::DRIVEN, "Driven"},
67 : {ProblemType::EIGENMODE, "Eigenmode"},
68 : {ProblemType::ELECTROSTATIC, "Electrostatic"},
69 : {ProblemType::MAGNETOSTATIC, "Magnetostatic"},
70 : {ProblemType::TRANSIENT, "Transient"}})
71 :
72 : // Helper for converting string keys to enum for EigenSolverBackend.
73 0 : PALACE_JSON_SERIALIZE_ENUM(EigenSolverBackend, {{EigenSolverBackend::DEFAULT, "Default"},
74 : {EigenSolverBackend::SLEPC, "SLEPc"},
75 : {EigenSolverBackend::ARPACK, "ARPACK"}})
76 :
77 : // Helper for converting string keys to enum for EigenSolverBackend.
78 0 : PALACE_JSON_SERIALIZE_ENUM(NonlinearEigenSolver, {{NonlinearEigenSolver::HYBRID, "Hybrid"},
79 : {NonlinearEigenSolver::SLP, "SLP"}})
80 :
81 : // Helper for converting string keys to enum for SurfaceFlux.
82 146 : PALACE_JSON_SERIALIZE_ENUM(SurfaceFlux, {{SurfaceFlux::ELECTRIC, "Electric"},
83 : {SurfaceFlux::MAGNETIC, "Magnetic"},
84 : {SurfaceFlux::POWER, "Power"}})
85 :
86 : // Helper for converting string keys to enum for InterfaceDielectric.
87 242 : PALACE_JSON_SERIALIZE_ENUM(InterfaceDielectric, {{InterfaceDielectric::DEFAULT, "Default"},
88 : {InterfaceDielectric::MA, "MA"},
89 : {InterfaceDielectric::MS, "MS"},
90 : {InterfaceDielectric::SA, "SA"}})
91 :
92 : // Helper for converting string keys to enum for FrequencySampling.
93 125 : PALACE_JSON_SERIALIZE_ENUM(FrequencySampling, {{FrequencySampling::DEFAULT, "Default"},
94 : {FrequencySampling::LINEAR, "Linear"},
95 : {FrequencySampling::LOG, "Log"},
96 : {FrequencySampling::POINT, "Point"}})
97 :
98 : // Helper for converting string keys to enum for TimeSteppingScheme and Excitation.
99 0 : PALACE_JSON_SERIALIZE_ENUM(TimeSteppingScheme,
100 : {{TimeSteppingScheme::DEFAULT, "Default"},
101 : {TimeSteppingScheme::GEN_ALPHA, "GeneralizedAlpha"},
102 : {TimeSteppingScheme::RUNGE_KUTTA, "RungeKutta"},
103 : {TimeSteppingScheme::CVODE, "CVODE"},
104 : {TimeSteppingScheme::ARKODE, "ARKODE"}})
105 0 : PALACE_JSON_SERIALIZE_ENUM(Excitation,
106 : {{Excitation::SINUSOIDAL, "Sinusoidal"},
107 : {Excitation::GAUSSIAN, "Gaussian"},
108 : {Excitation::DIFF_GAUSSIAN, "DifferentiatedGaussian"},
109 : {Excitation::MOD_GAUSSIAN, "ModulatedGaussian"},
110 : {Excitation::RAMP_STEP, "Ramp"},
111 : {Excitation::SMOOTH_STEP, "SmoothStep"}})
112 :
113 : // Helper for converting string keys to enum for LinearSolver, KrylovSolver, and
114 : // MultigridCoarsening
115 66 : PALACE_JSON_SERIALIZE_ENUM(LinearSolver, {{LinearSolver::DEFAULT, "Default"},
116 : {LinearSolver::AMS, "AMS"},
117 : {LinearSolver::BOOMER_AMG, "BoomerAMG"},
118 : {LinearSolver::MUMPS, "MUMPS"},
119 : {LinearSolver::SUPERLU, "SuperLU"},
120 : {LinearSolver::STRUMPACK, "STRUMPACK"},
121 : {LinearSolver::STRUMPACK_MP, "STRUMPACK-MP"},
122 : {LinearSolver::JACOBI, "Jacobi"}})
123 90 : PALACE_JSON_SERIALIZE_ENUM(KrylovSolver, {{KrylovSolver::DEFAULT, "Default"},
124 : {KrylovSolver::CG, "CG"},
125 : {KrylovSolver::MINRES, "MINRES"},
126 : {KrylovSolver::GMRES, "GMRES"},
127 : {KrylovSolver::FGMRES, "FGMRES"},
128 : {KrylovSolver::BICGSTAB, "BiCGSTAB"}})
129 0 : PALACE_JSON_SERIALIZE_ENUM(MultigridCoarsening,
130 : {{MultigridCoarsening::LINEAR, "Linear"},
131 : {MultigridCoarsening::LOGARITHMIC, "Logarithmic"}})
132 :
133 : // Helpers for converting string keys to enum for PreconditionerSide, SymbolicFactorization,
134 : // SparseCompression, and Orthogonalization.
135 0 : PALACE_JSON_SERIALIZE_ENUM(PreconditionerSide, {{PreconditionerSide::DEFAULT, "Default"},
136 : {PreconditionerSide::RIGHT, "Right"},
137 : {PreconditionerSide::LEFT, "Left"}})
138 0 : PALACE_JSON_SERIALIZE_ENUM(SymbolicFactorization,
139 : {{SymbolicFactorization::DEFAULT, "Default"},
140 : {SymbolicFactorization::METIS, "METIS"},
141 : {SymbolicFactorization::PARMETIS, "ParMETIS"},
142 : {SymbolicFactorization::SCOTCH, "Scotch"},
143 : {SymbolicFactorization::PTSCOTCH, "PTScotch"},
144 : {SymbolicFactorization::PORD, "PORD"},
145 : {SymbolicFactorization::AMD, "AMD"},
146 : {SymbolicFactorization::RCM, "RCM"}})
147 0 : PALACE_JSON_SERIALIZE_ENUM(SparseCompression,
148 : {{SparseCompression::NONE, "None"},
149 : {SparseCompression::BLR, "BLR"},
150 : {SparseCompression::HSS, "HSS"},
151 : {SparseCompression::HODLR, "HODLR"},
152 : {SparseCompression::ZFP, "ZFP"},
153 : {SparseCompression::BLR_HODLR, "BLR-HODLR"},
154 : {SparseCompression::ZFP_BLR_HODLR, "ZFP-BLR-HODLR"}})
155 0 : PALACE_JSON_SERIALIZE_ENUM(Orthogonalization, {{Orthogonalization::MGS, "MGS"},
156 : {Orthogonalization::CGS, "CGS"},
157 : {Orthogonalization::CGS2, "CGS2"}})
158 :
159 : // Helpers for converting string keys to enum for Device.
160 66 : PALACE_JSON_SERIALIZE_ENUM(Device, {{Device::CPU, "CPU"},
161 : {Device::GPU, "GPU"},
162 : {Device::DEBUG, "Debug"}})
163 : } // namespace palace
164 :
165 : namespace palace::config
166 : {
167 :
168 : namespace
169 : {
170 :
171 149 : int AtIndex(json::iterator &port_it, std::string_view errmsg_parent)
172 : {
173 149 : MFEM_VERIFY(
174 : port_it->find("Index") != port_it->end(),
175 : fmt::format("Missing {} \"Index\" in the configuration file!", errmsg_parent));
176 149 : auto index = port_it->at("Index").get<int>();
177 155 : MFEM_VERIFY(index > 0, fmt::format("The {} \"Index\" should be an integer > 0; got {}",
178 : errmsg_parent, index));
179 147 : return index;
180 : }
181 :
182 : template <std::size_t N>
183 32 : void ParseSymmetricMatrixData(json &mat, const std::string &name,
184 : SymmetricMatrixData<N> &data)
185 : {
186 32 : auto it = mat.find(name);
187 32 : if (it != mat.end() && it->is_array())
188 : {
189 : // Attempt to parse as an array.
190 0 : data.s = it->get<std::array<double, N>>();
191 : }
192 : else
193 : {
194 : // Fall back to scalar parsing with default.
195 32 : double s = mat.value(name, data.s[0]);
196 : data.s.fill(s);
197 : }
198 32 : data.v = mat.value("MaterialAxes", data.v);
199 32 : }
200 :
201 : // Helper function for extracting element data from the configuration file, either from a
202 : // provided keyword argument of from a specified vector. In extracting the direction various
203 : // checks are performed for validity of the input combinations.
204 63 : void ParseElementData(json &elem, const std::string &name, bool required,
205 : internal::ElementData &data)
206 : {
207 189 : data.attributes = elem.at("Attributes").get<std::vector<int>>(); // Required
208 63 : std::sort(data.attributes.begin(), data.attributes.end());
209 63 : auto it = elem.find(name);
210 63 : if (it != elem.end() && it->is_array())
211 : {
212 : // Attempt to parse as an array.
213 0 : data.direction = it->get<std::array<double, 3>>();
214 0 : data.coordinate_system = elem.value("CoordinateSystem", data.coordinate_system);
215 : }
216 : else
217 : {
218 : // Fall back to parsing as a string (value is optional).
219 63 : MFEM_VERIFY(elem.find("CoordinateSystem") == elem.end(),
220 : "Cannot specify \"CoordinateSystem\" when specifying a direction or side "
221 : "using a string in the configuration file!");
222 : std::string direction;
223 126 : direction = elem.value(name, direction);
224 189 : for (auto &c : direction)
225 : {
226 126 : c = std::tolower(c);
227 : }
228 : const auto xpos = direction.find("x");
229 : const auto ypos = direction.find("y");
230 : const auto zpos = direction.find("z");
231 : const auto rpos = direction.find("r");
232 : const bool xfound = xpos != std::string::npos;
233 : const bool yfound = ypos != std::string::npos;
234 : const bool zfound = zpos != std::string::npos;
235 : const bool rfound = rpos != std::string::npos;
236 63 : if (xfound)
237 : {
238 19 : MFEM_VERIFY(direction.length() == 1 || direction[xpos - 1] == '-' ||
239 : direction[xpos - 1] == '+',
240 : "Missing required sign specification on \"X\" for \""
241 : << name << "\" in the configuration file!");
242 19 : MFEM_VERIFY(!yfound && !zfound && !rfound,
243 : "\"X\" cannot be combined with \"Y\", \"Z\", or \"R\" for \""
244 : << name << "\" in the configuration file!");
245 19 : data.direction[0] =
246 19 : (direction.length() == 1 || direction[xpos - 1] == '+') ? 1.0 : -1.0;
247 19 : data.coordinate_system = CoordinateSystem::CARTESIAN;
248 : }
249 63 : if (yfound)
250 : {
251 44 : MFEM_VERIFY(direction.length() == 1 || direction[ypos - 1] == '-' ||
252 : direction[ypos - 1] == '+',
253 : "Missing required sign specification on \"Y\" for \""
254 : << name << "\" in the configuration file!");
255 44 : MFEM_VERIFY(!xfound && !zfound && !rfound,
256 : "\"Y\" cannot be combined with \"X\", \"Z\", or \"R\" for \""
257 : << name << "\" in the configuration file!");
258 44 : data.direction[1] =
259 44 : direction.length() == 1 || direction[ypos - 1] == '+' ? 1.0 : -1.0;
260 44 : data.coordinate_system = CoordinateSystem::CARTESIAN;
261 : }
262 63 : if (zfound)
263 : {
264 0 : MFEM_VERIFY(direction.length() == 1 || direction[zpos - 1] == '-' ||
265 : direction[zpos - 1] == '+',
266 : "Missing required sign specification on \"Z\" for \""
267 : << name << "\" in the configuration file!");
268 0 : MFEM_VERIFY(!xfound && !yfound && !rfound,
269 : "\"Z\" cannot be combined with \"X\", \"Y\", or \"R\" for \""
270 : << name << "\" in the configuration file!");
271 0 : data.direction[2] =
272 0 : direction.length() == 1 || direction[zpos - 1] == '+' ? 1.0 : -1.0;
273 0 : data.coordinate_system = CoordinateSystem::CARTESIAN;
274 : }
275 63 : if (rfound)
276 : {
277 0 : MFEM_VERIFY(direction.length() == 1 || direction[rpos - 1] == '-' ||
278 : direction[rpos - 1] == '+',
279 : "Missing required sign specification on \"R\" for \""
280 : << name << "\" in the configuration file!");
281 0 : MFEM_VERIFY(!xfound && !yfound && !zfound,
282 : "\"R\" cannot be combined with \"X\", \"Y\", or \"Z\" for \""
283 : << name << "\" in the configuration file!");
284 0 : data.direction[0] =
285 0 : direction.length() == 1 || direction[rpos - 1] == '+' ? 1.0 : -1.0;
286 0 : data.direction[1] = 0.0;
287 0 : data.direction[2] = 0.0;
288 0 : data.coordinate_system = CoordinateSystem::CYLINDRICAL;
289 : }
290 : }
291 63 : MFEM_VERIFY(data.coordinate_system != CoordinateSystem::CYLINDRICAL ||
292 : (data.direction[1] == 0.0 && data.direction[2] == 0.0),
293 : "Parsing azimuthal and longitudinal directions for cylindrical coordinate "
294 : "system directions from the configuration file is not currently supported!");
295 63 : MFEM_VERIFY(
296 : !required || data.direction[0] != 0.0 || data.direction[1] != 0.0 ||
297 : data.direction[2] != 0.0,
298 : "Missing \"" << name
299 : << "\" for an object which requires it in the configuration file!");
300 63 : }
301 :
302 : template <typename T>
303 : std::ostream &operator<<(std::ostream &os, const std::vector<T> &data)
304 : {
305 : os << fmt::format("{}", fmt::join(data, " "));
306 : return os;
307 : }
308 :
309 : template <typename T, std::size_t N>
310 : std::ostream &operator<<(std::ostream &os, const std::array<T, N> &data)
311 : {
312 : os << fmt::format("{}", fmt::join(data, " "));
313 : return os;
314 : }
315 :
316 : template <std::size_t N>
317 : std::ostream &operator<<(std::ostream &os, const SymmetricMatrixData<N> &data)
318 : {
319 : os << "s: " << data.s;
320 : int j = 0;
321 : for (const auto &x : data.v)
322 : {
323 : os << ", v" << j++ << ": " << x;
324 : }
325 : return os;
326 : }
327 :
328 : constexpr bool JSON_DEBUG = false;
329 :
330 : } // namespace
331 :
332 8 : void ProblemData::SetUp(json &config)
333 : {
334 8 : auto problem = config.find("Problem");
335 8 : MFEM_VERIFY(problem != config.end(),
336 : "\"Problem\" must be specified in the configuration file!");
337 8 : MFEM_VERIFY(problem->find("Type") != problem->end(),
338 : "Missing config[\"Problem\"][\"Type\"] in the configuration file!");
339 8 : type = problem->at("Type"); // Required
340 8 : verbose = problem->value("Verbose", verbose);
341 8 : output = problem->value("Output", output);
342 :
343 : // Parse output formats.
344 8 : auto output_formats_it = problem->find("OutputFormats");
345 8 : if (output_formats_it != problem->end())
346 : {
347 0 : output_formats.paraview = output_formats_it->value("Paraview", output_formats.paraview);
348 0 : output_formats.gridfunction =
349 0 : output_formats_it->value("GridFunction", output_formats.gridfunction);
350 : }
351 :
352 : // Check for provided solver configuration data (not required for electrostatics or
353 : // magnetostatics since defaults can be used for every option).
354 8 : auto solver = config.find("Solver");
355 8 : if (type == ProblemType::DRIVEN)
356 : {
357 8 : MFEM_VERIFY(solver->find("Driven") != solver->end(),
358 : "config[\"Problem\"][\"Type\"] == \"Driven\" should be accompanied by a "
359 : "config[\"Solver\"][\"Driven\"] configuration!");
360 : }
361 : else if (type == ProblemType::EIGENMODE)
362 : {
363 0 : MFEM_VERIFY(solver->find("Eigenmode") != solver->end(),
364 : "config[\"Problem\"][\"Type\"] == \"Eigenmode\" should be accompanied by a "
365 : "config[\"Solver\"][\"Eigenmode\"] configuration!");
366 : }
367 : else if (type == ProblemType::ELECTROSTATIC)
368 : {
369 : // MFEM_VERIFY(
370 : // solver->find("Electrostatic") != solver->end(),
371 : // "config[\"Problem\"][\"Type\"] == \"Electrostatic\" should be accompanied by a "
372 : // "config[\"Solver\"][\"Electrostatic\"] configuration!");
373 : }
374 : else if (type == ProblemType::MAGNETOSTATIC)
375 : {
376 : // MFEM_VERIFY(
377 : // solver->find("Magnetostatic") != solver->end(),
378 : // "config[\"Problem\"][\"Type\"] == \"Magnetostatic\" should be accompanied by a "
379 : // "config[\"Solver\"][\"Magnetostatic\"] configuration!");
380 : }
381 : else if (type == ProblemType::TRANSIENT)
382 : {
383 0 : MFEM_VERIFY(solver->find("Transient") != solver->end(),
384 : "config[\"Problem\"][\"Type\"] == \"Transient\" should be accompanied by a "
385 : "config[\"Solver\"][\"Transient\"] configuration!");
386 : }
387 :
388 : // Cleanup
389 8 : problem->erase("Type");
390 8 : problem->erase("Verbose");
391 8 : problem->erase("Output");
392 8 : problem->erase("OutputFormats");
393 16 : MFEM_VERIFY(problem->empty(),
394 : "Found an unsupported configuration file keyword under \"Problem\"!\n"
395 : << problem->dump(2));
396 :
397 : // Debug
398 : if constexpr (JSON_DEBUG)
399 : {
400 : std::cout << "Type: " << type << '\n';
401 : std::cout << "Verbose: " << verbose << '\n';
402 : std::cout << "Output: " << output << '\n';
403 : std::cout << "OutputFormats.Paraview: " << output_formats.paraview << '\n';
404 : std::cout << "OutputFormats.GridFunction: " << output_formats.gridfunction << '\n';
405 : }
406 8 : }
407 :
408 8 : void RefinementData::SetUp(json &model)
409 : {
410 8 : auto refinement = model.find("Refinement");
411 8 : if (refinement == model.end())
412 : {
413 0 : return;
414 : }
415 :
416 : // Options for AMR.
417 8 : tol = refinement->value("Tol", tol);
418 8 : max_it = refinement->value("MaxIts", max_it);
419 8 : max_size = refinement->value("MaxSize", max_size);
420 8 : nonconformal = refinement->value("Nonconformal", nonconformal);
421 8 : max_nc_levels = refinement->value("MaxNCLevels", max_nc_levels);
422 8 : update_fraction = refinement->value("UpdateFraction", update_fraction);
423 8 : maximum_imbalance = refinement->value("MaximumImbalance", maximum_imbalance);
424 8 : save_adapt_iterations = refinement->value("SaveAdaptIterations", save_adapt_iterations);
425 8 : save_adapt_mesh = refinement->value("SaveAdaptMesh", save_adapt_mesh);
426 8 : MFEM_VERIFY(tol > 0.0, "config[\"Refinement\"][\"Tol\"] must be strictly positive!");
427 8 : MFEM_VERIFY(max_it >= 0, "config[\"Refinement\"][\"MaxIts\"] must be non-negative!");
428 8 : MFEM_VERIFY(max_size >= 0, "config[\"Refinement\"][\"MaxSize\"] must be non-negative!");
429 8 : MFEM_VERIFY(max_nc_levels >= 0,
430 : "config[\"Refinement\"][\"MaxNCLevels\"] must be non-negative!");
431 8 : MFEM_VERIFY(update_fraction > 0 && update_fraction < 1,
432 : "config[\"Refinement\"][\"UpdateFraction\" must be in (0,1)!");
433 8 : MFEM_VERIFY(
434 : maximum_imbalance >= 1,
435 : "config[\"Refinement\"][\"MaximumImbalance\"] must be greater than or equal to 1!");
436 :
437 : // Options for a priori refinement.
438 8 : uniform_ref_levels = refinement->value("UniformLevels", uniform_ref_levels);
439 8 : ser_uniform_ref_levels = refinement->value("SerialUniformLevels", ser_uniform_ref_levels);
440 8 : MFEM_VERIFY(uniform_ref_levels >= 0 && ser_uniform_ref_levels >= 0,
441 : "Number of uniform mesh refinement levels must be non-negative!");
442 8 : auto boxes = refinement->find("Boxes");
443 8 : if (boxes != refinement->end())
444 : {
445 0 : MFEM_VERIFY(boxes->is_array(), "config[\"Refinement\"][\"Boxes\"] should specify an "
446 : "array in the configuration file!");
447 0 : for (auto it = boxes->begin(); it != boxes->end(); ++it)
448 : {
449 0 : MFEM_VERIFY(
450 : it->find("Levels") != it->end(),
451 : "Missing \"Boxes\" refinement region \"Levels\" in the configuration file!");
452 0 : auto bbmin = it->find("BoundingBoxMin");
453 0 : auto bbmax = it->find("BoundingBoxMax");
454 0 : MFEM_VERIFY(bbmin != it->end() && bbmax != it->end(),
455 : "Missing \"Boxes\" refinement region \"BoundingBoxMin/Max\" in the "
456 : "configuration file!");
457 0 : MFEM_VERIFY(bbmin->is_array() && bbmin->is_array(),
458 : "config[\"Refinement\"][\"Boxes\"][\"BoundingBoxMin/Max\"] should "
459 : "specify an array in the configuration file!");
460 0 : BoxRefinementData &data = box_list.emplace_back();
461 0 : data.ref_levels = it->at("Levels"); // Required
462 0 : data.bbmin = bbmin->get<std::array<double, 3>>(); // Required
463 0 : data.bbmax = bbmax->get<std::array<double, 3>>(); // Required
464 :
465 : // Cleanup
466 0 : it->erase("Levels");
467 0 : it->erase("BoundingBoxMin");
468 0 : it->erase("BoundingBoxMax");
469 0 : MFEM_VERIFY(it->empty(), "Found an unsupported configuration file keyword under "
470 : "config[\"Refinement\"][\"Boxes\"]!\n"
471 : << it->dump(2));
472 :
473 : // Debug
474 : if constexpr (JSON_DEBUG)
475 : {
476 : std::cout << "Levels: " << data.ref_levels << '\n';
477 : std::cout << "BoundingBoxMin: " << data.bbmin << '\n';
478 : std::cout << "BoundingBoxMax: " << data.bbmax << '\n';
479 : }
480 : }
481 : }
482 8 : auto spheres = refinement->find("Spheres");
483 8 : if (spheres != refinement->end())
484 : {
485 0 : MFEM_VERIFY(spheres->is_array(), "config[\"Refinement\"][\"Spheres\"] should specify "
486 : "an array in the configuration file!");
487 0 : for (auto it = spheres->begin(); it != spheres->end(); ++it)
488 : {
489 0 : MFEM_VERIFY(
490 : it->find("Levels") != it->end(),
491 : "Missing \"Spheres\" refinement region \"Levels\" in the configuration file!");
492 0 : auto ctr = it->find("Center");
493 0 : MFEM_VERIFY(ctr != it->end() && it->find("Radius") != it->end(),
494 : "Missing \"Spheres\" refinement region \"Center\" or \"Radius\" in "
495 : "configuration file!");
496 0 : MFEM_VERIFY(ctr->is_array(),
497 : "config[\"Refinement\"][\"Spheres\"][\"Center\"] should specify "
498 : "an array in the configuration file!");
499 0 : SphereRefinementData &data = sphere_list.emplace_back();
500 0 : data.ref_levels = it->at("Levels"); // Required
501 0 : data.r = it->at("Radius"); // Required
502 0 : data.center = ctr->get<std::array<double, 3>>(); // Required
503 :
504 : // Cleanup
505 0 : it->erase("Levels");
506 0 : it->erase("Radius");
507 0 : it->erase("Center");
508 0 : MFEM_VERIFY(it->empty(), "Found an unsupported configuration file keyword under "
509 : "config[\"Refinement\"][\"Spheres\"]!\n"
510 : << it->dump(2));
511 :
512 : // Debug
513 : if constexpr (JSON_DEBUG)
514 : {
515 : std::cout << "Levels: " << data.ref_levels << '\n';
516 : std::cout << "Radius: " << data.r << '\n';
517 : std::cout << "Center: " << data.center << '\n';
518 : }
519 : }
520 : }
521 :
522 : // Cleanup
523 8 : refinement->erase("Tol");
524 8 : refinement->erase("MaxIts");
525 8 : refinement->erase("MaxSize");
526 8 : refinement->erase("Nonconformal");
527 8 : refinement->erase("MaxNCLevels");
528 8 : refinement->erase("UpdateFraction");
529 8 : refinement->erase("MaximumImbalance");
530 8 : refinement->erase("SaveAdaptIterations");
531 8 : refinement->erase("SaveAdaptMesh");
532 8 : refinement->erase("UniformLevels");
533 8 : refinement->erase("SerialUniformLevels");
534 8 : refinement->erase("Boxes");
535 8 : refinement->erase("Spheres");
536 16 : MFEM_VERIFY(refinement->empty(),
537 : "Found an unsupported configuration file keyword under \"Refinement\"!\n"
538 : << refinement->dump(2));
539 :
540 : // Debug
541 : if constexpr (JSON_DEBUG)
542 : {
543 : std::cout << "Tol: " << tol << '\n';
544 : std::cout << "MaxIts: " << max_it << '\n';
545 : std::cout << "MaxSize: " << max_size << '\n';
546 : std::cout << "Nonconformal: " << nonconformal << '\n';
547 : std::cout << "MaxNCLevels: " << max_nc_levels << '\n';
548 : std::cout << "UpdateFraction: " << update_fraction << '\n';
549 : std::cout << "MaximumImbalance: " << maximum_imbalance << '\n';
550 : std::cout << "SaveAdaptIterations: " << save_adapt_iterations << '\n';
551 : std::cout << "SaveAdaptMesh: " << save_adapt_mesh << '\n';
552 : std::cout << "UniformLevels: " << uniform_ref_levels << '\n';
553 : std::cout << "SerialUniformLevels: " << ser_uniform_ref_levels << '\n';
554 : }
555 : }
556 :
557 8 : void ModelData::SetUp(json &config)
558 : {
559 8 : auto model = config.find("Model");
560 8 : MFEM_VERIFY(model != config.end(),
561 : "\"Model\" must be specified in the configuration file!");
562 8 : MFEM_VERIFY(model->find("Mesh") != model->end(),
563 : "Missing config[\"Model\"][\"Mesh\"] file in the configuration file!");
564 16 : mesh = model->at("Mesh"); // Required
565 8 : L0 = model->value("L0", L0);
566 8 : Lc = model->value("Lc", Lc);
567 8 : remove_curvature = model->value("RemoveCurvature", remove_curvature);
568 8 : make_simplex = model->value("MakeSimplex", make_simplex);
569 8 : make_hex = model->value("MakeHexahedral", make_hex);
570 8 : reorder_elements = model->value("ReorderElements", reorder_elements);
571 8 : clean_unused_elements = model->value("CleanUnusedElements", clean_unused_elements);
572 8 : crack_bdr_elements = model->value("CrackInternalBoundaryElements", crack_bdr_elements);
573 8 : refine_crack_elements = model->value("RefineCrackElements", refine_crack_elements);
574 8 : crack_displ_factor = model->value("CrackDisplacementFactor", crack_displ_factor);
575 8 : add_bdr_elements = model->value("AddInterfaceBoundaryElements", add_bdr_elements);
576 8 : export_prerefined_mesh = model->value("ExportPrerefinedMesh", export_prerefined_mesh);
577 8 : reorient_tet_mesh = model->value("ReorientTetMesh", reorient_tet_mesh);
578 8 : partitioning = model->value("Partitioning", partitioning);
579 8 : refinement.SetUp(*model);
580 :
581 : // Cleanup
582 8 : model->erase("Mesh");
583 8 : model->erase("L0");
584 8 : model->erase("Lc");
585 8 : model->erase("RemoveCurvature");
586 8 : model->erase("MakeSimplex");
587 8 : model->erase("MakeHexahedral");
588 8 : model->erase("ReorderElements");
589 8 : model->erase("CleanUnusedElements");
590 8 : model->erase("CrackInternalBoundaryElements");
591 8 : model->erase("RefineCrackElements");
592 8 : model->erase("CrackDisplacementFactor");
593 8 : model->erase("AddInterfaceBoundaryElements");
594 8 : model->erase("ExportPrerefinedMesh");
595 8 : model->erase("ReorientTetMesh");
596 8 : model->erase("Partitioning");
597 8 : model->erase("Refinement");
598 16 : MFEM_VERIFY(model->empty(),
599 : "Found an unsupported configuration file keyword under \"Model\"!\n"
600 : << model->dump(2));
601 :
602 : // Debug
603 : if constexpr (JSON_DEBUG)
604 : {
605 : std::cout << "Mesh: " << mesh << '\n';
606 : std::cout << "L0: " << L0 << '\n';
607 : std::cout << "Lc: " << Lc << '\n';
608 : std::cout << "RemoveCurvature: " << remove_curvature << '\n';
609 : std::cout << "MakeSimplex: " << make_simplex << '\n';
610 : std::cout << "MakeHexahedral: " << make_hex << '\n';
611 : std::cout << "ReorderElements: " << reorder_elements << '\n';
612 : std::cout << "CleanUnusedElements: " << clean_unused_elements << '\n';
613 : std::cout << "CrackInternalBoundaryElements: " << crack_bdr_elements << '\n';
614 : std::cout << "RefineCrackElements: " << refine_crack_elements << '\n';
615 : std::cout << "CrackDisplacementFactor: " << crack_displ_factor << '\n';
616 : std::cout << "AddInterfaceBoundaryElements: " << add_bdr_elements << '\n';
617 : std::cout << "ExportPrerefinedMesh: " << export_prerefined_mesh << '\n';
618 : std::cout << "ReorientTetMesh: " << reorient_tet_mesh << '\n';
619 : std::cout << "Partitioning: " << partitioning << '\n';
620 : }
621 8 : }
622 :
623 8 : void DomainMaterialData::SetUp(json &domains)
624 : {
625 8 : auto materials = domains.find("Materials");
626 8 : MFEM_VERIFY(materials != domains.end() && materials->is_array(),
627 : "\"Materials\" must be specified as an array in the configuration file!");
628 16 : for (auto it = materials->begin(); it != materials->end(); ++it)
629 : {
630 8 : MFEM_VERIFY(
631 : it->find("Attributes") != it->end(),
632 : "Missing \"Attributes\" list for \"Materials\" domain in the configuration file!");
633 : MaterialData &data = emplace_back();
634 24 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
635 8 : std::sort(data.attributes.begin(), data.attributes.end());
636 8 : ParseSymmetricMatrixData(*it, "Permeability", data.mu_r);
637 8 : ParseSymmetricMatrixData(*it, "Permittivity", data.epsilon_r);
638 8 : ParseSymmetricMatrixData(*it, "LossTan", data.tandelta);
639 8 : ParseSymmetricMatrixData(*it, "Conductivity", data.sigma);
640 8 : data.lambda_L = it->value("LondonDepth", data.lambda_L);
641 :
642 : // Cleanup
643 8 : it->erase("Attributes");
644 8 : it->erase("Permeability");
645 8 : it->erase("Permittivity");
646 8 : it->erase("LossTan");
647 8 : it->erase("Conductivity");
648 8 : it->erase("MaterialAxes");
649 8 : it->erase("LondonDepth");
650 16 : MFEM_VERIFY(it->empty(),
651 : "Found an unsupported configuration file keyword under \"Materials\"!\n"
652 : << it->dump(2));
653 :
654 : // Debug
655 : if constexpr (JSON_DEBUG)
656 : {
657 : std::cout << "Attributes: " << data.attributes << '\n';
658 : std::cout << "Permeability: " << data.mu_r << '\n';
659 : std::cout << "Permittivity: " << data.epsilon_r << '\n';
660 : std::cout << "LossTan: " << data.tandelta << '\n';
661 : std::cout << "Conductivity: " << data.sigma << '\n';
662 : std::cout << "LondonDepth: " << data.lambda_L << '\n';
663 : }
664 : }
665 8 : }
666 :
667 8 : void DomainEnergyPostData::SetUp(json &postpro)
668 : {
669 8 : auto energy = postpro.find("Energy");
670 8 : if (energy == postpro.end())
671 : {
672 0 : return;
673 : }
674 8 : MFEM_VERIFY(energy->is_array(),
675 : "\"Energy\" should specify an array in the configuration file!");
676 16 : for (auto it = energy->begin(); it != energy->end(); ++it)
677 : {
678 8 : auto index = AtIndex(it, "\"Energy\" domain");
679 8 : MFEM_VERIFY(
680 : it->find("Attributes") != it->end(),
681 : "Missing \"Attributes\" list for \"Energy\" domain in the configuration file!");
682 8 : auto ret = mapdata.insert(std::make_pair(index, DomainEnergyData()));
683 8 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"Energy\" domains "
684 : "in the configuration file!");
685 : auto &data = ret.first->second;
686 24 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
687 8 : std::sort(data.attributes.begin(), data.attributes.end());
688 :
689 : // Cleanup
690 8 : it->erase("Index");
691 8 : it->erase("Attributes");
692 16 : MFEM_VERIFY(it->empty(),
693 : "Found an unsupported configuration file keyword under \"Energy\"!\n"
694 : << it->dump(2));
695 :
696 : // Debug
697 : if constexpr (JSON_DEBUG)
698 : {
699 : std::cout << "Index: " << ret.first->first << '\n';
700 : std::cout << "Attributes: " << data.attributes << '\n';
701 : }
702 : }
703 : }
704 :
705 8 : void ProbePostData::SetUp(json &postpro)
706 : {
707 8 : auto probe = postpro.find("Probe");
708 8 : if (probe == postpro.end())
709 : {
710 0 : return;
711 : }
712 8 : MFEM_VERIFY(probe->is_array(),
713 : "\"Probe\" should specify an array in the configuration file!");
714 24 : for (auto it = probe->begin(); it != probe->end(); ++it)
715 : {
716 16 : auto index = AtIndex(it, "\"Probe\" point");
717 16 : auto ctr = it->find("Center");
718 16 : MFEM_VERIFY(ctr != it->end() && ctr->is_array(),
719 : "Missing \"Probe\" point \"Center\" or \"Center\" should specify an array "
720 : "in the configuration file!");
721 48 : auto ret = mapdata.insert(std::make_pair(index, ProbeData()));
722 16 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"Probe\" points in "
723 : "the configuration file!");
724 : auto &data = ret.first->second;
725 16 : data.center = ctr->get<std::array<double, 3>>(); // Required
726 :
727 : // Cleanup
728 16 : it->erase("Index");
729 16 : it->erase("Center");
730 32 : MFEM_VERIFY(it->empty(),
731 : "Found an unsupported configuration file keyword under \"Probe\"!\n"
732 : << it->dump(2));
733 :
734 : // Debug
735 : if constexpr (JSON_DEBUG)
736 : {
737 : std::cout << "Index: " << ret.first->first << '\n';
738 : std::cout << "Center: " << data.center << '\n';
739 : }
740 : }
741 : }
742 :
743 8 : void DomainPostData::SetUp(json &domains)
744 : {
745 8 : auto postpro = domains.find("Postprocessing");
746 8 : if (postpro == domains.end())
747 : {
748 0 : return;
749 : }
750 8 : energy.SetUp(*postpro);
751 8 : probe.SetUp(*postpro);
752 :
753 : // Store all unique postprocessing domain attributes.
754 16 : for (const auto &[idx, data] : energy)
755 : {
756 8 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
757 : }
758 8 : std::sort(attributes.begin(), attributes.end());
759 8 : attributes.erase(std::unique(attributes.begin(), attributes.end()), attributes.end());
760 : attributes.shrink_to_fit();
761 :
762 : // Cleanup
763 8 : postpro->erase("Energy");
764 8 : postpro->erase("Probe");
765 16 : MFEM_VERIFY(postpro->empty(),
766 : "Found an unsupported configuration file keyword under \"Postprocessing\"!\n"
767 : << postpro->dump(2));
768 : }
769 :
770 8 : void DomainData::SetUp(json &config)
771 : {
772 8 : auto domains = config.find("Domains");
773 8 : MFEM_VERIFY(domains != config.end(),
774 : "\"Domains\" must be specified in the configuration file!");
775 8 : materials.SetUp(*domains);
776 8 : postpro.SetUp(*domains);
777 :
778 : // Store all unique domain attributes.
779 16 : for (const auto &data : materials)
780 : {
781 8 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
782 : }
783 8 : std::sort(attributes.begin(), attributes.end());
784 8 : attributes.erase(std::unique(attributes.begin(), attributes.end()), attributes.end());
785 : attributes.shrink_to_fit();
786 16 : for (const auto &attr : postpro.attributes)
787 : {
788 8 : MFEM_VERIFY(std::lower_bound(attributes.begin(), attributes.end(), attr) !=
789 : attributes.end(),
790 : "Domain postprocessing can only be enabled on domains which have a "
791 : "corresponding \"Materials\" entry!");
792 : }
793 :
794 : // Cleanup
795 8 : domains->erase("Materials");
796 8 : domains->erase("Postprocessing");
797 16 : MFEM_VERIFY(domains->empty(),
798 : "Found an unsupported configuration file keyword under \"Domains\"!\n"
799 : << domains->dump(2));
800 8 : }
801 :
802 39 : void PecBoundaryData::SetUp(json &boundaries)
803 : {
804 39 : auto pec = boundaries.find("PEC");
805 39 : auto ground = boundaries.find("Ground");
806 39 : if (pec == boundaries.end() && ground == boundaries.end())
807 : {
808 37 : return;
809 : }
810 2 : if (pec == boundaries.end())
811 : {
812 : pec = ground;
813 : }
814 2 : else if (ground == boundaries.end()) // Do nothing
815 : {
816 : }
817 : else
818 : {
819 0 : MFEM_ABORT(
820 : "Configuration file should not specify both \"PEC\" and \"Ground\" boundaries!");
821 : }
822 2 : MFEM_VERIFY(
823 : pec->find("Attributes") != pec->end(),
824 : "Missing \"Attributes\" list for \"PEC\" boundary in the configuration file!");
825 6 : attributes = pec->at("Attributes").get<std::vector<int>>(); // Required
826 2 : std::sort(attributes.begin(), attributes.end());
827 :
828 : // Cleanup
829 2 : pec->erase("Attributes");
830 4 : MFEM_VERIFY(pec->empty(),
831 : "Found an unsupported configuration file keyword under \"PEC\"!\n"
832 : << pec->dump(2));
833 :
834 : // Debug
835 : if constexpr (JSON_DEBUG)
836 : {
837 : std::cout << "PEC:" << attributes << '\n';
838 : }
839 : }
840 :
841 39 : void PmcBoundaryData::SetUp(json &boundaries)
842 : {
843 39 : auto pmc = boundaries.find("PMC");
844 39 : auto zeroq = boundaries.find("ZeroCharge");
845 39 : if (pmc == boundaries.end() && zeroq == boundaries.end())
846 : {
847 39 : return;
848 : }
849 0 : if (pmc == boundaries.end())
850 : {
851 : pmc = zeroq;
852 : }
853 0 : else if (zeroq == boundaries.end()) // Do nothing
854 : {
855 : }
856 : else
857 : {
858 0 : MFEM_ABORT("Configuration file should not specify both \"PMC\" and \"ZeroCharge\" "
859 : "boundaries!");
860 : }
861 0 : MFEM_VERIFY(
862 : pmc->find("Attributes") != pmc->end(),
863 : "Missing \"Attributes\" list for \"PMC\" boundary in the configuration file!");
864 0 : attributes = pmc->at("Attributes").get<std::vector<int>>(); // Required
865 0 : std::sort(attributes.begin(), attributes.end());
866 :
867 : // Cleanup
868 0 : pmc->erase("Attributes");
869 0 : MFEM_VERIFY(pmc->empty(),
870 : "Found an unsupported configuration file keyword under \"PMC\"!\n"
871 : << pmc->dump(2));
872 :
873 : // Debug
874 : if constexpr (JSON_DEBUG)
875 : {
876 : std::cout << "PMC:" << attributes << '\n';
877 : }
878 : }
879 :
880 39 : void WavePortPecBoundaryData::SetUp(json &boundaries)
881 : {
882 39 : auto pec = boundaries.find("WavePortPEC");
883 39 : if (pec == boundaries.end())
884 : {
885 39 : return;
886 : }
887 0 : MFEM_VERIFY(pec->find("Attributes") != pec->end(),
888 : "Missing \"Attributes\" list for \"WavePortPEC\" boundary in the "
889 : "configuration file!");
890 0 : attributes = pec->at("Attributes").get<std::vector<int>>(); // Required
891 0 : std::sort(attributes.begin(), attributes.end());
892 :
893 : // Cleanup
894 0 : pec->erase("Attributes");
895 0 : MFEM_VERIFY(pec->empty(),
896 : "Found an unsupported configuration file keyword under \"WavePortPEC\"!\n"
897 : << pec->dump(2));
898 :
899 : // Debug
900 : if constexpr (JSON_DEBUG)
901 : {
902 : std::cout << "WavePortPEC:" << attributes << '\n';
903 : }
904 : }
905 :
906 39 : void FarfieldBoundaryData::SetUp(json &boundaries)
907 : {
908 39 : auto absorbing = boundaries.find("Absorbing");
909 39 : if (absorbing == boundaries.end())
910 : {
911 37 : return;
912 : }
913 2 : MFEM_VERIFY(
914 : absorbing->find("Attributes") != absorbing->end(),
915 : "Missing \"Attributes\" list for \"Absorbing\" boundary in the configuration file!");
916 6 : attributes = absorbing->at("Attributes").get<std::vector<int>>(); // Required
917 2 : std::sort(attributes.begin(), attributes.end());
918 2 : order = absorbing->value("Order", order);
919 2 : MFEM_VERIFY(order == 1 || order == 2,
920 : "Supported values for config[\"Absorbing\"][\"Order\"] are 1 or 2!");
921 :
922 : // Cleanup
923 2 : absorbing->erase("Attributes");
924 2 : absorbing->erase("Order");
925 4 : MFEM_VERIFY(absorbing->empty(),
926 : "Found an unsupported configuration file keyword under \"Absorbing\"!\n"
927 : << absorbing->dump(2));
928 :
929 : // Debug
930 : if constexpr (JSON_DEBUG)
931 : {
932 : std::cout << "Absorbing:" << attributes << '\n';
933 : std::cout << "Order: " << order << '\n';
934 : }
935 : }
936 :
937 39 : void ConductivityBoundaryData::SetUp(json &boundaries)
938 : {
939 39 : auto conductivity = boundaries.find("Conductivity");
940 39 : if (conductivity == boundaries.end())
941 : {
942 39 : return;
943 : }
944 0 : MFEM_VERIFY(conductivity->is_array(),
945 : "\"Conductivity\" should specify an array in the configuration file!");
946 0 : for (auto it = conductivity->begin(); it != conductivity->end(); ++it)
947 : {
948 0 : MFEM_VERIFY(it->find("Attributes") != it->end(),
949 : "Missing \"Attributes\" list for \"Conductivity\" boundary in the "
950 : "configuration file!");
951 0 : MFEM_VERIFY(
952 : it->find("Conductivity") != it->end(),
953 : "Missing \"Conductivity\" boundary \"Conductivity\" in the configuration file!");
954 : ConductivityData &data = emplace_back();
955 0 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
956 0 : std::sort(data.attributes.begin(), data.attributes.end());
957 0 : data.sigma = it->at("Conductivity"); // Required
958 0 : data.mu_r = it->value("Permeability", data.mu_r);
959 0 : data.h = it->value("Thickness", data.h);
960 0 : data.external = it->value("External", data.external);
961 :
962 : // Cleanup
963 0 : it->erase("Attributes");
964 0 : it->erase("Conductivity");
965 0 : it->erase("Permeability");
966 0 : it->erase("Thickness");
967 0 : it->erase("External");
968 0 : MFEM_VERIFY(it->empty(),
969 : "Found an unsupported configuration file keyword under \"Conductivity\"!\n"
970 : << it->dump(2));
971 :
972 : // Debug
973 : if constexpr (JSON_DEBUG)
974 : {
975 : std::cout << "Attributes: " << data.attributes << '\n';
976 : std::cout << "Conductivity: " << data.sigma << '\n';
977 : std::cout << "Permeability: " << data.mu_r << '\n';
978 : std::cout << "Thickness: " << data.h << '\n';
979 : std::cout << "External: " << data.external << '\n';
980 : }
981 : }
982 : }
983 :
984 39 : void ImpedanceBoundaryData::SetUp(json &boundaries)
985 : {
986 39 : auto impedance = boundaries.find("Impedance");
987 39 : if (impedance == boundaries.end())
988 : {
989 39 : return;
990 : }
991 0 : MFEM_VERIFY(impedance->is_array(),
992 : "\"Impedance\" should specify an array in the configuration file!");
993 0 : for (auto it = impedance->begin(); it != impedance->end(); ++it)
994 : {
995 0 : MFEM_VERIFY(it->find("Attributes") != it->end(),
996 : "Missing \"Attributes\" list for \"Impedance\" boundary in the "
997 : "configuration file!");
998 : ImpedanceData &data = emplace_back();
999 0 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
1000 0 : std::sort(data.attributes.begin(), data.attributes.end());
1001 0 : data.Rs = it->value("Rs", data.Rs);
1002 0 : data.Ls = it->value("Ls", data.Ls);
1003 0 : data.Cs = it->value("Cs", data.Cs);
1004 :
1005 : // Cleanup
1006 0 : it->erase("Attributes");
1007 0 : it->erase("Rs");
1008 0 : it->erase("Ls");
1009 0 : it->erase("Cs");
1010 0 : MFEM_VERIFY(it->empty(),
1011 : "Found an unsupported configuration file keyword under \"Impedance\"!\n"
1012 : << it->dump(2));
1013 :
1014 : // Debug
1015 : if constexpr (JSON_DEBUG)
1016 : {
1017 : std::cout << "Attributes: " << data.attributes << '\n';
1018 : std::cout << "Rs: " << data.Rs << '\n';
1019 : std::cout << "Ls: " << data.Ls << '\n';
1020 : std::cout << "Cs: " << data.Cs << '\n';
1021 : }
1022 : }
1023 : }
1024 :
1025 81 : int ParsePortExcitation(json::iterator &port_it, int default_excitation)
1026 : {
1027 81 : auto it_excitation = port_it->find("Excitation");
1028 81 : if (it_excitation == port_it->end())
1029 : {
1030 : // Keep default; don't set input flag.
1031 : return default_excitation;
1032 : }
1033 51 : else if (it_excitation->is_boolean())
1034 : {
1035 58 : return int(it_excitation->get<bool>()); // 0 false; 1 true
1036 : }
1037 22 : else if (it_excitation->is_number_unsigned())
1038 : {
1039 40 : return it_excitation->get<int>();
1040 : }
1041 : else
1042 : {
1043 10 : MFEM_ABORT(fmt::format("\"Excitation\" on port index {:d} could not be parsed "
1044 : "as a bool or unsigned (non-negative) integer; got {}",
1045 : int(port_it->at("Index")), it_excitation->dump(2)));
1046 : }
1047 : }
1048 :
1049 39 : void LumpedPortBoundaryData::SetUp(json &boundaries)
1050 : {
1051 39 : auto port = boundaries.find("LumpedPort");
1052 39 : auto terminal = boundaries.find("Terminal");
1053 39 : if (port == boundaries.end() && terminal == boundaries.end())
1054 : {
1055 1 : return;
1056 : }
1057 :
1058 38 : if (port == boundaries.end())
1059 : {
1060 : port = terminal;
1061 : }
1062 38 : else if (terminal == boundaries.end()) // Do nothing
1063 : {
1064 : }
1065 : else
1066 : {
1067 0 : MFEM_ABORT("Configuration file should not specify both \"LumpedPort\" and \"Terminal\" "
1068 : "boundaries!");
1069 : }
1070 :
1071 76 : std::string label = (terminal != boundaries.end()) ? "\"Terminal\"" : "\"LumpedPort\"";
1072 38 : MFEM_VERIFY(port->is_array(),
1073 : label << " should specify an array in the configuration file!");
1074 99 : for (auto it = port->begin(); it != port->end(); ++it)
1075 : {
1076 64 : auto index = AtIndex(it, label);
1077 63 : auto ret = mapdata.insert(std::make_pair(index, LumpedPortData()));
1078 66 : MFEM_VERIFY(ret.second, fmt::format("Repeated \"Index\" found when processing {} "
1079 : "boundaries in the configuration file!",
1080 : label));
1081 : auto &data = ret.first->second;
1082 62 : data.R = it->value("R", data.R);
1083 62 : data.L = it->value("L", data.L);
1084 62 : data.C = it->value("C", data.C);
1085 62 : data.Rs = it->value("Rs", data.Rs);
1086 62 : data.Ls = it->value("Ls", data.Ls);
1087 62 : data.Cs = it->value("Cs", data.Cs);
1088 :
1089 62 : data.excitation = ParsePortExcitation(it, data.excitation);
1090 61 : data.active = it->value("Active", data.active);
1091 61 : if (it->find("Attributes") != it->end())
1092 : {
1093 59 : MFEM_VERIFY(
1094 : it->find("Elements") == it->end(),
1095 : fmt::format(
1096 : "Cannot specify both top-level \"Attributes\" list and \"Elements\" for "
1097 : "{} in the configuration file!",
1098 : label));
1099 59 : auto &elem = data.elements.emplace_back();
1100 118 : ParseElementData(*it, "Direction", terminal == boundaries.end(), elem);
1101 : }
1102 : else
1103 : {
1104 2 : auto elements = it->find("Elements");
1105 2 : MFEM_VERIFY(elements != it->end(),
1106 : fmt::format("Missing top-level \"Attributes\" list or \"Elements\" for "
1107 : "{} in the configuration file!",
1108 : label));
1109 6 : for (auto elem_it = elements->begin(); elem_it != elements->end(); ++elem_it)
1110 : {
1111 4 : MFEM_VERIFY(elem_it->find("Attributes") != elem_it->end(),
1112 : fmt::format("Missing \"Attributes\" list for {} element in "
1113 : "the configuration file!",
1114 : label));
1115 4 : auto &elem = data.elements.emplace_back();
1116 7 : ParseElementData(*elem_it, "Direction", terminal == boundaries.end(), elem);
1117 :
1118 : // Cleanup
1119 4 : elem_it->erase("Attributes");
1120 4 : elem_it->erase("Direction");
1121 4 : elem_it->erase("CoordinateSystem");
1122 8 : MFEM_VERIFY(elem_it->empty(), fmt::format("Found an unsupported configuration file "
1123 : "keyword under {} element!\n{}",
1124 : label, elem_it->dump(2)));
1125 : }
1126 : }
1127 :
1128 : // Cleanup
1129 61 : it->erase("Index");
1130 61 : it->erase("R");
1131 61 : it->erase("L");
1132 61 : it->erase("C");
1133 61 : it->erase("Rs");
1134 61 : it->erase("Ls");
1135 61 : it->erase("Cs");
1136 61 : it->erase("Excitation");
1137 61 : it->erase("Active");
1138 61 : it->erase("Attributes");
1139 61 : it->erase("Direction");
1140 61 : it->erase("CoordinateSystem");
1141 61 : it->erase("Elements");
1142 122 : MFEM_VERIFY(it->empty(),
1143 : fmt::format("Found an unsupported configuration file keyword under {}!\n{}",
1144 : label, it->dump(2)));
1145 :
1146 : // Debug
1147 : if constexpr (JSON_DEBUG)
1148 : {
1149 : std::cout << "Index: " << ret.first->first << '\n';
1150 : std::cout << "R: " << data.R << '\n';
1151 : std::cout << "L: " << data.L << '\n';
1152 : std::cout << "C: " << data.C << '\n';
1153 : std::cout << "Rs: " << data.Rs << '\n';
1154 : std::cout << "Ls: " << data.Ls << '\n';
1155 : std::cout << "Cs: " << data.Cs << '\n';
1156 : std::cout << "Excitation: " << data.excitation << '\n';
1157 : std::cout << "Active: " << data.active << '\n';
1158 : for (const auto &elem : data.elements)
1159 : {
1160 : std::cout << "Attributes: " << elem.attributes << '\n';
1161 : std::cout << "Direction: " << elem.direction << '\n';
1162 : std::cout << "CoordinateSystem: " << elem.coordinate_system << '\n';
1163 : }
1164 : }
1165 : }
1166 : }
1167 :
1168 36 : void PeriodicBoundaryData::SetUp(json &boundaries)
1169 : {
1170 36 : auto periodic = boundaries.find("Periodic");
1171 36 : if (periodic == boundaries.end())
1172 : {
1173 35 : return;
1174 : }
1175 1 : auto floquet = periodic->find("FloquetWaveVector");
1176 1 : if (floquet != periodic->end())
1177 : {
1178 0 : MFEM_VERIFY(floquet->is_array(),
1179 : "\"FloquetWaveVector\" should specify an array in the configuration file!");
1180 0 : wave_vector = floquet->get<std::array<double, 3>>();
1181 : }
1182 :
1183 : // Debug
1184 : if constexpr (JSON_DEBUG)
1185 : {
1186 : std::cout << "FloquetWaveVector: " << wave_vector << '\n';
1187 : }
1188 :
1189 1 : auto pairs = periodic->find("BoundaryPairs");
1190 1 : MFEM_VERIFY(pairs->is_array(),
1191 : "\"BoundaryPairs\" should specify an array in the configuration file!");
1192 2 : for (auto it = pairs->begin(); it != pairs->end(); ++it)
1193 : {
1194 1 : MFEM_VERIFY(it->find("DonorAttributes") != it->end(),
1195 : "Missing \"DonorAttributes\" list for \"Periodic\" boundary in the "
1196 : "configuration file!");
1197 1 : MFEM_VERIFY(it->find("ReceiverAttributes") != it->end(),
1198 : "Missing \"ReceiverAttributes\" list for \"Periodic\" boundary in the "
1199 : "configuration file!");
1200 :
1201 1 : PeriodicData &data = boundary_pairs.emplace_back();
1202 2 : data.donor_attributes = it->at("DonorAttributes").get<std::vector<int>>(); // Required
1203 : data.receiver_attributes =
1204 2 : it->at("ReceiverAttributes").get<std::vector<int>>(); // Required
1205 1 : auto translation = it->find("Translation");
1206 1 : if (translation != it->end())
1207 : {
1208 0 : MFEM_VERIFY(translation->is_array(),
1209 : "\"Translation\" should specify an array in the configuration file!");
1210 0 : std::array<double, 3> translation_array = translation->get<std::array<double, 3>>();
1211 0 : for (int i = 0; i < 3; i++)
1212 : {
1213 0 : data.affine_transform[i * 4 + i] = 1.0;
1214 0 : data.affine_transform[i * 4 + 3] = translation_array[i];
1215 : }
1216 0 : data.affine_transform[3 * 4 + 3] = 1.0;
1217 : }
1218 1 : auto transformation = it->find("AffineTransformation");
1219 1 : if (transformation != it->end())
1220 : {
1221 0 : MFEM_VERIFY(
1222 : transformation->is_array(),
1223 : "\"AffineTransformation\" should specify an array in the configuration file!");
1224 0 : data.affine_transform = transformation->get<std::array<double, 16>>();
1225 : }
1226 :
1227 : // Cleanup
1228 1 : it->erase("DonorAttributes");
1229 1 : it->erase("ReceiverAttributes");
1230 1 : it->erase("Translation");
1231 1 : it->erase("AffineTransformation");
1232 2 : MFEM_VERIFY(it->empty(),
1233 : "Found an unsupported configuration file keyword under \"Periodic\"!\n"
1234 : << it->dump(2));
1235 :
1236 : // Debug
1237 : if constexpr (JSON_DEBUG)
1238 : {
1239 : std::cout << "DonorAttributes: " << data.donor_attributes << '\n';
1240 : std::cout << "ReceiverAttributes: " << data.receiver_attributes << '\n';
1241 : std::cout << "AffineTransformation: " << data.affine_transform << '\n';
1242 : }
1243 : }
1244 : }
1245 :
1246 36 : void WavePortBoundaryData::SetUp(json &boundaries)
1247 : {
1248 36 : auto port = boundaries.find("WavePort");
1249 36 : if (port == boundaries.end())
1250 : {
1251 24 : return;
1252 : }
1253 12 : MFEM_VERIFY(port->is_array(),
1254 : "\"WavePort\" should specify an array in the configuration file!");
1255 :
1256 30 : for (auto it = port->begin(); it != port->end(); ++it)
1257 : {
1258 21 : MFEM_VERIFY(
1259 : it->find("Attributes") != it->end(),
1260 : "Missing \"Attributes\" list for \"WavePort\" boundary in the configuration file!");
1261 21 : auto index = AtIndex(it, "\"WavePort\" boundary");
1262 20 : auto ret = mapdata.insert(std::make_pair(index, WavePortData()));
1263 23 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"WavePort\" "
1264 : "boundaries in the configuration file!");
1265 : auto &data = ret.first->second;
1266 57 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
1267 19 : std::sort(data.attributes.begin(), data.attributes.end());
1268 19 : data.mode_idx = it->value("Mode", data.mode_idx);
1269 19 : MFEM_VERIFY(data.mode_idx > 0,
1270 : "\"WavePort\" boundary \"Mode\" must be positive (1-based)!");
1271 19 : data.d_offset = it->value("Offset", data.d_offset);
1272 19 : data.eigen_solver = it->value("SolverType", data.eigen_solver);
1273 :
1274 19 : data.excitation = ParsePortExcitation(it, data.excitation);
1275 18 : data.active = it->value("Active", data.active);
1276 18 : data.ksp_max_its = it->value("MaxIts", data.ksp_max_its);
1277 18 : data.ksp_tol = it->value("KSPTol", data.ksp_tol);
1278 18 : data.eig_tol = it->value("EigenTol", data.eig_tol);
1279 18 : data.verbose = it->value("Verbose", data.verbose);
1280 :
1281 : // Cleanup
1282 18 : it->erase("Index");
1283 18 : it->erase("Attributes");
1284 18 : it->erase("Mode");
1285 18 : it->erase("Offset");
1286 18 : it->erase("SolverType");
1287 18 : it->erase("Excitation");
1288 18 : it->erase("Active");
1289 18 : it->erase("MaxIts");
1290 18 : it->erase("KSPTol");
1291 18 : it->erase("EigenTol");
1292 18 : it->erase("Verbose");
1293 36 : MFEM_VERIFY(it->empty(),
1294 : "Found an unsupported configuration file keyword under \"WavePort\"!\n"
1295 : << it->dump(2));
1296 :
1297 : // Debug
1298 : if constexpr (JSON_DEBUG)
1299 : {
1300 : std::cout << "Index: " << ret.first->first << '\n';
1301 : std::cout << "Attributes: " << data.attributes << '\n';
1302 : std::cout << "Mode: " << data.mode_idx << '\n';
1303 : std::cout << "Offset: " << data.d_offset << '\n';
1304 : std::cout << "SolverType: " << data.eigen_solver << '\n';
1305 : std::cout << "Excitation: " << data.excitation << '\n';
1306 : std::cout << "Active: " << data.active << '\n';
1307 : std::cout << "MaxIts: " << data.ksp_max_its << '\n';
1308 : std::cout << "KSPTol: " << data.ksp_tol << '\n';
1309 : std::cout << "EigenTol: " << data.eig_tol << '\n';
1310 : std::cout << "Verbose: " << data.verbose << '\n';
1311 : }
1312 : }
1313 : }
1314 :
1315 33 : void SurfaceCurrentBoundaryData::SetUp(json &boundaries)
1316 : {
1317 33 : auto source = boundaries.find("SurfaceCurrent");
1318 33 : if (source == boundaries.end())
1319 : {
1320 33 : return;
1321 : }
1322 0 : MFEM_VERIFY(source->is_array(),
1323 : "\"SurfaceCurrent\" should specify an array in the configuration file!");
1324 0 : for (auto it = source->begin(); it != source->end(); ++it)
1325 : {
1326 0 : auto index = AtIndex(it, "\"SurfaceCurrent\" source");
1327 0 : auto ret = mapdata.insert(std::make_pair(index, SurfaceCurrentData()));
1328 0 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"SurfaceCurrent\" "
1329 : "boundaries in the configuration file!");
1330 : auto &data = ret.first->second;
1331 0 : if (it->find("Attributes") != it->end())
1332 : {
1333 0 : MFEM_VERIFY(it->find("Elements") == it->end(),
1334 : "Cannot specify both top-level \"Attributes\" list and \"Elements\" for "
1335 : "\"SurfaceCurrent\" boundary in the configuration file!");
1336 0 : auto &elem = data.elements.emplace_back();
1337 0 : ParseElementData(*it, "Direction", true, elem);
1338 : }
1339 : else
1340 : {
1341 0 : auto elements = it->find("Elements");
1342 0 : MFEM_VERIFY(
1343 : elements != it->end(),
1344 : "Missing top-level \"Attributes\" list or \"Elements\" for \"SurfaceCurrent\" "
1345 : "boundary in the configuration file!");
1346 0 : for (auto elem_it = elements->begin(); elem_it != elements->end(); ++elem_it)
1347 : {
1348 0 : MFEM_VERIFY(
1349 : elem_it->find("Attributes") != elem_it->end(),
1350 : "Missing \"Attributes\" list for \"SurfaceCurrent\" boundary element in "
1351 : "configuration file!");
1352 0 : auto &elem = data.elements.emplace_back();
1353 0 : ParseElementData(*elem_it, "Direction", true, elem);
1354 :
1355 : // Cleanup
1356 0 : elem_it->erase("Attributes");
1357 0 : elem_it->erase("Direction");
1358 0 : elem_it->erase("CoordinateSystem");
1359 0 : MFEM_VERIFY(elem_it->empty(), "Found an unsupported configuration file keyword "
1360 : "under \"SurfaceCurrent\" boundary element!\n"
1361 : << elem_it->dump(2));
1362 : }
1363 : }
1364 :
1365 : // Cleanup
1366 0 : it->erase("Index");
1367 0 : it->erase("Attributes");
1368 0 : it->erase("Direction");
1369 0 : it->erase("Elements");
1370 0 : it->erase("CoordinateSystem");
1371 0 : MFEM_VERIFY(
1372 : it->empty(),
1373 : "Found an unsupported configuration file keyword under \"SurfaceCurrent\"!\n"
1374 : << it->dump(2));
1375 :
1376 : // Debug
1377 : if constexpr (JSON_DEBUG)
1378 : {
1379 : std::cout << "Index: " << ret.first->first << '\n';
1380 : for (const auto &elem : data.elements)
1381 : {
1382 : std::cout << "Attributes: " << elem.attributes << '\n';
1383 : std::cout << "Direction: " << elem.direction << '\n';
1384 : std::cout << "CoordinateSystem: " << elem.coordinate_system << '\n';
1385 : }
1386 : }
1387 : }
1388 : }
1389 :
1390 8 : void SurfaceFluxPostData::SetUp(json &postpro)
1391 : {
1392 8 : auto flux = postpro.find("SurfaceFlux");
1393 8 : if (flux == postpro.end())
1394 : {
1395 0 : return;
1396 : }
1397 8 : MFEM_VERIFY(flux->is_array(),
1398 : "\"SurfaceFlux\" should specify an array in the configuration file!");
1399 24 : for (auto it = flux->begin(); it != flux->end(); ++it)
1400 : {
1401 16 : auto index = AtIndex(it, "\"SurfaceFlux\" boundary");
1402 16 : MFEM_VERIFY(it->find("Attributes") != it->end() && it->find("Type") != it->end(),
1403 : "Missing \"Attributes\" list or \"Type\" for \"SurfaceFlux\" boundary "
1404 : "in the configuration file!");
1405 16 : auto ret = mapdata.insert(std::make_pair(index, SurfaceFluxData()));
1406 16 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"SurfaceFlux\" "
1407 : "boundaries in the configuration file!");
1408 : auto &data = ret.first->second;
1409 48 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
1410 16 : std::sort(data.attributes.begin(), data.attributes.end());
1411 16 : data.type = it->at("Type"); // Required
1412 16 : data.two_sided = it->value("TwoSided", data.two_sided);
1413 16 : auto ctr = it->find("Center");
1414 16 : if (ctr != it->end())
1415 : {
1416 0 : MFEM_VERIFY(ctr->is_array(),
1417 : "\"Center\" should specify an array in the configuration file!");
1418 0 : data.center = ctr->get<std::array<double, 3>>();
1419 0 : data.no_center = false;
1420 : }
1421 :
1422 : // Cleanup
1423 16 : it->erase("Index");
1424 16 : it->erase("Attributes");
1425 16 : it->erase("Type");
1426 16 : it->erase("TwoSided");
1427 16 : it->erase("Center");
1428 32 : MFEM_VERIFY(it->empty(),
1429 : "Found an unsupported configuration file keyword under \"SurfaceFlux\"!\n"
1430 : << it->dump(2));
1431 :
1432 : // Debug
1433 : if constexpr (JSON_DEBUG)
1434 : {
1435 : std::cout << "Index: " << ret.first->first << '\n';
1436 : std::cout << "Attributes: " << data.attributes << '\n';
1437 : std::cout << "Type: " << data.type << '\n';
1438 : std::cout << "TwoSided: " << data.two_sided << '\n';
1439 : std::cout << "Center: " << data.center << '\n';
1440 : }
1441 : }
1442 : }
1443 :
1444 8 : void InterfaceDielectricPostData::SetUp(json &postpro)
1445 : {
1446 8 : auto dielectric = postpro.find("Dielectric");
1447 8 : if (dielectric == postpro.end())
1448 : {
1449 0 : return;
1450 : }
1451 8 : MFEM_VERIFY(dielectric->is_array(),
1452 : "\"Dielectric\" should specify an array in the configuration file!");
1453 32 : for (auto it = dielectric->begin(); it != dielectric->end(); ++it)
1454 : {
1455 24 : auto index = AtIndex(it, "\"Dielectric\" boundary");
1456 24 : MFEM_VERIFY(it->find("Attributes") != it->end() && it->find("Thickness") != it->end() &&
1457 : it->find("Permittivity") != it->end(),
1458 : "Missing \"Dielectric\" boundary \"Attributes\" list, \"Thickness\", or "
1459 : "\"Permittivity\" in the configuration file!");
1460 24 : auto ret = mapdata.insert(std::make_pair(index, InterfaceDielectricData()));
1461 24 : MFEM_VERIFY(ret.second, "Repeated \"Index\" found when processing \"Dielectric\" "
1462 : "boundaries in the configuration file!");
1463 : auto &data = ret.first->second;
1464 72 : data.attributes = it->at("Attributes").get<std::vector<int>>(); // Required
1465 24 : std::sort(data.attributes.begin(), data.attributes.end());
1466 24 : data.type = it->value("Type", data.type);
1467 24 : data.t = it->at("Thickness"); // Required
1468 24 : data.epsilon_r = it->at("Permittivity"); // Required
1469 24 : data.tandelta = it->value("LossTan", data.tandelta);
1470 :
1471 : // Cleanup
1472 24 : it->erase("Index");
1473 24 : it->erase("Attributes");
1474 24 : it->erase("Type");
1475 24 : it->erase("Thickness");
1476 24 : it->erase("Permittivity");
1477 24 : it->erase("LossTan");
1478 48 : MFEM_VERIFY(it->empty(),
1479 : "Found an unsupported configuration file keyword under \"Dielectric\"!\n"
1480 : << it->dump(2));
1481 :
1482 : // Debug
1483 : if constexpr (JSON_DEBUG)
1484 : {
1485 : std::cout << "Index: " << ret.first->first << '\n';
1486 : std::cout << "Attributes: " << data.attributes << '\n';
1487 : std::cout << "Type: " << data.type << '\n';
1488 : std::cout << "Thickness: " << data.t << '\n';
1489 : std::cout << "Permittivity: " << data.epsilon_r << '\n';
1490 : std::cout << "LossTan: " << data.tandelta << '\n';
1491 : }
1492 : }
1493 : }
1494 25 : void FarFieldPostData::SetUp(json &postpro)
1495 : {
1496 25 : auto farfield = postpro.find("FarField");
1497 25 : if (farfield == postpro.end())
1498 : {
1499 9 : return;
1500 : }
1501 :
1502 16 : MFEM_VERIFY(farfield->find("Attributes") != farfield->end(),
1503 : "Missing \"Attributes\" list for \"FarField\" postprocessing in the "
1504 : "configuration file!");
1505 :
1506 48 : attributes = farfield->at("Attributes").get<std::vector<int>>(); // Required
1507 16 : std::sort(attributes.begin(), attributes.end());
1508 :
1509 : // Generate NSample points with the following properties:
1510 : // - If NSample >= 2, the generated points are precisely NSample, otherwise NSample = 2.
1511 : // - The poles, the equator, and the XZ plane are always included.
1512 : // - The points are almost uniformly on a sphere, with a small bias due to satisfying the
1513 : // previous condition.
1514 : // - The points are on rings of constant theta.
1515 :
1516 16 : auto nsample_json = farfield->find("NSample");
1517 : int nsample = 0;
1518 16 : if (nsample_json != farfield->end())
1519 : {
1520 10 : nsample = nsample_json->get<int>();
1521 10 : if (nsample > 0)
1522 : {
1523 : // Always include poles.
1524 9 : thetaphis.emplace_back(0.0, 0.0); // North pole.
1525 9 : thetaphis.emplace_back(M_PI, 0.0); // South pole.
1526 :
1527 9 : if (nsample > 2)
1528 : {
1529 7 : int remaining = nsample - 2;
1530 :
1531 : // Distribute all remaining points across rings with number weighted by the
1532 : // local circumference.
1533 7 : int n_theta = std::max(1, static_cast<int>(std::sqrt(remaining)));
1534 7 : n_theta = std::min(n_theta, remaining); // Can't have more rings than points.
1535 :
1536 7 : std::vector<int> points_per_level(n_theta);
1537 7 : std::vector<double> sin_theta_values(n_theta);
1538 : double total_sin_theta = 0.0;
1539 :
1540 : // Calculate sin(theta) for each ring and total (sin(theta) is proportional to the
1541 : // circumference).
1542 278 : for (int i = 0; i < n_theta; ++i)
1543 : {
1544 271 : double theta = std::acos(1.0 - 2.0 * (i + 1) / (n_theta + 1.0));
1545 271 : sin_theta_values[i] = std::sin(theta);
1546 271 : total_sin_theta += sin_theta_values[i];
1547 : }
1548 :
1549 : // Distribute points proportional to sin(theta).
1550 : int assigned_points = 0;
1551 271 : for (int i = 0; i < n_theta - 1; ++i)
1552 : {
1553 264 : points_per_level[i] =
1554 264 : static_cast<int>(remaining * sin_theta_values[i] / total_sin_theta + 0.5);
1555 264 : assigned_points += points_per_level[i];
1556 : }
1557 : // Assign remaining points to last ring to ensure exact total.
1558 7 : points_per_level[n_theta - 1] = remaining - assigned_points;
1559 :
1560 278 : for (int i = 1; i <= n_theta; ++i)
1561 : {
1562 : // Ensure equator and XZ plane inclusion.
1563 271 : bool is_equator = (i == (n_theta + 1) / 2);
1564 271 : double theta = is_equator ? M_PI / 2 : std::acos(1.0 - 2.0 * i / (n_theta + 1.0));
1565 271 : int points_in_level = points_per_level[i - 1];
1566 :
1567 65143 : for (int j = 0; j < points_in_level; ++j)
1568 : {
1569 64872 : double phi = 2.0 * M_PI * j / points_in_level;
1570 :
1571 : // Force XZ plane points (phi = 0 or π).
1572 64872 : if (j == 0)
1573 : {
1574 271 : phi = 0.0;
1575 : }
1576 64601 : else if (j == points_in_level / 2)
1577 : {
1578 271 : phi = M_PI;
1579 : }
1580 :
1581 64872 : thetaphis.emplace_back(theta, phi);
1582 : }
1583 : }
1584 : }
1585 :
1586 : if (nsample > 2)
1587 : MFEM_ASSERT(thetaphis.size() == nsample,
1588 : "Sampled number of points is not NSample!");
1589 : }
1590 : }
1591 :
1592 16 : auto thetaphis_json = farfield->find("ThetaPhis");
1593 16 : if (thetaphis_json != farfield->end())
1594 : {
1595 6 : MFEM_VERIFY(thetaphis_json->is_array(),
1596 : "\"ThetaPhis\" should specify an array in the configuration file!");
1597 :
1598 : // JSON does not support the notion of pair, so we read the theta and phis as vectors
1599 : // of vectors, and then cast them to vectors of pairs.
1600 : //
1601 : // Convert to radians in the process.
1602 6 : auto vec_of_vec = thetaphis_json->get<std::vector<std::vector<double>>>();
1603 20 : for (const auto &vec : vec_of_vec)
1604 : {
1605 14 : thetaphis.emplace_back(vec[0] * M_PI / 180, vec[1] * M_PI / 180);
1606 : }
1607 6 : }
1608 :
1609 : // Remove duplicate entries with numerical tolerance.
1610 : constexpr double tol = 1e-6;
1611 16 : std::sort(thetaphis.begin(), thetaphis.end());
1612 16 : auto it = std::unique(thetaphis.begin(), thetaphis.end(),
1613 64890 : [tol](const auto &a, const auto &b)
1614 : {
1615 : // At poles (theta ≈ 0 or π), phi is irrelevant.
1616 64890 : if ((std::abs(a.first) < tol || std::abs(a.first - M_PI) < tol) &&
1617 14 : (std::abs(b.first) < tol || std::abs(b.first - M_PI) < tol))
1618 : {
1619 5 : return std::abs(a.first - b.first) < tol;
1620 : }
1621 :
1622 : // Check direct match.
1623 64885 : if (std::abs(a.first - b.first) < tol)
1624 : {
1625 64603 : double phi_diff = std::abs(a.second - b.second);
1626 64603 : return phi_diff < tol || std::abs(phi_diff - 2.0 * M_PI) < tol;
1627 : }
1628 :
1629 : // Check theta periodicity: (θ, φ) ≡ (π-θ, φ+π).
1630 282 : if (std::abs(a.first - (M_PI - b.first)) < tol)
1631 : {
1632 1 : double phi_diff = std::abs(a.second - (b.second + M_PI));
1633 1 : if (phi_diff > M_PI)
1634 1 : phi_diff = 2.0 * M_PI - phi_diff;
1635 1 : return phi_diff < tol;
1636 : }
1637 :
1638 : return false;
1639 : });
1640 16 : thetaphis.erase(it, thetaphis.end());
1641 :
1642 16 : if (thetaphis.empty())
1643 : {
1644 8 : MFEM_WARNING("No target points specified under farfield \"FarField\"!\n");
1645 : }
1646 :
1647 : // Cleanup
1648 16 : farfield->erase("Attributes");
1649 16 : farfield->erase("NSample");
1650 16 : farfield->erase("ThetaPhis");
1651 32 : MFEM_VERIFY(farfield->empty(),
1652 : "Found an unsupported configuration file keyword under \"FarField\"!\n"
1653 : << farfield->dump(2));
1654 :
1655 : // Debug
1656 : if constexpr (JSON_DEBUG)
1657 : {
1658 : std::cout << "Attributes: " << attributes << '\n';
1659 : std::cout << "NSample: " << nsample << '\n';
1660 : std::cout << "ThetaPhis: " << thetaphis << '\n';
1661 : }
1662 : }
1663 33 : void BoundaryPostData::SetUp(json &boundaries)
1664 : {
1665 33 : auto postpro = boundaries.find("Postprocessing");
1666 33 : if (postpro == boundaries.end())
1667 : {
1668 25 : return;
1669 : }
1670 8 : flux.SetUp(*postpro);
1671 8 : dielectric.SetUp(*postpro);
1672 8 : farfield.SetUp(*postpro);
1673 :
1674 : // Store all unique postprocessing boundary attributes.
1675 24 : for (const auto &[idx, data] : flux)
1676 : {
1677 16 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
1678 : }
1679 32 : for (const auto &[idx, data] : dielectric)
1680 : {
1681 24 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
1682 : }
1683 :
1684 8 : attributes.insert(attributes.end(), farfield.attributes.begin(),
1685 : farfield.attributes.end());
1686 :
1687 8 : std::sort(attributes.begin(), attributes.end());
1688 8 : attributes.erase(std::unique(attributes.begin(), attributes.end()), attributes.end());
1689 : attributes.shrink_to_fit();
1690 :
1691 : // Cleanup
1692 8 : postpro->erase("SurfaceFlux");
1693 8 : postpro->erase("Dielectric");
1694 8 : postpro->erase("FarField");
1695 16 : MFEM_VERIFY(postpro->empty(),
1696 : "Found an unsupported configuration file keyword under \"Postprocessing\"!\n"
1697 : << postpro->dump(2));
1698 : }
1699 :
1700 39 : void BoundaryData::SetUp(json &config)
1701 : {
1702 39 : auto boundaries = config.find("Boundaries");
1703 39 : MFEM_VERIFY(boundaries != config.end(),
1704 : "\"Boundaries\" must be specified in the configuration file!");
1705 39 : pec.SetUp(*boundaries);
1706 39 : pmc.SetUp(*boundaries);
1707 39 : auxpec.SetUp(*boundaries);
1708 39 : farfield.SetUp(*boundaries);
1709 39 : conductivity.SetUp(*boundaries);
1710 39 : impedance.SetUp(*boundaries);
1711 39 : lumpedport.SetUp(*boundaries);
1712 36 : periodic.SetUp(*boundaries);
1713 36 : waveport.SetUp(*boundaries);
1714 33 : current.SetUp(*boundaries);
1715 33 : postpro.SetUp(*boundaries);
1716 :
1717 : // Ensure unique indexing of lumpedport, waveport, current.
1718 : {
1719 : std::map<int, std::string> index_map;
1720 : std::map<int, std::vector<int>> excitation_map;
1721 36 : const std::string lumpedport_str = "\"LumpedPort\"";
1722 36 : const std::string waveport_str = "WavePort";
1723 36 : const std::string current_str = "SurfaceCurrent";
1724 :
1725 90 : for (const auto &data : lumpedport)
1726 : {
1727 60 : auto result = index_map.insert({data.first, lumpedport_str});
1728 57 : MFEM_VERIFY(result.second, "Duplicate \"Index\": " << data.first << " in "
1729 : << index_map[data.first] << "!");
1730 57 : excitation_map[data.second.excitation].emplace_back(data.first);
1731 : }
1732 49 : for (const auto &data : waveport)
1733 : {
1734 20 : auto result = index_map.insert({data.first, waveport_str});
1735 22 : MFEM_VERIFY(result.second, "Duplicate \"Index\": " << data.first << " in "
1736 : << index_map[data.first] << "!");
1737 16 : excitation_map[data.second.excitation].emplace_back(data.first);
1738 : }
1739 32 : for (const auto &data : current)
1740 : {
1741 3 : auto result = index_map.insert({data.first, current_str});
1742 0 : MFEM_VERIFY(result.second, "Duplicate \"Index\": " << data.first << " in "
1743 : << index_map[data.first] << "!");
1744 : }
1745 : // Typical usecase: If each excitation is simple, S-parameters will be calculated.
1746 : // If there were multiple excitations specified, check their indices match the
1747 : // port indices. If there was only one, assign it.
1748 32 : excitation_map.erase(0); // zeroth index is unexcited.
1749 : bool calc_s_params = std::all_of(excitation_map.begin(), excitation_map.end(),
1750 : [](const auto &x) { return x.second.size() == 1; });
1751 32 : if (calc_s_params && !excitation_map.empty())
1752 : {
1753 : // If there's one excitation, needs to be 1 (set with bool) or the port index.
1754 : const auto &ext1 = *excitation_map.begin();
1755 42 : MFEM_VERIFY(
1756 : (excitation_map.size() == 1 &&
1757 : (ext1.first == 1 || ext1.second[0] == ext1.first)) ||
1758 : std::all_of(excitation_map.begin(), excitation_map.end(),
1759 : [](const auto &x) { return x.first == x.second[0]; }),
1760 : "\"Excitation\" must match \"Index\" for single ports to avoid ambiguity!");
1761 :
1762 71 : for (auto &[port_idx, lp] : lumpedport)
1763 : {
1764 45 : if (lp.excitation == 1)
1765 : {
1766 25 : lp.excitation = port_idx;
1767 : }
1768 : }
1769 32 : for (auto &[port_idx, wp] : waveport)
1770 : {
1771 6 : if (wp.excitation == 1)
1772 : {
1773 1 : wp.excitation = port_idx;
1774 : }
1775 : }
1776 : }
1777 : }
1778 :
1779 : // Store all unique boundary attributes.
1780 30 : attributes.insert(attributes.end(), pec.attributes.begin(), pec.attributes.end());
1781 30 : attributes.insert(attributes.end(), pmc.attributes.begin(), pmc.attributes.end());
1782 30 : attributes.insert(attributes.end(), auxpec.attributes.begin(), auxpec.attributes.end());
1783 30 : attributes.insert(attributes.end(), farfield.attributes.begin(),
1784 : farfield.attributes.end());
1785 30 : for (const auto &data : conductivity)
1786 : {
1787 0 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
1788 : }
1789 30 : for (const auto &data : impedance)
1790 : {
1791 0 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
1792 : }
1793 83 : for (const auto &[idx, data] : lumpedport)
1794 : {
1795 108 : for (const auto &elem : data.elements)
1796 : {
1797 55 : attributes.insert(attributes.end(), elem.attributes.begin(), elem.attributes.end());
1798 : }
1799 : }
1800 44 : for (const auto &[idx, data] : waveport)
1801 : {
1802 14 : attributes.insert(attributes.end(), data.attributes.begin(), data.attributes.end());
1803 : }
1804 30 : for (const auto &[idx, data] : current)
1805 : {
1806 0 : for (const auto &elem : data.elements)
1807 : {
1808 0 : attributes.insert(attributes.end(), elem.attributes.begin(), elem.attributes.end());
1809 : }
1810 : }
1811 30 : std::sort(attributes.begin(), attributes.end());
1812 30 : attributes.erase(std::unique(attributes.begin(), attributes.end()), attributes.end());
1813 : attributes.shrink_to_fit();
1814 :
1815 : // Cleanup
1816 30 : boundaries->erase("PEC");
1817 30 : boundaries->erase("PMC");
1818 30 : boundaries->erase("WavePortPEC");
1819 30 : boundaries->erase("Absorbing");
1820 30 : boundaries->erase("Conductivity");
1821 30 : boundaries->erase("Impedance");
1822 30 : boundaries->erase("LumpedPort");
1823 30 : boundaries->erase("Periodic");
1824 30 : boundaries->erase("FloquetWaveVector");
1825 30 : boundaries->erase("WavePort");
1826 30 : boundaries->erase("SurfaceCurrent");
1827 30 : boundaries->erase("Ground");
1828 30 : boundaries->erase("ZeroCharge");
1829 30 : boundaries->erase("Terminal");
1830 30 : boundaries->erase("Postprocessing");
1831 60 : MFEM_VERIFY(boundaries->empty(),
1832 : "Found an unsupported configuration file keyword under \"Boundaries\"!\n"
1833 : << boundaries->dump(2));
1834 30 : }
1835 :
1836 11 : std::vector<double> ConstructLinearRange(double start, double end, double delta)
1837 : {
1838 11 : auto n_step = GetNumSteps(start, end, delta);
1839 11 : std::vector<double> f(n_step);
1840 : std::iota(f.begin(), f.end(), 0);
1841 81 : std::for_each(f.begin(), f.end(), [=](double &x) { x = start + x * delta; });
1842 11 : return f;
1843 : }
1844 5 : std::vector<double> ConstructLinearRange(double start, double end, int n_sample)
1845 : {
1846 5 : std::vector<double> f(n_sample);
1847 44 : for (int i = 0; i < n_sample; i++)
1848 : {
1849 39 : f[i] = start + (double(i) / (n_sample - 1)) * (end - start);
1850 : }
1851 5 : return f;
1852 : }
1853 2 : std::vector<double> ConstructLogRange(double start, double end, int n_sample)
1854 : {
1855 5 : MFEM_VERIFY(start > 0 && end > 0,
1856 : "\"Type\": \"Log\" only valid for non-zero start and end!");
1857 1 : std::vector<double> f(n_sample);
1858 1 : double log_start = std::log10(start);
1859 1 : double log_end = std::log10(end);
1860 6 : for (int i = 0; i < n_sample; i++)
1861 : {
1862 5 : double log_val = log_start + (double(i) / (n_sample - 1)) * (log_end - log_start);
1863 5 : f[i] = std::pow(10.0, log_val);
1864 : }
1865 1 : return f;
1866 : }
1867 :
1868 : // Helper to find entry closest to x in vec, up to tol. If no match, returns end.
1869 4 : auto FindNearestValue(const std::vector<double> &vec, double x, double tol)
1870 : {
1871 : // Find the first element not less than x.
1872 : auto it = std::lower_bound(vec.begin(), vec.end(), x);
1873 : // Check if we found an exact match or a close enough value.
1874 4 : if (it != vec.end() && std::abs(*it - x) <= tol)
1875 : {
1876 3 : return it;
1877 : }
1878 : // If we're not at the beginning, check the previous element too.
1879 1 : if (it != vec.begin())
1880 : {
1881 : auto prev = std::prev(it);
1882 1 : if (std::abs(*prev - x) <= tol)
1883 : {
1884 0 : return prev;
1885 : }
1886 : }
1887 : // No value within tol found
1888 : return vec.end();
1889 : }
1890 :
1891 20 : void DrivenSolverData::SetUp(json &solver)
1892 : {
1893 20 : auto driven = solver.find("Driven");
1894 20 : if (driven == solver.end())
1895 : {
1896 0 : return;
1897 : }
1898 :
1899 20 : restart = driven->value("Restart", restart);
1900 20 : adaptive_tol = driven->value("AdaptiveTol", adaptive_tol);
1901 20 : adaptive_max_size = driven->value("AdaptiveMaxSamples", adaptive_max_size);
1902 20 : adaptive_memory = driven->value("AdaptiveConvergenceMemory", adaptive_memory);
1903 :
1904 20 : MFEM_VERIFY(!(restart != 1 && adaptive_tol > 0.0),
1905 : "\"Restart\" is incompatible with adaptive frequency sweep!");
1906 :
1907 : std::vector<double> save_f, prom_f; // samples to be saved to paraview and added to prom
1908 : // Backwards compatible top level interface.
1909 22 : if (driven->find("MinFreq") != driven->end() &&
1910 22 : driven->find("MaxFreq") != driven->end() && driven->find("FreqStep") != driven->end())
1911 : {
1912 2 : double min_f = driven->at("MinFreq"); // Required
1913 2 : double max_f = driven->at("MaxFreq"); // Required
1914 2 : double delta_f = driven->at("FreqStep"); // Required
1915 2 : sample_f = ConstructLinearRange(min_f, max_f, delta_f);
1916 2 : if (int save_step = driven->value("SaveStep", 0); save_step > 0)
1917 : {
1918 14 : for (std::size_t n = 0; n < sample_f.size(); n += save_step)
1919 : {
1920 12 : save_f.emplace_back(sample_f[n]);
1921 : }
1922 : }
1923 : }
1924 20 : if (auto freq_samples = driven->find("Samples"); freq_samples != driven->end())
1925 : {
1926 72 : for (auto &r : *freq_samples)
1927 : {
1928 40 : auto type = r.value("Type", r.find("Freq") != r.end() ? FrequencySampling::POINT
1929 22 : : FrequencySampling::DEFAULT);
1930 68 : auto f = [&]()
1931 : {
1932 22 : switch (type)
1933 : {
1934 15 : case FrequencySampling::LINEAR:
1935 : {
1936 15 : auto min_f = r.at("MinFreq");
1937 14 : auto max_f = r.at("MaxFreq");
1938 14 : auto delta_f = r.value("FreqStep", 0.0);
1939 14 : auto n_sample = r.value("NSample", 0);
1940 14 : MFEM_VERIFY(delta_f > 0 ^ n_sample > 0,
1941 : "Only one of \"FreqStep\" or \"NSample\" can be specified for "
1942 : "\"Type\": \"Linear\"!");
1943 14 : if (delta_f > 0)
1944 : {
1945 9 : return ConstructLinearRange(min_f, max_f, delta_f);
1946 : }
1947 5 : if (n_sample > 0)
1948 : {
1949 5 : return ConstructLinearRange(min_f, max_f, n_sample);
1950 : }
1951 : }
1952 : case FrequencySampling::LOG:
1953 : {
1954 3 : auto min_f = r.at("MinFreq");
1955 3 : auto max_f = r.at("MaxFreq");
1956 3 : auto n_sample = r.at("NSample");
1957 2 : return ConstructLogRange(min_f, max_f, n_sample);
1958 : }
1959 4 : case FrequencySampling::POINT:
1960 4 : return r.at("Freq").get<std::vector<double>>();
1961 : }
1962 0 : return std::vector<double>{};
1963 22 : }();
1964 18 : sample_f.insert(sample_f.end(), f.begin(), f.end());
1965 :
1966 18 : if (auto save_step = r.value("SaveStep", 0); save_step > 0)
1967 : {
1968 52 : for (std::size_t n = 0; n < f.size(); n += save_step)
1969 : {
1970 42 : save_f.emplace_back(f[n]);
1971 : }
1972 : }
1973 18 : if (auto prom_sample = r.value("AddToPROM", false); prom_sample)
1974 : {
1975 3 : if (adaptive_tol == 0)
1976 : {
1977 0 : MFEM_WARNING("Ignoring \"AddToPROM\" for non-adaptive simulation!");
1978 : }
1979 3 : prom_f.insert(prom_f.end(), f.begin(), f.end());
1980 : }
1981 :
1982 : // Debug
1983 : if constexpr (JSON_DEBUG)
1984 : {
1985 : std::cout << "Type: " << type << '\n';
1986 : std::cout << "Freq: " << f << '\n';
1987 : std::cout << "FreqStep: " << r.value("FreqStep", 0.0) << '\n';
1988 : std::cout << "NSample: " << r.value("NSample", 0.0) << '\n';
1989 : std::cout << "SaveStep: " << r.value("SaveStep", 0) << '\n';
1990 : std::cout << "AddToPROM: " << r.value("AddToPROM", false) << '\n';
1991 : }
1992 :
1993 : // Cleanup
1994 : r.erase("Type");
1995 : r.erase("MinFreq");
1996 : r.erase("MaxFreq");
1997 : r.erase("FreqStep");
1998 : r.erase("NSample");
1999 : r.erase("Freq");
2000 : r.erase("SaveStep");
2001 : r.erase("AddToPROM");
2002 18 : MFEM_VERIFY(r.empty(),
2003 : "Found an unsupported configuration file keyword in \"Samples\"!\n"
2004 : << r.dump(2));
2005 : }
2006 : }
2007 :
2008 : // Deduplicate all samples, and find indices of save and prom samples.
2009 : constexpr double delta_eps = 1.0e-9; // Precision in frequency comparisons (Hz)
2010 165 : auto equal_f = [=](auto x, auto y) { return std::abs(x - y) < delta_eps; };
2011 46 : auto deduplicate = [&equal_f](auto &f)
2012 : {
2013 46 : std::sort(f.begin(), f.end());
2014 46 : f.erase(std::unique(f.begin(), f.end(), equal_f), f.end());
2015 62 : };
2016 :
2017 : // Enforce explicit saves exactly match the sample frequencies.
2018 16 : deduplicate(sample_f);
2019 38 : auto explicit_save_f = driven->value("Save", std::vector<double>());
2020 19 : for (auto &f : explicit_save_f)
2021 : {
2022 4 : auto it = FindNearestValue(sample_f, f, delta_eps);
2023 8 : MFEM_VERIFY(it != sample_f.end(),
2024 : "Entry " << f << " in \"Save\" must be an explicitly sampled frequency!");
2025 3 : f = *it;
2026 : }
2027 15 : save_f.insert(save_f.end(), explicit_save_f.begin(), explicit_save_f.end());
2028 15 : deduplicate(save_f);
2029 15 : deduplicate(prom_f);
2030 :
2031 : // Given the matched ordering, and values are assigned by copying, can do a
2032 : // paired-iterator scan.
2033 53 : for (auto it_sample = sample_f.begin(), it_save = save_f.begin(); it_save != save_f.end();
2034 : ++it_save)
2035 : {
2036 92 : while (*it_sample != *it_save) // safe because save samples is a subset of samples
2037 : {
2038 : ++it_sample;
2039 54 : MFEM_VERIFY(it_sample != sample_f.end(),
2040 : "Save frequency " << *it_save << " not found in sample frequencies!");
2041 : }
2042 38 : save_indices.emplace_back(std::distance(sample_f.begin(), it_sample));
2043 : }
2044 : // PROM sampling always begins with the minimum and maximum frequencies. Exclude them from
2045 : // extra samples. Can use equality comparison given no floating point operations have been
2046 : // done.
2047 30 : prom_indices = {0, sample_f.size() - 1};
2048 15 : if (prom_f.size() > 0 && prom_f.back() == sample_f.back())
2049 : {
2050 : prom_f.pop_back();
2051 : }
2052 15 : if (prom_f.size() > 0 && prom_f.front() == sample_f.front())
2053 : {
2054 : prom_f.erase(prom_f.begin(), std::next(prom_f.begin()));
2055 : }
2056 21 : for (auto it_sample = sample_f.begin(), it_prom = prom_f.begin(); it_prom != prom_f.end();
2057 : ++it_prom)
2058 : {
2059 18 : while (*it_sample != *it_prom) // safe because prom samples is a subset of samples
2060 : {
2061 : ++it_sample;
2062 12 : MFEM_VERIFY(it_sample != sample_f.end(), "PROM sample frequency "
2063 : << *it_prom
2064 : << " not found in sample frequencies!");
2065 : }
2066 8 : prom_indices.emplace_back(std::distance(sample_f.begin(), it_sample));
2067 : }
2068 :
2069 18 : MFEM_VERIFY(!sample_f.empty(), "No sample frequency samples specified in \"Driven\"!");
2070 :
2071 : // Debug
2072 : if constexpr (JSON_DEBUG)
2073 : {
2074 : std::cout << "MinFreq: " << driven->value("MinFreq", 0.0) << '\n';
2075 : std::cout << "MaxFreq: " << driven->value("MaxFreq", 0.0) << '\n';
2076 : std::cout << "FreqStep: " << driven->value("FreqStep", 0) << '\n';
2077 : std::cout << "Samples: " << sample_f << '\n';
2078 : std::cout << "SaveSamples: " << save_f << '\n';
2079 : std::cout << "PROMSamples: " << prom_f << '\n';
2080 : std::cout << "SaveIndices: " << save_indices << '\n';
2081 : std::cout << "PromIndices: " << prom_indices << '\n';
2082 : std::cout << "Restart: " << restart << '\n';
2083 : std::cout << "AdaptiveTol: " << adaptive_tol << '\n';
2084 : std::cout << "AdaptiveMaxSamples: " << adaptive_max_size << '\n';
2085 : std::cout << "AdaptiveConvergenceMemory: " << adaptive_memory << '\n';
2086 : }
2087 :
2088 : // Cleanup
2089 14 : driven->erase("MinFreq");
2090 14 : driven->erase("MaxFreq");
2091 14 : driven->erase("FreqStep");
2092 14 : driven->erase("Samples");
2093 14 : driven->erase("Save");
2094 14 : driven->erase("SaveStep");
2095 14 : driven->erase("Restart");
2096 14 : driven->erase("AdaptiveTol");
2097 14 : driven->erase("AdaptiveMaxSamples");
2098 14 : driven->erase("AdaptiveConvergenceMemory");
2099 28 : MFEM_VERIFY(driven->empty(),
2100 : "Found an unsupported configuration file keyword under \"Driven\"!\n"
2101 : << driven->dump(2));
2102 : }
2103 :
2104 8 : void EigenSolverData::SetUp(json &solver)
2105 : {
2106 8 : auto eigenmode = solver.find("Eigenmode");
2107 8 : if (eigenmode == solver.end())
2108 : {
2109 8 : return;
2110 : }
2111 0 : MFEM_VERIFY(eigenmode->find("Target") != eigenmode->end() ||
2112 : solver.find("Driven") != solver.end(),
2113 : "Missing \"Eigenmode\" solver \"Target\" in the configuration file!");
2114 0 : target = eigenmode->value("Target", target); // Required (only for eigenmode simulations)
2115 0 : tol = eigenmode->value("Tol", tol);
2116 0 : max_it = eigenmode->value("MaxIts", max_it);
2117 0 : max_size = eigenmode->value("MaxSize", max_size);
2118 0 : n = eigenmode->value("N", n);
2119 0 : n_post = eigenmode->value("Save", n_post);
2120 0 : type = eigenmode->value("Type", type);
2121 0 : pep_linear = eigenmode->value("PEPLinear", pep_linear);
2122 0 : scale = eigenmode->value("Scaling", scale);
2123 0 : init_v0 = eigenmode->value("StartVector", init_v0);
2124 0 : init_v0_const = eigenmode->value("StartVectorConstant", init_v0_const);
2125 0 : mass_orthog = eigenmode->value("MassOrthogonal", mass_orthog);
2126 0 : nonlinear_type = eigenmode->value("NonlinearType", nonlinear_type);
2127 0 : refine_nonlinear = eigenmode->value("RefineNonlinear", refine_nonlinear);
2128 0 : linear_tol = eigenmode->value("LinearTol", linear_tol);
2129 0 : target_upper = eigenmode->value("TargetUpper", target_upper);
2130 0 : preconditioner_lag = eigenmode->value("PreconditionerLag", preconditioner_lag);
2131 0 : preconditioner_lag_tol = eigenmode->value("PreconditionerLagTol", preconditioner_lag_tol);
2132 0 : max_restart = eigenmode->value("MaxRestart", max_restart);
2133 :
2134 0 : target_upper = (target_upper < 0) ? 3 * target : target_upper; // default = 3 * target
2135 0 : MFEM_VERIFY(target > 0.0, "config[\"Eigenmode\"][\"Target\"] must be strictly positive!");
2136 0 : MFEM_VERIFY(target_upper > target, "config[\"Eigenmode\"][\"TargetUpper\"] must be "
2137 : "greater than config[\"Eigenmode\"][\"Target\"]!");
2138 0 : MFEM_VERIFY(preconditioner_lag >= 0,
2139 : "config[\"Eigenmode\"][\"PreconditionerLag\"] must be non-negative!");
2140 0 : MFEM_VERIFY(preconditioner_lag_tol >= 0,
2141 : "config[\"Eigenmode\"][\"PreconditionerLagTol\"] must be non-negative!");
2142 0 : MFEM_VERIFY(max_restart >= 0,
2143 : "config[\"Eigenmode\"][\"MaxRestart\"] must be non-negative!");
2144 :
2145 0 : MFEM_VERIFY(n > 0, "\"N\" must be greater than 0!");
2146 :
2147 : // Cleanup
2148 0 : eigenmode->erase("Target");
2149 0 : eigenmode->erase("Tol");
2150 0 : eigenmode->erase("MaxIts");
2151 0 : eigenmode->erase("MaxSize");
2152 0 : eigenmode->erase("N");
2153 0 : eigenmode->erase("Save");
2154 0 : eigenmode->erase("Type");
2155 0 : eigenmode->erase("PEPLinear");
2156 0 : eigenmode->erase("Scaling");
2157 0 : eigenmode->erase("StartVector");
2158 0 : eigenmode->erase("StartVectorConstant");
2159 0 : eigenmode->erase("MassOrthogonal");
2160 0 : eigenmode->erase("NonlinearType");
2161 0 : eigenmode->erase("RefineNonlinear");
2162 0 : eigenmode->erase("LinearTol");
2163 0 : eigenmode->erase("TargetUpper");
2164 0 : eigenmode->erase("PreconditionerLag");
2165 0 : eigenmode->erase("PreconditionerLagTol");
2166 0 : eigenmode->erase("MaxRestart");
2167 0 : MFEM_VERIFY(eigenmode->empty(),
2168 : "Found an unsupported configuration file keyword under \"Eigenmode\"!\n"
2169 : << eigenmode->dump(2));
2170 :
2171 : // Debug
2172 : if constexpr (JSON_DEBUG)
2173 : {
2174 : std::cout << "Target: " << target << '\n';
2175 : std::cout << "Tol: " << tol << '\n';
2176 : std::cout << "MaxIts: " << max_it << '\n';
2177 : std::cout << "MaxSize: " << max_size << '\n';
2178 : std::cout << "N: " << n << '\n';
2179 : std::cout << "Save: " << n_post << '\n';
2180 : std::cout << "Type: " << type << '\n';
2181 : std::cout << "PEPLinear: " << pep_linear << '\n';
2182 : std::cout << "Scaling: " << scale << '\n';
2183 : std::cout << "StartVector: " << init_v0 << '\n';
2184 : std::cout << "StartVectorConstant: " << init_v0_const << '\n';
2185 : std::cout << "MassOrthogonal: " << mass_orthog << '\n';
2186 : std::cout << "NonlinearType: " << nonlinear_type << '\n';
2187 : std::cout << "RefineNonlinear: " << refine_nonlinear << '\n';
2188 : std::cout << "LinearTol: " << linear_tol << '\n';
2189 : std::cout << "TargetUpper: " << target_upper << '\n';
2190 : std::cout << "PreconditionerLag: " << preconditioner_lag << '\n';
2191 : std::cout << "PreconditionerLagTol: " << preconditioner_lag_tol << '\n';
2192 : std::cout << "MaxRestart: " << max_restart << '\n';
2193 : }
2194 : }
2195 :
2196 8 : void ElectrostaticSolverData::SetUp(json &solver)
2197 : {
2198 8 : auto electrostatic = solver.find("Electrostatic");
2199 8 : if (electrostatic == solver.end())
2200 : {
2201 8 : return;
2202 : }
2203 0 : n_post = electrostatic->value("Save", n_post);
2204 :
2205 : // Cleanup
2206 0 : electrostatic->erase("Save");
2207 0 : MFEM_VERIFY(electrostatic->empty(),
2208 : "Found an unsupported configuration file keyword under \"Electrostatic\"!\n"
2209 : << electrostatic->dump(2));
2210 :
2211 : // Debug
2212 : if constexpr (JSON_DEBUG)
2213 : {
2214 : std::cout << "Save: " << n_post << '\n';
2215 : }
2216 : }
2217 :
2218 8 : void MagnetostaticSolverData::SetUp(json &solver)
2219 : {
2220 8 : auto magnetostatic = solver.find("Magnetostatic");
2221 8 : if (magnetostatic == solver.end())
2222 : {
2223 8 : return;
2224 : }
2225 0 : n_post = magnetostatic->value("Save", n_post);
2226 :
2227 : // Cleanup
2228 0 : magnetostatic->erase("Save");
2229 0 : MFEM_VERIFY(magnetostatic->empty(),
2230 : "Found an unsupported configuration file keyword under \"Magnetostatic\"!\n"
2231 : << magnetostatic->dump(2));
2232 :
2233 : // Debug
2234 : if constexpr (JSON_DEBUG)
2235 : {
2236 : std::cout << "Save: " << n_post << '\n';
2237 : }
2238 : }
2239 :
2240 8 : void TransientSolverData::SetUp(json &solver)
2241 : {
2242 8 : auto transient = solver.find("Transient");
2243 8 : if (transient == solver.end())
2244 : {
2245 8 : return;
2246 : }
2247 0 : MFEM_VERIFY(
2248 : transient->find("Excitation") != transient->end(),
2249 : "Missing \"Transient\" solver \"Excitation\" type in the configuration file!");
2250 0 : MFEM_VERIFY(transient->find("MaxTime") != transient->end() &&
2251 : transient->find("TimeStep") != transient->end(),
2252 : "Missing \"Transient\" solver \"MaxTime\" or \"TimeStep\" in the "
2253 : "configuration file!");
2254 0 : type = transient->value("Type", type);
2255 0 : excitation = transient->at("Excitation"); // Required
2256 0 : pulse_f = transient->value("ExcitationFreq", pulse_f);
2257 0 : pulse_tau = transient->value("ExcitationWidth", pulse_tau);
2258 0 : max_t = transient->at("MaxTime"); // Required
2259 0 : delta_t = transient->at("TimeStep"); // Required
2260 0 : delta_post = transient->value("SaveStep", delta_post);
2261 0 : order = transient->value("Order", order);
2262 0 : rel_tol = transient->value("RelTol", rel_tol);
2263 0 : abs_tol = transient->value("AbsTol", abs_tol);
2264 0 : MFEM_VERIFY(delta_t > 0, "\"TimeStep\" must be greater than 0.0!");
2265 :
2266 0 : if (type == TimeSteppingScheme::GEN_ALPHA || type == TimeSteppingScheme::RUNGE_KUTTA)
2267 : {
2268 0 : if (transient->contains("Order"))
2269 : {
2270 0 : MFEM_WARNING("GeneralizedAlpha and RungeKutta transient solvers do not use "
2271 : "config[\"Transient\"][\"Order\"]!");
2272 : }
2273 0 : if (transient->contains("RelTol") || transient->contains("AbsTol"))
2274 : {
2275 0 : MFEM_WARNING(
2276 : "GeneralizedAlpha and RungeKutta transient solvers do not use\n"
2277 : "config[\"Transient\"][\"RelTol\"] and config[\"Transient\"][\"AbsTol\"]!");
2278 : }
2279 : }
2280 : else
2281 : {
2282 0 : MFEM_VERIFY(rel_tol > 0,
2283 : "config[\"Transient\"][\"RelTol\"] must be strictly positive!");
2284 0 : MFEM_VERIFY(abs_tol > 0,
2285 : "config[\"Transient\"][\"AbsTol\"] must be strictly positive!");
2286 0 : MFEM_VERIFY(order >= 2 && order <= 5,
2287 : "config[\"Transient\"][\"Order\"] must be between 2 and 5!");
2288 : }
2289 :
2290 : // Cleanup
2291 0 : transient->erase("Type");
2292 0 : transient->erase("Excitation");
2293 0 : transient->erase("ExcitationFreq");
2294 0 : transient->erase("ExcitationWidth");
2295 0 : transient->erase("MaxTime");
2296 0 : transient->erase("TimeStep");
2297 0 : transient->erase("SaveStep");
2298 0 : transient->erase("Order");
2299 0 : transient->erase("RelTol");
2300 0 : transient->erase("AbsTol");
2301 0 : MFEM_VERIFY(transient->empty(),
2302 : "Found an unsupported configuration file keyword under \"Transient\"!\n"
2303 : << transient->dump(2));
2304 :
2305 : // Debug
2306 : if constexpr (JSON_DEBUG)
2307 : {
2308 : std::cout << "Type: " << type << '\n';
2309 : std::cout << "Excitation: " << excitation << '\n';
2310 : std::cout << "ExcitationFreq: " << pulse_f << '\n';
2311 : std::cout << "ExcitationWidth: " << pulse_tau << '\n';
2312 : std::cout << "MaxTime: " << max_t << '\n';
2313 : std::cout << "TimeStep: " << delta_t << '\n';
2314 : std::cout << "SaveStep: " << delta_post << '\n';
2315 : std::cout << "Order: " << order << '\n';
2316 : std::cout << "RelTol: " << rel_tol << '\n';
2317 : std::cout << "AbsTol: " << abs_tol << '\n';
2318 : }
2319 : }
2320 :
2321 8 : void LinearSolverData::SetUp(json &solver)
2322 : {
2323 8 : auto linear = solver.find("Linear");
2324 8 : if (linear == solver.end())
2325 : {
2326 0 : return;
2327 : }
2328 8 : type = linear->value("Type", type);
2329 8 : krylov_solver = linear->value("KSPType", krylov_solver);
2330 8 : tol = linear->value("Tol", tol);
2331 8 : max_it = linear->value("MaxIts", max_it);
2332 8 : max_size = linear->value("MaxSize", max_size);
2333 8 : initial_guess = linear->value("InitialGuess", initial_guess);
2334 :
2335 : // Options related to multigrid.
2336 8 : mg_max_levels = linear->value("MGMaxLevels", mg_max_levels);
2337 8 : mg_coarsening = linear->value("MGCoarsenType", mg_coarsening);
2338 8 : mg_use_mesh = linear->value("MGUseMesh", mg_use_mesh);
2339 8 : mg_cycle_it = linear->value("MGCycleIts", mg_cycle_it);
2340 8 : mg_smooth_aux = linear->value("MGAuxiliarySmoother", mg_smooth_aux);
2341 8 : mg_smooth_it = linear->value("MGSmoothIts", mg_smooth_it);
2342 8 : mg_smooth_order = linear->value("MGSmoothOrder", mg_smooth_order);
2343 8 : mg_smooth_sf_max = linear->value("MGSmoothEigScaleMax", mg_smooth_sf_max);
2344 8 : mg_smooth_sf_min = linear->value("MGSmoothEigScaleMin", mg_smooth_sf_min);
2345 8 : mg_smooth_cheby_4th = linear->value("MGSmoothChebyshev4th", mg_smooth_cheby_4th);
2346 :
2347 : // Preconditioner-specific options.
2348 8 : pc_mat_real = linear->value("PCMatReal", pc_mat_real);
2349 8 : pc_mat_shifted = linear->value("PCMatShifted", pc_mat_shifted);
2350 8 : complex_coarse_solve = linear->value("ComplexCoarseSolve", complex_coarse_solve);
2351 8 : pc_side = linear->value("PCSide", pc_side);
2352 8 : sym_factorization = linear->value("ColumnOrdering", sym_factorization);
2353 8 : strumpack_compression_type =
2354 8 : linear->value("STRUMPACKCompressionType", strumpack_compression_type);
2355 8 : strumpack_lr_tol = linear->value("STRUMPACKCompressionTol", strumpack_lr_tol);
2356 8 : strumpack_lossy_precision =
2357 8 : linear->value("STRUMPACKLossyPrecision", strumpack_lossy_precision);
2358 8 : strumpack_butterfly_l = linear->value("STRUMPACKButterflyLevels", strumpack_butterfly_l);
2359 8 : superlu_3d = linear->value("SuperLU3DCommunicator", superlu_3d);
2360 8 : ams_vector_interp = linear->value("AMSVectorInterpolation", ams_vector_interp);
2361 8 : ams_singular_op = linear->value("AMSSingularOperator", ams_singular_op);
2362 8 : amg_agg_coarsen = linear->value("AMGAggressiveCoarsening", amg_agg_coarsen);
2363 :
2364 : // Other linear solver options.
2365 8 : divfree_tol = linear->value("DivFreeTol", divfree_tol);
2366 8 : divfree_max_it = linear->value("DivFreeMaxIts", divfree_max_it);
2367 8 : estimator_tol = linear->value("EstimatorTol", estimator_tol);
2368 8 : estimator_max_it = linear->value("EstimatorMaxIts", estimator_max_it);
2369 8 : estimator_mg = linear->value("EstimatorMG", estimator_mg);
2370 8 : gs_orthog = linear->value("GSOrthogonalization", gs_orthog);
2371 :
2372 : // Cleanup
2373 8 : linear->erase("Type");
2374 8 : linear->erase("KSPType");
2375 8 : linear->erase("Tol");
2376 8 : linear->erase("MaxIts");
2377 8 : linear->erase("MaxSize");
2378 8 : linear->erase("InitialGuess");
2379 :
2380 8 : linear->erase("MGMaxLevels");
2381 8 : linear->erase("MGCoarsenType");
2382 8 : linear->erase("MGUseMesh");
2383 8 : linear->erase("MGCycleIts");
2384 8 : linear->erase("MGAuxiliarySmoother");
2385 8 : linear->erase("MGSmoothIts");
2386 8 : linear->erase("MGSmoothOrder");
2387 8 : linear->erase("MGSmoothEigScaleMax");
2388 8 : linear->erase("MGSmoothEigScaleMin");
2389 8 : linear->erase("MGSmoothChebyshev4th");
2390 :
2391 8 : linear->erase("PCMatReal");
2392 8 : linear->erase("PCMatShifted");
2393 8 : linear->erase("ComplexCoarseSolve");
2394 8 : linear->erase("PCSide");
2395 8 : linear->erase("ColumnOrdering");
2396 8 : linear->erase("STRUMPACKCompressionType");
2397 8 : linear->erase("STRUMPACKCompressionTol");
2398 8 : linear->erase("STRUMPACKLossyPrecision");
2399 8 : linear->erase("STRUMPACKButterflyLevels");
2400 8 : linear->erase("SuperLU3DCommunicator");
2401 8 : linear->erase("AMSVectorInterpolation");
2402 8 : linear->erase("AMSSingularOperator");
2403 8 : linear->erase("AMGAggressiveCoarsening");
2404 :
2405 8 : linear->erase("DivFreeTol");
2406 8 : linear->erase("DivFreeMaxIts");
2407 8 : linear->erase("EstimatorTol");
2408 8 : linear->erase("EstimatorMaxIts");
2409 8 : linear->erase("EstimatorMG");
2410 8 : linear->erase("GSOrthogonalization");
2411 16 : MFEM_VERIFY(linear->empty(),
2412 : "Found an unsupported configuration file keyword under \"Linear\"!\n"
2413 : << linear->dump(2));
2414 :
2415 : // Debug
2416 : if constexpr (JSON_DEBUG)
2417 : {
2418 : std::cout << "Type: " << type << '\n';
2419 : std::cout << "KSPType: " << krylov_solver << '\n';
2420 : std::cout << "Tol: " << tol << '\n';
2421 : std::cout << "MaxIts: " << max_it << '\n';
2422 : std::cout << "MaxSize: " << max_size << '\n';
2423 : std::cout << "InitialGuess: " << initial_guess << '\n';
2424 :
2425 : std::cout << "MGMaxLevels: " << mg_max_levels << '\n';
2426 : std::cout << "MGCoarsenType: " << mg_coarsening << '\n';
2427 : std::cout << "MGUseMesh: " << mg_use_mesh << '\n';
2428 : std::cout << "MGCycleIts: " << mg_cycle_it << '\n';
2429 : std::cout << "MGAuxiliarySmoother: " << mg_smooth_aux << '\n';
2430 : std::cout << "MGSmoothIts: " << mg_smooth_it << '\n';
2431 : std::cout << "MGSmoothOrder: " << mg_smooth_order << '\n';
2432 : std::cout << "MGSmoothEigScaleMax: " << mg_smooth_sf_max << '\n';
2433 : std::cout << "MGSmoothEigScaleMin: " << mg_smooth_sf_min << '\n';
2434 : std::cout << "MGSmoothChebyshev4th: " << mg_smooth_cheby_4th << '\n';
2435 :
2436 : std::cout << "PCMatReal: " << pc_mat_real << '\n';
2437 : std::cout << "PCMatShifted: " << pc_mat_shifted << '\n';
2438 : std::cout << "ComplexCoarseSolve: " << complex_coarse_solve << '\n';
2439 : std::cout << "PCSide: " << pc_side << '\n';
2440 : std::cout << "ColumnOrdering: " << sym_factorization << '\n';
2441 : std::cout << "STRUMPACKCompressionType: " << strumpack_compression_type << '\n';
2442 : std::cout << "STRUMPACKCompressionTol: " << strumpack_lr_tol << '\n';
2443 : std::cout << "STRUMPACKLossyPrecision: " << strumpack_lossy_precision << '\n';
2444 : std::cout << "STRUMPACKButterflyLevels: " << strumpack_butterfly_l << '\n';
2445 : std::cout << "SuperLU3DCommunicator: " << superlu_3d << '\n';
2446 : std::cout << "AMSVectorInterpolation: " << ams_vector_interp << '\n';
2447 : std::cout << "AMSSingularOperator: " << ams_singular_op << '\n';
2448 : std::cout << "AMGAggressiveCoarsening: " << amg_agg_coarsen << '\n';
2449 :
2450 : std::cout << "DivFreeTol: " << divfree_tol << '\n';
2451 : std::cout << "DivFreeMaxIts: " << divfree_max_it << '\n';
2452 : std::cout << "EstimatorTol: " << estimator_tol << '\n';
2453 : std::cout << "EstimatorMaxIts: " << estimator_max_it << '\n';
2454 : std::cout << "EstimatorMG: " << estimator_mg << '\n';
2455 : std::cout << "GSOrthogonalization: " << gs_orthog << '\n';
2456 : }
2457 : }
2458 :
2459 8 : void SolverData::SetUp(json &config)
2460 : {
2461 8 : auto solver = config.find("Solver");
2462 8 : if (solver == config.end())
2463 : {
2464 0 : return;
2465 : }
2466 8 : order = solver->value("Order", order);
2467 8 : pa_order_threshold = solver->value("PartialAssemblyOrder", pa_order_threshold);
2468 8 : q_order_jac = solver->value("QuadratureOrderJacobian", q_order_jac);
2469 8 : q_order_extra = solver->value("QuadratureOrderExtra", q_order_extra);
2470 8 : device = solver->value("Device", device);
2471 8 : ceed_backend = solver->value("Backend", ceed_backend);
2472 :
2473 8 : driven.SetUp(*solver);
2474 8 : eigenmode.SetUp(*solver);
2475 8 : electrostatic.SetUp(*solver);
2476 8 : magnetostatic.SetUp(*solver);
2477 8 : transient.SetUp(*solver);
2478 8 : linear.SetUp(*solver);
2479 :
2480 : // Cleanup
2481 8 : solver->erase("Order");
2482 8 : solver->erase("PartialAssemblyOrder");
2483 8 : solver->erase("QuadratureOrderJacobian");
2484 8 : solver->erase("QuadratureOrderExtra");
2485 8 : solver->erase("Device");
2486 8 : solver->erase("Backend");
2487 :
2488 8 : solver->erase("Driven");
2489 8 : solver->erase("Eigenmode");
2490 8 : solver->erase("Electrostatic");
2491 8 : solver->erase("Magnetostatic");
2492 8 : solver->erase("Transient");
2493 8 : solver->erase("Linear");
2494 16 : MFEM_VERIFY(solver->empty(),
2495 : "Found an unsupported configuration file keyword under \"Solver\"!\n"
2496 : << solver->dump(2));
2497 :
2498 : // Debug
2499 : if constexpr (JSON_DEBUG)
2500 : {
2501 : std::cout << "Order: " << order << '\n';
2502 : std::cout << "PartialAssemblyOrder: " << pa_order_threshold << '\n';
2503 : std::cout << "QuadratureOrderJacobian: " << q_order_jac << '\n';
2504 : std::cout << "QuadratureOrderExtra: " << q_order_extra << '\n';
2505 : std::cout << "Device: " << device << '\n';
2506 : std::cout << "Backend: " << ceed_backend << '\n';
2507 : }
2508 : }
2509 :
2510 11 : int GetNumSteps(double start, double end, double delta)
2511 : {
2512 11 : if (end < start)
2513 : {
2514 : return 1;
2515 : }
2516 : constexpr double delta_eps = 1.0e-9; // 9 digits of precision comparing endpoint
2517 11 : double dn = std::abs(end - start) / std::abs(delta);
2518 11 : int n_step = 1 + static_cast<int>(dn);
2519 11 : double dfinal = start + n_step * delta;
2520 11 : return n_step + ((delta < 0.0 && dfinal - end > -delta_eps * end) ||
2521 11 : (delta > 0.0 && dfinal - end < delta_eps * end));
2522 : }
2523 :
2524 : } // namespace palace::config
|