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