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
12
18
21
22#include <jse/jse.h>
23#include <polyfem/embedded_spec/polyfem.hpp>
24#include <polyfem/embedded_spec/polyfem_dirichlet.hpp>
25
26#include <polysolve/linear/Solver.hpp>
27
28#include <spdlog/sinks/basic_file_sink.h>
29#include <spdlog/sinks/ostream_sink.h>
30#include <spdlog/sinks/stdout_color_sinks.h>
31
32#include <ipc/utils/logger.hpp>
33#ifdef POLYFEM_WITH_ITR
34#include <wmtk/utils/Logger.hpp>
35#endif
36
37#include <igl/Timer.h>
38
39#include <cassert>
40#include <cmath>
41#include <filesystem>
42#include <fstream>
43#include <sstream>
44
45namespace spdlog::level
46{
48 spdlog::level::level_enum,
49 {{spdlog::level::level_enum::trace, "trace"},
50 {spdlog::level::level_enum::debug, "debug"},
51 {spdlog::level::level_enum::info, "info"},
52 {spdlog::level::level_enum::warn, "warning"},
53 {spdlog::level::level_enum::err, "error"},
54 {spdlog::level::level_enum::critical, "critical"},
55 {spdlog::level::level_enum::off, "off"},
56 {spdlog::level::level_enum::trace, 0},
57 {spdlog::level::level_enum::debug, 1},
58 {spdlog::level::level_enum::info, 2},
59 {spdlog::level::level_enum::warn, 3},
60 {spdlog::level::level_enum::err, 3},
61 {spdlog::level::level_enum::critical, 4},
62 {spdlog::level::level_enum::off, 5}})
63}
64
65namespace polyfem
66{
67 using namespace mesh;
68 using namespace utils;
69
70 namespace
71 {
72 std::string resolve_output_path(const std::string &output_dir, const std::string &path)
73 {
74 if (output_dir.empty() || path.empty() || std::filesystem::path(path).is_absolute())
75 return path;
76 return std::filesystem::weakly_canonical(std::filesystem::path(output_dir) / path).string();
77 }
78
79 bool contact_enabled(const json &args)
80 {
81 return args["contact"]["enabled"];
82 }
83
84 void init_time(json &args, Units &units)
85 {
86 if (!is_param_valid(args, "time"))
87 return;
88
89 const double t0 = Units::convert(args["time"]["t0"], units.time());
90 double tend = 0;
91 double dt = 0;
92 int time_steps = 0;
93
94 const int num_valid = is_param_valid(args["time"], "tend")
95 + is_param_valid(args["time"], "dt")
96 + is_param_valid(args["time"], "time_steps");
97 if (num_valid < 2)
98 {
99 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
100 }
101 else if (num_valid == 2)
102 {
103 if (is_param_valid(args["time"], "tend"))
104 {
105 tend = Units::convert(args["time"]["tend"], units.time());
106 assert(tend > t0);
107 if (is_param_valid(args["time"], "dt"))
108 {
109 dt = Units::convert(args["time"]["dt"], units.time());
110 assert(dt > 0);
111 time_steps = int(std::ceil((tend - t0) / dt));
112 assert(time_steps > 0);
113 }
114 else if (is_param_valid(args["time"], "time_steps"))
115 {
116 time_steps = args["time"]["time_steps"];
117 if (time_steps <= 0)
118 log_and_throw_error("A positive number of time steps is required to infer dt from tend.");
119 dt = (tend - t0) / time_steps;
120 assert(dt > 0);
121 }
122 else
123 {
124 throw std::runtime_error("This code should be unreachable!");
125 }
126 }
127 else if (is_param_valid(args["time"], "dt"))
128 {
129 assert(is_param_valid(args["time"], "time_steps"));
130
131 dt = Units::convert(args["time"]["dt"], units.time());
132 assert(dt > 0);
133
134 time_steps = args["time"]["time_steps"];
135 // A completed checkpoint has a known dt and zero remaining steps.
136 if (time_steps < 0)
137 log_and_throw_error("The number of time steps cannot be negative.");
138
139 tend = t0 + time_steps * dt;
140 }
141 else
142 {
143 throw std::runtime_error("This code should be unreachable!");
144 }
145 }
146 else if (num_valid == 3)
147 {
148 tend = Units::convert(args["time"]["tend"], units.time());
149 dt = Units::convert(args["time"]["dt"], units.time());
150 time_steps = args["time"]["time_steps"];
151
152 if (std::abs(t0 + dt * time_steps - tend) > 1e-12)
153 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
154 }
155
156 args["time"]["tend"] = tend;
157 args["time"]["dt"] = dt;
158 args["time"]["time_steps"] = time_steps;
159
160 units.characteristic_length() *= dt;
161
162 logger().info("t0={}, dt={}, tend={}", t0, dt, tend);
163 }
164 } // namespace
165
170
172 const std::string &log_file,
173 const spdlog::level::level_enum log_level,
174 const spdlog::level::level_enum file_log_level,
175 const bool is_quiet)
176 {
177 std::vector<spdlog::sink_ptr> sinks;
178
179 if (!is_quiet)
180 {
181 console_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
182 sinks.emplace_back(console_sink_);
183 }
184
185 if (!log_file.empty())
186 {
187 file_sink_ = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, /*truncate=*/true);
188 file_sink_->set_level(file_log_level);
189 sinks.push_back(file_sink_);
190 }
191
192 init_logger(sinks, log_level);
193 spdlog::flush_every(std::chrono::seconds(3));
194 }
195
196 void State::init_logger(std::ostream &os, const spdlog::level::level_enum log_level)
197 {
198 std::vector<spdlog::sink_ptr> sinks;
199 sinks.emplace_back(std::make_shared<spdlog::sinks::ostream_sink_mt>(os, false));
200 init_logger(sinks, log_level);
201 }
202
204 const std::vector<spdlog::sink_ptr> &sinks,
205 const spdlog::level::level_enum log_level)
206 {
207 set_logger(std::make_shared<spdlog::logger>("polyfem", sinks.begin(), sinks.end()));
209
210 ipc::set_logger(std::make_shared<spdlog::logger>("ipctk", sinks.begin(), sinks.end()));
211
212#ifdef POLYFEM_WITH_ITR
213 wmtk::set_logger(std::make_shared<spdlog::logger>("wmtk", sinks.begin(), sinks.end()));
214#endif
215
216 logger().set_level(spdlog::level::trace);
217 ipc::logger().set_level(spdlog::level::trace);
218#ifdef POLYFEM_WITH_ITR
219 wmtk::logger().set_level(spdlog::level::trace);
220#endif
221
222 set_log_level(log_level);
223 }
224
225 void State::set_log_level(const spdlog::level::level_enum log_level)
226 {
227 spdlog::set_level(log_level);
228 if (console_sink_)
229 {
230 console_sink_->set_level(log_level);
231 }
232 else
233 {
234 logger().set_level(log_level);
235 ipc::logger().set_level(log_level);
236 }
237 }
238
240 const json &p_args_in,
241 const bool strict_validation,
242 const bool is_adjoint_optimization)
243 {
244 const io::FileSystemIO resources(std::filesystem::current_path());
245 init(p_args_in, resources, strict_validation, is_adjoint_optimization);
246 }
247
249 const json &p_args_in,
250 const io::ResourceIO &resources,
251 const bool strict_validation,
252 const bool is_adjoint_optimization)
253 {
254 checkpoint_.reset();
255 json args_in = p_args_in;
256 const std::string resource_root = utils::is_param_valid(args_in, "root_path")
257 ? args_in["root_path"].get<std::string>()
258 : std::string();
259 resources_ = resources.with_root(resource_root);
260 args_in.erase("root_path");
261 const bool contact_dhat_was_explicit = args_in.contains("/contact/dhat"_json_pointer);
262
263 if (auto common_resources = apply_common_params(args_in, *resources_))
264 resources_ = std::move(common_resources);
265
266 json rules;
267 jse::JSE jse;
268 {
269 jse.strict = strict_validation;
270 rules = jse::embed::polyfem_spec::polyfem::spec();
271
272 polysolve::linear::Solver::apply_default_solver(rules, "/solver/linear");
273 polysolve::linear::Solver::apply_default_solver(rules, "/solver/adjoint_linear");
274 }
275
276 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["linear"], logger());
277 if (args_in["solver"]["adjoint_linear"].is_null())
278 args_in["solver"]["adjoint_linear"] = args_in["solver"]["linear"];
279 else
280 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["adjoint_linear"], logger());
281
282 if (args_in.contains("/solver/nonlinear"_json_pointer))
283 {
284 if (args_in.contains("/solver/augmented_lagrangian/nonlinear"_json_pointer))
285 {
286 assert(args_in["solver"]["augmented_lagrangian"]["nonlinear"].is_object());
287 json nonlinear = args_in["solver"]["nonlinear"];
288 nonlinear.merge_patch(args_in["solver"]["augmented_lagrangian"]["nonlinear"]);
289 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = nonlinear;
290 }
291 else
292 {
293 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = args_in["solver"]["nonlinear"];
294 }
295 }
296
297 const bool valid_input = jse.verify_json(args_in, rules);
298 if (!valid_input)
299 {
300 logger().error("invalid input json:\n{}", jse.log2str());
301 throw std::runtime_error("Invalid input json file");
302 }
303
304 args = jse.inject_defaults(args_in, rules);
305
307 args, jse::embed::polyfem_dirichlet_spec::polyfem_dirichlet::spec(), *resources_);
308
309 Units units;
310 units.init(args["units"]);
311
312 if (!args_in.contains("/space/advanced/bc_method"_json_pointer) && args["space"]["basis_type"] != "Lagrange")
313 {
314 logger().warn("Setting bc method to lsq for non-Lagrange basis");
315 args["space"]["advanced"]["bc_method"] = "lsq";
316 }
317
318 const std::string configured_output_dir = args["output"]["directory"].get<std::string>();
319 const std::string output_dir = configured_output_dir.empty()
320 ? std::string()
321 : (std::filesystem::path(configured_output_dir).is_absolute()
322 ? std::filesystem::path(configured_output_dir)
323 : resources_->host_directory() / configured_output_dir)
324 .lexically_normal()
325 .string();
326 if (!output_dir.empty())
327 std::filesystem::create_directories(output_dir);
328
329 std::string out_path_log = args["output"]["log"]["path"];
330 if (!out_path_log.empty())
331 out_path_log = resolve_output_path(output_dir, out_path_log);
332
334 out_path_log,
335 args["output"]["log"]["level"],
336 args["output"]["log"]["file_level"],
337 args["output"]["log"]["quiet"]);
338
339 logger().info("Saving output to {}", output_dir);
340
341 set_max_threads(args["solver"]["max_threads"]);
342
343 init_time(args, units);
344
345 if (contact_enabled(args))
346 {
347 if (args["solver"]["contact"]["friction_iterations"] == 0)
348 {
349 logger().info("specified friction_iterations is 0; disabling friction");
350 args["contact"]["friction_coefficient"] = 0.0;
351 }
352 else if (args["solver"]["contact"]["friction_iterations"] < 0)
353 {
354 args["solver"]["contact"]["friction_iterations"] = std::numeric_limits<int>::max();
355 }
356 if (args["contact"]["friction_coefficient"] == 0.0)
357 {
358 args["solver"]["contact"]["friction_iterations"] = 0;
359 }
360 }
361 else
362 {
363 args["solver"]["contact"]["friction_iterations"] = 0;
364 args["contact"]["friction_coefficient"] = 0;
365 args["contact"]["periodic"] = false;
366 }
367
368 const std::string formulation = varform::formulation_from_args(args);
369 if (formulation.empty())
370 {
371 logger().error("specify some 'materials'");
372 throw std::runtime_error("invalid input");
373 }
374
375 variational_formulation = varform::VarFormFactory::create(formulation, args, *resources_, is_adjoint_optimization);
377 throw std::runtime_error("polyfem::State is varform-only; use polyfem::legacy::State for " + formulation + ".");
378
379 logger().info("Using variational formulation: {}", variational_formulation->name());
380 args["contact"]["_dhat_was_explicit"] = contact_dhat_was_explicit;
381 variational_formulation->init(formulation, units, args, output_dir);
382 args["contact"].erase("_dhat_was_explicit");
383 }
384
385 void State::init(const io::CheckpointReader &checkpoint, const bool strict_validation)
386 {
387 init(checkpoint.config(), checkpoint, strict_validation);
388 }
389
391 const json &continuation,
392 const io::CheckpointReader &checkpoint,
393 const bool strict_validation)
394 {
395 init(continuation, checkpoint.resources(), strict_validation, false);
396 checkpoint_ = std::cref(checkpoint);
397 variational_formulation->set_checkpoint_reader(checkpoint);
398 if (checkpoint_->get().metadata().formulation != variational_formulation->name())
400 "Checkpoint formulation '{}' is incompatible with configured formulation '{}'.",
401 checkpoint_->get().metadata().formulation, variational_formulation->name());
402 if (std::abs(args["time"]["dt"].get<double>() - checkpoint_->get().metadata().dt) > 1e-12)
403 log_and_throw_error("Checkpoint dt is incompatible with its continuation configuration.");
404 if (std::abs(args["time"]["t0"].get<double>() - checkpoint_->get().metadata().time) > 1e-12)
405 log_and_throw_error("Checkpoint time is incompatible with its continuation configuration.");
406 }
407
408 void State::set_max_threads(const int max_threads)
409 {
410 NThread::get().set_num_threads(max_threads);
411 }
412
413 void State::set_mesh(
414 GEO::Mesh &meshin,
415 const std::function<int(const size_t, const std::vector<int> &, const RowVectorNd &, bool)> &boundary_marker,
416 bool non_conforming,
417 bool skip_boundary_sideset)
418 {
419 igl::Timer timer;
420 timer.start();
421 logger().info("Loading mesh...");
422
423 std::unique_ptr<Mesh> mesh = Mesh::create(
424 mesh::MeshReader::from_geogram(meshin), non_conforming);
425 if (!mesh)
426 {
427 logger().error("Unable to load the mesh");
428 return;
429 }
430
431 RowVectorNd min, max;
432 mesh->bounding_box(min, max);
433
434 logger().info("mesh bb min [{}], max [{}]", min, max);
435
436 if (!skip_boundary_sideset)
437 mesh->compute_boundary_ids(boundary_marker);
438
439 timer.stop();
440 logger().info(" took {}s", timer.getElapsedTime());
441
442 assert(variational_formulation != nullptr);
443 variational_formulation->set_mesh(std::move(mesh), timer.getElapsedTime());
444 }
445
446 void State::load_mesh(const bool non_conforming)
447 {
448 igl::Timer timer;
449 timer.start();
450
451 logger().info("Loading mesh ...");
452 assert(is_param_valid(args, "geometry"));
453 Units units;
454 units.init(args["units"]);
455 mesh::GeometryLoader geometry_loader(units, *resources_);
456 const auto obstacle_displacements = utils::json_as_array(args["boundary_conditions"]["obstacle_displacements"]);
457 const auto dirichlet_conditions = utils::json_as_array(args["boundary_conditions"]["dirichlet_boundary"]);
458 mesh::LoadedGeometry loaded_geometry;
459 if (checkpoint_)
460 {
461 loaded_geometry.fem = checkpoint_->get().read_mesh("/checkpoint/meshes/active", non_conforming);
462 loaded_geometry.obstacle = geometry_loader.load_obstacles(
463 args["geometry"], obstacle_displacements, dirichlet_conditions,
464 loaded_geometry.fem->dimension(), non_conforming);
465 }
466 else
467 loaded_geometry = geometry_loader.load(
468 args["geometry"], obstacle_displacements, dirichlet_conditions, non_conforming);
469 auto &mesh = loaded_geometry.fem;
470
471 if (mesh == nullptr)
472 log_and_throw_error("unable to load the mesh!");
473
474 RowVectorNd min, max;
475 mesh->bounding_box(min, max);
476
477 logger().info("mesh bb min [{}], max [{}]", min, max);
478
479 timer.stop();
480 logger().info(" took {}s", timer.getElapsedTime());
481
482#ifdef POLYFEM_WITH_MISO
483 if (!mesh->is_simplicial())
484#else
485 if constexpr (true)
486#endif
487 {
488 args["space"]["advanced"]["count_flipped_els_continuous"] = false;
489 args["output"]["paraview"]["options"]["jacobian_validity"] = false;
490 args["solver"]["advanced"]["check_inversion"] = "Discrete";
491 }
492 else if (args["solver"]["advanced"]["check_inversion"] != "Discrete")
493 {
494 args["space"]["advanced"]["use_corner_quadrature"] = true;
495 }
496 // 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.
497 assert(variational_formulation != nullptr);
498 variational_formulation->set_args(args);
499 variational_formulation->set_geometry(std::move(loaded_geometry), timer.getElapsedTime());
500 }
501
502 void State::solve(Eigen::MatrixXd &sol)
503 {
504 assert(variational_formulation != nullptr);
505 variational_formulation->set_time_callback(time_callback);
506 variational_formulation->solve(sol);
507 }
508
509 void State::set_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, bool non_conforming)
510 {
511 assert(variational_formulation != nullptr);
512 igl::Timer timer;
513 timer.start();
514 auto mesh = mesh::Mesh::create(mesh::MeshData(V, F), non_conforming);
515 timer.stop();
516 variational_formulation->set_mesh(std::move(mesh), timer.getElapsedTime());
517 }
518} // namespace polyfem
int V
std::shared_ptr< varform::VarForm > variational_formulation
active variational formulation
Definition State.hpp:71
void init(const json &args, bool strict_validation, bool is_adjoint_optimization=false)
initialize the polyfem solver with a json settings
Definition State.cpp:239
void set_max_threads(const int max_threads=std::numeric_limits< int >::max())
Definition State.cpp:408
std::optional< std::reference_wrapper< const io::CheckpointReader > > checkpoint_
Definition State.hpp:135
spdlog::sink_ptr file_sink_
Definition State.hpp:106
spdlog::sink_ptr console_sink_
logger sink to stdout
Definition State.hpp:105
json args
main input arguments containing all defaults
Definition State.hpp:68
void set_log_level(const spdlog::level::level_enum log_level)
change log level
Definition State.cpp:225
std::unique_ptr< const io::ResourceIO > resources_
Definition State.hpp:134
std::function< void(int, int, double, double)> time_callback
Optional UI progress callback.
Definition State.hpp:74
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:171
void init(const json &json)
Definition Units.cpp:9
static double convert(const json &val, const std::string &unit_type)
Definition Units.cpp:35
const ResourceIO & resources() const
const json & config() const
Read-only interface to a hierarchical collection of simulation resources.
virtual std::unique_ptr< const ResourceIO > with_root(const std::string &root) const =0
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:94
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:364
State()
Constructor.
Definition StateInit.cpp:59
Applies geometry JSON semantics while delegating all decoding to MeshLoader.
Obstacle load_obstacles(const json &geometry, const std::vector< json > &obstacle_displacements, const std::vector< json > &dirichlet_conditions, int dimension, bool non_conforming=false) const
LoadedGeometry load(const json &geometry, const std::vector< json > &obstacle_displacements, const std::vector< json > &dirichlet_conditions, bool non_conforming=false) const
std::unique_ptr< Mesh > fem
Format-independent input used to construct a runtime Mesh.
Definition MeshData.hpp:12
static std::unique_ptr< Mesh > create(MeshData data, bool non_conforming=false)
Construct a runtime mesh from format-independent decoded data.
Definition Mesh.cpp:80
static MeshData from_geogram(GEO::Mesh &mesh)
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, const io::ResourceIO &resources, bool is_optimization=false)
NLOHMANN_JSON_SERIALIZE_ENUM(CollisionProxyTessellation, {{CollisionProxyTessellation::REGULAR, "regular"}, {CollisionProxyTessellation::IRREGULAR, "irregular"}})
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.
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:49
bool is_param_valid(const json &params, const std::string &key)
Determine if a key exists and is non-null in a json object.
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