PolyFEM
Loading...
Searching...
No Matches
State.cpp
Go to the documentation of this file.
1#include <polyfem/State.hpp>
2
3#include <polyfem/Units.hpp>
4
8
14
17
18#include <jse/jse.h>
19#include <polyfem/embedded_spec/polyfem.hpp>
20
21#include <polysolve/linear/Solver.hpp>
22
23#include <spdlog/sinks/basic_file_sink.h>
24#include <spdlog/sinks/ostream_sink.h>
25#include <spdlog/sinks/stdout_color_sinks.h>
26
27#include <ipc/utils/logger.hpp>
28#ifdef POLYFEM_WITH_ITR
29#include <wmtk/utils/Logger.hpp>
30#endif
31
32#include <igl/Timer.h>
33
34#include <cassert>
35#include <cmath>
36#include <filesystem>
37#include <fstream>
38#include <sstream>
39
40namespace spdlog::level
41{
43 spdlog::level::level_enum,
44 {{spdlog::level::level_enum::trace, "trace"},
45 {spdlog::level::level_enum::debug, "debug"},
46 {spdlog::level::level_enum::info, "info"},
47 {spdlog::level::level_enum::warn, "warning"},
48 {spdlog::level::level_enum::err, "error"},
49 {spdlog::level::level_enum::critical, "critical"},
50 {spdlog::level::level_enum::off, "off"},
51 {spdlog::level::level_enum::trace, 0},
52 {spdlog::level::level_enum::debug, 1},
53 {spdlog::level::level_enum::info, 2},
54 {spdlog::level::level_enum::warn, 3},
55 {spdlog::level::level_enum::err, 3},
56 {spdlog::level::level_enum::critical, 4},
57 {spdlog::level::level_enum::off, 5}})
58}
59
60namespace polyfem
61{
62 using namespace mesh;
63 using namespace utils;
64
65 namespace
66 {
67 std::string root_path(const json &args)
68 {
69 if (utils::is_param_valid(args, "root_path"))
70 return args["root_path"].get<std::string>();
71 return "";
72 }
73
74 std::string resolve_input_path(const json &args, const std::string &path, const bool only_if_exists = false)
75 {
76 return utils::resolve_path(path, root_path(args), only_if_exists);
77 }
78
79 std::string resolve_output_path(const std::string &output_dir, const std::string &path)
80 {
81 if (output_dir.empty() || path.empty() || std::filesystem::path(path).is_absolute())
82 return path;
83 return std::filesystem::weakly_canonical(std::filesystem::path(output_dir) / path).string();
84 }
85
86 bool contact_enabled(const json &args)
87 {
88 return args["contact"]["enabled"];
89 }
90
91 void init_time(json &args, Units &units)
92 {
93 if (!is_param_valid(args, "time"))
94 return;
95
96 const double t0 = Units::convert(args["time"]["t0"], units.time());
97 double tend = 0;
98 double dt = 0;
99 int time_steps = 0;
100
101 const int num_valid = is_param_valid(args["time"], "tend")
102 + is_param_valid(args["time"], "dt")
103 + is_param_valid(args["time"], "time_steps");
104 if (num_valid < 2)
105 {
106 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
107 }
108 else if (num_valid == 2)
109 {
110 if (is_param_valid(args["time"], "tend"))
111 {
112 tend = Units::convert(args["time"]["tend"], units.time());
113 assert(tend > t0);
114 if (is_param_valid(args["time"], "dt"))
115 {
116 dt = Units::convert(args["time"]["dt"], units.time());
117 assert(dt > 0);
118 time_steps = int(std::ceil((tend - t0) / dt));
119 assert(time_steps > 0);
120 }
121 else if (is_param_valid(args["time"], "time_steps"))
122 {
123 time_steps = args["time"]["time_steps"];
124 assert(time_steps > 0);
125 dt = (tend - t0) / time_steps;
126 assert(dt > 0);
127 }
128 else
129 {
130 throw std::runtime_error("This code should be unreachable!");
131 }
132 }
133 else if (is_param_valid(args["time"], "dt"))
134 {
135 assert(is_param_valid(args["time"], "time_steps"));
136
137 dt = Units::convert(args["time"]["dt"], units.time());
138 assert(dt > 0);
139
140 time_steps = args["time"]["time_steps"];
141 assert(time_steps > 0);
142
143 tend = t0 + time_steps * dt;
144 }
145 else
146 {
147 throw std::runtime_error("This code should be unreachable!");
148 }
149 }
150 else if (num_valid == 3)
151 {
152 tend = Units::convert(args["time"]["tend"], units.time());
153 dt = Units::convert(args["time"]["dt"], units.time());
154 time_steps = args["time"]["time_steps"];
155
156 if (std::abs(t0 + dt * time_steps - tend) > 1e-12)
157 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
158 }
159
160 args["time"]["tend"] = tend;
161 args["time"]["dt"] = dt;
162 args["time"]["time_steps"] = time_steps;
163
164 units.characteristic_length() *= dt;
165
166 logger().info("t0={}, dt={}, tend={}", t0, dt, tend);
167 }
168 } // namespace
169
174
176 const std::string &log_file,
177 const spdlog::level::level_enum log_level,
178 const spdlog::level::level_enum file_log_level,
179 const bool is_quiet)
180 {
181 std::vector<spdlog::sink_ptr> sinks;
182
183 if (!is_quiet)
184 {
185 console_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
186 sinks.emplace_back(console_sink_);
187 }
188
189 if (!log_file.empty())
190 {
191 file_sink_ = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, /*truncate=*/true);
192 file_sink_->set_level(file_log_level);
193 sinks.push_back(file_sink_);
194 }
195
196 init_logger(sinks, log_level);
197 spdlog::flush_every(std::chrono::seconds(3));
198 }
199
200 void State::init_logger(std::ostream &os, const spdlog::level::level_enum log_level)
201 {
202 std::vector<spdlog::sink_ptr> sinks;
203 sinks.emplace_back(std::make_shared<spdlog::sinks::ostream_sink_mt>(os, false));
204 init_logger(sinks, log_level);
205 }
206
208 const std::vector<spdlog::sink_ptr> &sinks,
209 const spdlog::level::level_enum log_level)
210 {
211 set_logger(std::make_shared<spdlog::logger>("polyfem", sinks.begin(), sinks.end()));
213
214 ipc::set_logger(std::make_shared<spdlog::logger>("ipctk", sinks.begin(), sinks.end()));
215
216#ifdef POLYFEM_WITH_ITR
217 wmtk::set_logger(std::make_shared<spdlog::logger>("wmtk", sinks.begin(), sinks.end()));
218#endif
219
220 logger().set_level(spdlog::level::trace);
221 ipc::logger().set_level(spdlog::level::trace);
222#ifdef POLYFEM_WITH_ITR
223 wmtk::logger().set_level(spdlog::level::trace);
224#endif
225
226 set_log_level(log_level);
227 }
228
229 void State::set_log_level(const spdlog::level::level_enum log_level)
230 {
231 spdlog::set_level(log_level);
232 if (console_sink_)
233 {
234 console_sink_->set_level(log_level);
235 }
236 else
237 {
238 logger().set_level(log_level);
239 ipc::logger().set_level(log_level);
240 }
241 }
242
243 void State::init(const json &p_args_in, const bool strict_validation)
244 {
245 json args_in = p_args_in;
246 const bool contact_dhat_was_explicit = args_in.contains("/contact/dhat"_json_pointer);
247
248 apply_common_params(args_in);
249
250 json rules;
251 jse::JSE jse;
252 {
253 jse.strict = strict_validation;
254 rules = jse::embed::polyfem_spec::polyfem::spec();
255
256 polysolve::linear::Solver::apply_default_solver(rules, "/solver/linear");
257 polysolve::linear::Solver::apply_default_solver(rules, "/solver/adjoint_linear");
258 }
259
260 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["linear"], logger());
261 if (args_in["solver"]["adjoint_linear"].is_null())
262 args_in["solver"]["adjoint_linear"] = args_in["solver"]["linear"];
263 else
264 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["adjoint_linear"], logger());
265
266 if (args_in.contains("/solver/nonlinear"_json_pointer))
267 {
268 if (args_in.contains("/solver/augmented_lagrangian/nonlinear"_json_pointer))
269 {
270 assert(args_in["solver"]["augmented_lagrangian"]["nonlinear"].is_object());
271 json nonlinear = args_in["solver"]["nonlinear"];
272 nonlinear.merge_patch(args_in["solver"]["augmented_lagrangian"]["nonlinear"]);
273 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = nonlinear;
274 }
275 else
276 {
277 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = args_in["solver"]["nonlinear"];
278 }
279 }
280
281 const bool valid_input = jse.verify_json(args_in, rules);
282 if (!valid_input)
283 {
284 logger().error("invalid input json:\n{}", jse.log2str());
285 throw std::runtime_error("Invalid input json file");
286 }
287
288 args = jse.inject_defaults(args_in, rules);
289
290 Units units;
291 units.init(args["units"]);
292
293 if (!args_in.contains("/space/advanced/bc_method"_json_pointer) && args["space"]["basis_type"] != "Lagrange")
294 {
295 logger().warn("Setting bc method to lsq for non-Lagrange basis");
296 args["space"]["advanced"]["bc_method"] = "lsq";
297 }
298
299 const std::string output_dir = resolve_input_path(args, args["output"]["directory"].get<std::string>());
300 if (!output_dir.empty())
301 std::filesystem::create_directories(output_dir);
302
303 std::string out_path_log = args["output"]["log"]["path"];
304 if (!out_path_log.empty())
305 out_path_log = resolve_output_path(output_dir, out_path_log);
306
307 for (auto &path : args["constraints"]["hard"])
308 path = resolve_input_path(args, path.get<std::string>());
309
310 for (auto &path : args["constraints"]["soft"])
311 path["data"] = resolve_input_path(args, path["data"].get<std::string>());
312
314 out_path_log,
315 args["output"]["log"]["level"],
316 args["output"]["log"]["file_level"],
317 args["output"]["log"]["quiet"]);
318
319 logger().info("Saving output to {}", output_dir);
320
321 set_max_threads(args["solver"]["max_threads"]);
322
323 init_time(args, units);
324
325 if (contact_enabled(args))
326 {
327 if (args["solver"]["contact"]["friction_iterations"] == 0)
328 {
329 logger().info("specified friction_iterations is 0; disabling friction");
330 args["contact"]["friction_coefficient"] = 0.0;
331 }
332 else if (args["solver"]["contact"]["friction_iterations"] < 0)
333 {
334 args["solver"]["contact"]["friction_iterations"] = std::numeric_limits<int>::max();
335 }
336 if (args["contact"]["friction_coefficient"] == 0.0)
337 {
338 args["solver"]["contact"]["friction_iterations"] = 0;
339 }
340 }
341 else
342 {
343 args["solver"]["contact"]["friction_iterations"] = 0;
344 args["contact"]["friction_coefficient"] = 0;
345 args["contact"]["periodic"] = false;
346 }
347
348 const std::string formulation = varform::formulation_from_args(args);
349 if (formulation.empty())
350 {
351 logger().error("specify some 'materials'");
352 throw std::runtime_error("invalid input");
353 }
354
357 throw std::runtime_error("polyfem::State is varform-only; use polyfem::legacy::State for " + formulation + ".");
358
359 logger().info("Using variational formulation: {}", variational_formulation->name());
360 args["contact"]["_dhat_was_explicit"] = contact_dhat_was_explicit;
361 variational_formulation->init(formulation, units, args, output_dir);
362 args["contact"].erase("_dhat_was_explicit");
363 }
364
365 void State::set_max_threads(const int max_threads)
366 {
367 NThread::get().set_num_threads(max_threads);
368 }
369
371 GEO::Mesh &meshin,
372 const std::function<int(const size_t, const std::vector<int> &, const RowVectorNd &, bool)> &boundary_marker,
373 bool non_conforming,
374 bool skip_boundary_sideset)
375 {
376 igl::Timer timer;
377 timer.start();
378 logger().info("Loading mesh...");
379
380 std::unique_ptr<Mesh> mesh = Mesh::create(meshin, non_conforming);
381 if (!mesh)
382 {
383 logger().error("Unable to load the mesh");
384 return;
385 }
386
387 RowVectorNd min, max;
388 mesh->bounding_box(min, max);
389
390 logger().info("mesh bb min [{}], max [{}]", min, max);
391
392 if (!skip_boundary_sideset)
393 mesh->compute_boundary_ids(boundary_marker);
394
395 timer.stop();
396 logger().info(" took {}s", timer.getElapsedTime());
397
398 assert(variational_formulation != nullptr);
399 variational_formulation->set_mesh(std::move(mesh), timer.getElapsedTime());
400 }
401
403 bool non_conforming,
404 const std::vector<std::string> &names,
405 const std::vector<Eigen::MatrixXi> &cells,
406 const std::vector<Eigen::MatrixXd> &vertices)
407 {
408 assert(names.size() == cells.size());
409 assert(vertices.size() == cells.size());
410
411 igl::Timer timer;
412 timer.start();
413
414 logger().info("Loading mesh ...");
415 assert(is_param_valid(args, "geometry"));
416 Units units;
417 units.init(args["units"]);
418 std::unique_ptr<Mesh> mesh = mesh::read_fem_geometry(
419 units,
420 args["geometry"], args["root_path"],
421 names, vertices, cells, non_conforming);
422
423 if (mesh == nullptr)
424 log_and_throw_error("unable to load the mesh!");
425
426 RowVectorNd min, max;
427 mesh->bounding_box(min, max);
428
429 logger().info("mesh bb min [{}], max [{}]", min, max);
430
431 timer.stop();
432 logger().info(" took {}s", timer.getElapsedTime());
433
434#ifdef POLYFEM_WITH_BEZIER
435 if (!mesh->is_simplicial())
436#else
437 if constexpr (true)
438#endif
439 {
440 args["space"]["advanced"]["count_flipped_els_continuous"] = false;
441 args["output"]["paraview"]["options"]["jacobian_validity"] = false;
442 args["solver"]["advanced"]["check_inversion"] = "Discrete";
443 }
444 else if (args["solver"]["advanced"]["check_inversion"] != "Discrete")
445 {
446 args["space"]["advanced"]["use_corner_quadrature"] = true;
447 }
448 // FIXME: this is a temporary workaround to avoid incorrect Jacobian validity results for non-simplicial meshes when using discrete inversion checking. We should instead implement proper Jacobian validity checking for non-simplicial meshes.
449 assert(variational_formulation != nullptr);
450 variational_formulation->set_args(args);
451 variational_formulation->set_mesh(std::move(mesh), timer.getElapsedTime());
452 }
453
454 void State::solve(Eigen::MatrixXd &sol)
455 {
456 assert(variational_formulation != nullptr);
457
458 variational_formulation->set_time_callback(time_callback);
459 variational_formulation->solve(sol);
460 }
461
462 void State::load_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, bool non_conforming)
463 {
464 assert(variational_formulation != nullptr);
465 igl::Timer timer;
466 timer.start();
467 auto mesh = mesh::Mesh::create(V, F, non_conforming);
468 timer.stop();
469 variational_formulation->set_mesh(std::move(mesh), timer.getElapsedTime());
470 }
471} // namespace polyfem
int V
std::shared_ptr< varform::VarForm > variational_formulation
active variational formulation
Definition State.hpp:47
void set_max_threads(const int max_threads=std::numeric_limits< int >::max())
Definition State.cpp:365
spdlog::sink_ptr file_sink_
Definition State.hpp:82
spdlog::sink_ptr console_sink_
logger sink to stdout
Definition State.hpp:81
json args
main input arguments containing all defaults
Definition State.hpp:44
void set_log_level(const spdlog::level::level_enum log_level)
change log level
Definition State.cpp:229
std::function< void(int, int, double, double)> time_callback
Optional UI progress callback.
Definition State.hpp:50
void init_logger(const std::string &log_file, const spdlog::level::level_enum log_level, const spdlog::level::level_enum file_log_level, const bool is_quiet)
initializing the logger
Definition State.cpp:175
void init(const json &json)
Definition Units.cpp:9
static double convert(const json &val, const std::string &unit_type)
Definition Units.cpp:31
void init(const json &args, const bool strict_validation)
initialize the polyfem solver with a json settings
void load_mesh(bool non_conforming=false, const std::vector< std::string > &names=std::vector< std::string >(), const std::vector< Eigen::MatrixXi > &cells=std::vector< Eigen::MatrixXi >(), const std::vector< Eigen::MatrixXd > &vertices=std::vector< Eigen::MatrixXd >())
loads the mesh from the json arguments
Definition StateLoad.cpp:91
void set_max_threads(const int max_threads=std::numeric_limits< int >::max())
void set_log_level(const spdlog::level::level_enum log_level)
change log level
void init_logger(const std::string &log_file, const spdlog::level::level_enum log_level, const spdlog::level::level_enum file_log_level, const bool is_quiet)
initializing the logger
Definition StateInit.cpp:68
void solve(Eigen::MatrixXd &sol, Eigen::MatrixXd &pressure, UserPostStepCallback user_post_step={}, const InitialConditionOverride *ic_override=nullptr)
solves the problem, call other methods
Definition State.hpp:361
State()
Constructor.
Definition StateInit.cpp:59
static std::unique_ptr< Mesh > create(const std::string &path, const bool non_conforming=false)
factory to build the proper mesh
Definition Mesh.cpp:173
void set_logger(spdlog::logger &logger)
static GeogramUtils & instance()
static NThread & get()
Definition par_for.hpp:19
void set_num_threads(const int max_threads)
Definition par_for.hpp:27
static std::shared_ptr< VarForm > create(const std::string &formulation, const json &args)
NLOHMANN_JSON_SERIALIZE_ENUM(CollisionProxyTessellation, {{CollisionProxyTessellation::REGULAR, "regular"}, {CollisionProxyTessellation::IRREGULAR, "irregular"}})
std::unique_ptr< Mesh > read_fem_geometry(const Units &units, const json &geometry, const std::string &root_path, const std::vector< std::string > &_names, const std::vector< Eigen::MatrixXd > &_vertices, const std::vector< Eigen::MatrixXi > &_cells, const bool non_conforming)
read FEM meshes from a geometry JSON array (or single)
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
bool is_param_valid(const json &params, const std::string &key)
Determine if a key exists and is non-null in a json object.
void apply_common_params(json &args)
Definition JSONUtils.cpp:14
std::string formulation_from_args(const json &args)
Extracts the formulation type from the given JSON arguments.
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
nlohmann::json json
Definition Common.hpp:9
Eigen::Matrix< double, 1, Eigen::Dynamic, Eigen::RowMajor, 1, 3 > RowVectorNd
Definition Types.hpp:13
void set_logger(std::shared_ptr< spdlog::logger > p_logger)
Setup a logger object to be used by Polyfem.
Definition Logger.cpp:62
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73