PolyFEM
Loading...
Searching...
No Matches
JSONUtils.cpp
Go to the documentation of this file.
1#include "JSONUtils.hpp"
2
6
7#include <jse/jse.h>
8
9#include <Eigen/Geometry>
10
11namespace polyfem
12{
13 namespace utils
14 {
15 namespace
16 {
17 std::string logical_parent(const std::string &path)
18 {
19 const std::filesystem::path parent = std::filesystem::path(path).parent_path();
20 return parent.empty() ? "." : parent.generic_string();
21 }
22 } // namespace
23
24 std::unique_ptr<const io::ResourceIO> apply_common_params(json &args, const io::ResourceIO &resources)
25 {
26 if (!args.contains("common"))
27 return nullptr;
28 const std::string common_path = args["common"].get<std::string>();
29 if (common_path.empty())
30 return nullptr;
31
32 json common_params = json::parse(resources.read_string(common_path));
33 auto common_resources = resources.with_root(logical_parent(common_path));
34 const bool has_explicit_root = common_params.contains("root_path")
35 && common_params["root_path"].is_string()
36 && !common_params["root_path"].get<std::string>().empty();
37 if (has_explicit_root)
38 common_resources = common_resources->with_root(common_params["root_path"].get<std::string>());
39 common_params.erase("root_path");
40 if (auto nested_resources = apply_common_params(common_params, *common_resources))
41 common_resources = std::move(nested_resources);
42
43 json patch;
44 if (args.contains("patch"))
45 {
46 patch = args["patch"];
47 args.erase("patch");
48 }
49 args.erase("root_path");
50 common_params.merge_patch(args);
51 if (!patch.empty())
52 common_params = common_params.patch(patch);
53 args = std::move(common_params);
54 args.erase("common");
55 // Preserve the caller's resource root unless this common file explicitly
56 // overrides it. Historically, paths inherited from a common file were
57 // resolved against the top-level input; merely locating the common file
58 // must not silently reroot the merged simulation.
59 return has_explicit_root ? std::move(common_resources) : nullptr;
60 }
61
63 {
64 if (!args.contains("common"))
65 return;
66
67 const std::string common_params_path = resolve_path(args["common"], args["root_path"]);
68
69 if (common_params_path.empty())
70 return;
71
72 const std::filesystem::path common_path(common_params_path);
73 const io::FileSystemIO resources(common_path.parent_path());
74 json common_params = json::parse(resources.read_string(common_path.filename().string()));
75
76 // Recursively apply common params
77 const bool has_root_path = common_params.contains("root_path");
78 if (has_root_path)
79 common_params["root_path"] = resolve_path(common_params["root_path"], common_params_path);
80 else
81 common_params["root_path"] = common_params_path;
82 apply_common_params(common_params);
83
84 // If there is a root path in the common params, it overrides the one in the current params.
85 // This is somewhat backwards as normally current params override common params, but this is
86 // an easy way to make sure that the relative paths in common are correct.
87 if (has_root_path)
88 args["root_path"] = common_params["root_path"];
89
90 json patch;
91 if (args.contains("patch"))
92 {
93 patch = args["patch"];
94 args.erase("patch");
95 }
96
97 common_params.merge_patch(args);
98 if (!patch.empty())
99 common_params = common_params.patch(patch);
100 args = common_params;
101
102 args.erase("common"); // Remove common params from the final json
103 }
104
105 void expand_bc_sidecars(json &args, const json &rules)
106 {
107 if (!args.contains("boundary_conditions"))
108 return;
109
110 json &bcs = args["boundary_conditions"];
111 if (!bcs.contains("dirichlet_boundary") || !bcs["dirichlet_boundary"].is_array())
112 return;
113
114 const json root_path = args.contains("root_path") ? args["root_path"] : json("");
115
116 json expanded = json::array();
117 for (const auto &entry : bcs["dirichlet_boundary"])
118 {
119 if (entry.is_string())
120 {
121 const std::string path = resolve_path(entry, root_path);
122 if (std::filesystem::path(path).extension() == ".json")
123 {
124 std::ifstream file(path);
125 if (!file.is_open())
126 log_and_throw_error("Unable to open dirichlet_boundary {} file", path);
127
128 json sidecar;
129 try
130 {
131 file >> sidecar;
132 }
133 catch (const std::exception &e)
134 {
135 log_and_throw_error("Invalid JSON in dirichlet_boundary file {}: {}", path, e.what());
136 }
137 file.close();
138
139 if (!sidecar.is_array())
140 log_and_throw_error("dirichlet_boundary file {} must contain an array", path);
141
142 jse::JSE jse;
143 for (const auto &e : sidecar)
144 {
145 json filled = e;
146 expanded.push_back(jse.inject_defaults(filled, rules));
147 }
148 continue;
149 }
150 }
151 expanded.push_back(entry);
152 }
153 bcs["dirichlet_boundary"] = expanded;
154 }
155
157 json &args,
158 const json &rules,
159 const io::ResourceIO &resources)
160 {
161 if (!args.contains("boundary_conditions"))
162 return;
163
164 json &bcs = args["boundary_conditions"];
165 if (!bcs.contains("dirichlet_boundary") || !bcs["dirichlet_boundary"].is_array())
166 return;
167
168 json expanded = json::array();
169 for (const auto &entry : bcs["dirichlet_boundary"])
170 {
171 if (entry.is_string())
172 {
173 const std::string path = entry.get<std::string>();
174 if (std::filesystem::path(path).extension() == ".json")
175 {
176 json sidecar;
177 try
178 {
179 sidecar = json::parse(resources.read_string(path));
180 }
181 catch (const std::exception &e)
182 {
184 "Unable to read dirichlet_boundary resource {}: {}", path, e.what());
185 }
186
187 if (!sidecar.is_array())
188 log_and_throw_error("dirichlet_boundary resource {} must contain an array", path);
189
190 jse::JSE jse;
191 for (const auto &e : sidecar)
192 {
193 json filled = e;
194 expanded.push_back(jse.inject_defaults(filled, rules));
195 }
196 continue;
197 }
198 }
199 expanded.push_back(entry);
200 }
201 bcs["dirichlet_boundary"] = expanded;
202 }
203
204 Eigen::Matrix3d to_rotation_matrix(const json &jr, std::string mode)
205 {
206 std::transform(mode.begin(), mode.end(), mode.begin(), ::tolower);
207
208 if (jr.is_array() && jr.empty())
209 {
210 return Eigen::Matrix3d::Identity(3, 3);
211 }
212
213 Eigen::VectorXd r;
214 if (jr.is_number())
215 {
216 r.setZero(3);
217 assert(mode.size() == 1); // must be either "x", "y", or "z"
218 int i = mode[0] - 'x';
219 assert(i >= 0 && i < 3);
220 r[i] = jr.get<double>();
221 }
222 else
223 {
224 assert(jr.is_array());
225 r = jr;
226 }
227
228 if (mode == "axis_angle")
229 {
230 assert(r.size() == 4);
231 double angle = deg2rad(r[0]); // NOTE: assumes input angle is in degrees
232 Eigen::Vector3d axis = r.tail<3>().normalized();
233 return Eigen::AngleAxisd(angle, axis).toRotationMatrix();
234 }
235
236 if (mode == "quaternion")
237 {
238 assert(r.size() == 4);
239 Eigen::Vector4d q = r.normalized();
240 return Eigen::Quaterniond(q).toRotationMatrix();
241 }
242
243 // The following expect the input is given in degrees
244 r = deg2rad(r);
245
246 if (mode == "rotation_vector")
247 {
248 assert(r.size() == 3);
249 double angle = r.norm();
250 if (angle != 0)
251 {
252 return Eigen::AngleAxisd(angle, r / angle).toRotationMatrix();
253 }
254 else
255 {
256 return Eigen::Matrix3d::Identity();
257 }
258 }
259
260 Eigen::Matrix3d R = Eigen::Matrix3d::Identity();
261
262 for (int i = 0; i < mode.size(); i++)
263 {
264 int j = mode[i] - 'x';
265 assert(j >= 0 && j < 3);
266 Eigen::Vector3d axis = Eigen::Vector3d::Zero();
267 axis[j] = 1;
268 R = Eigen::AngleAxisd(r[j], axis).toRotationMatrix() * R;
269 }
270
271 return R;
272 }
273
274 bool is_param_valid(const json &params, const std::string &key)
275 {
276 return !params.is_null() && params.contains(key) && !params[key].is_null();
277 }
278 } // namespace utils
279} // namespace polyfem
Read-only interface to a hierarchical collection of simulation resources.
virtual std::string read_string(const std::string &path) const
virtual std::unique_ptr< const ResourceIO > with_root(const std::string &root) const =0
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
std::unique_ptr< const io::ResourceIO > apply_common_params(json &args, const io::ResourceIO &resources)
Definition JSONUtils.cpp:24
void expand_bc_sidecars(json &args, const json &rules)
Expand string entries in dirichlet_boundary that point to .json files.
Eigen::Matrix3d to_rotation_matrix(const json &jr, std::string mode)
T deg2rad(T deg)
Definition JSONUtils.hpp:29
bool is_param_valid(const json &params, const std::string &key)
Determine if a key exists and is non-null in a json object.
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73