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