PolyFEM
Loading...
Searching...
No Matches
StateInit.cpp
Go to the documentation of this file.
2
6
9
14
16
17#include <jse/jse.h>
18#include <polyfem/embedded_spec/polyfem.hpp>
19
20#include <spdlog/sinks/stdout_color_sinks.h>
21#include <spdlog/sinks/basic_file_sink.h>
22#include <spdlog/sinks/ostream_sink.h>
23
24#include <ipc/utils/logger.hpp>
25#ifdef POLYFEM_WITH_ITR
26#include <wmtk/utils/Logger.hpp>
27#endif
28
31
32#include <sstream>
33
35{
36 NLOHMANN_JSON_SERIALIZE_ENUM(
37 spdlog::level::level_enum,
38 {{spdlog::level::level_enum::trace, "trace"},
39 {spdlog::level::level_enum::debug, "debug"},
40 {spdlog::level::level_enum::info, "info"},
41 {spdlog::level::level_enum::warn, "warning"},
42 {spdlog::level::level_enum::err, "error"},
43 {spdlog::level::level_enum::critical, "critical"},
44 {spdlog::level::level_enum::off, "off"},
45 {spdlog::level::level_enum::trace, 0},
46 {spdlog::level::level_enum::debug, 1},
47 {spdlog::level::level_enum::info, 2},
48 {spdlog::level::level_enum::warn, 3},
49 {spdlog::level::level_enum::err, 3},
50 {spdlog::level::level_enum::critical, 4},
51 {spdlog::level::level_enum::off, 5}})
52}
53
54namespace polyfem::legacy
55{
56 using namespace problem;
57 using namespace utils;
58
60 {
61 using namespace polysolve;
62
64
66 }
67
69 const std::string &log_file,
70 const spdlog::level::level_enum log_level,
71 const spdlog::level::level_enum file_log_level,
72 const bool is_quiet)
73 {
74 std::vector<spdlog::sink_ptr> sinks;
75
76 if (!is_quiet)
77 {
78 console_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
79 sinks.emplace_back(console_sink_);
80 }
81
82 if (!log_file.empty())
83 {
84 file_sink_ = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, /*truncate=*/true);
85 // Set the file sink separately from the console so it can save all messages
86 file_sink_->set_level(file_log_level);
87 sinks.push_back(file_sink_);
88 }
89
90 init_logger(sinks, log_level);
91 spdlog::flush_every(std::chrono::seconds(3));
92 }
93
94 void State::init_logger(std::ostream &os, const spdlog::level::level_enum log_level)
95 {
96 std::vector<spdlog::sink_ptr> sinks;
97 sinks.emplace_back(std::make_shared<spdlog::sinks::ostream_sink_mt>(os, false));
98 init_logger(sinks, log_level);
99 }
100
102 const std::vector<spdlog::sink_ptr> &sinks,
103 const spdlog::level::level_enum log_level)
104 {
105 set_logger(std::make_shared<spdlog::logger>("polyfem", sinks.begin(), sinks.end()));
107
108 ipc::set_logger(std::make_shared<spdlog::logger>("ipctk", sinks.begin(), sinks.end()));
109
110#ifdef POLYFEM_WITH_ITR
111 wmtk::set_logger(std::make_shared<spdlog::logger>("wmtk", sinks.begin(), sinks.end()));
112#endif
113
114 // Set the logger at the lowest level, so all messages are passed to the sinks
115 logger().set_level(spdlog::level::trace);
116 ipc::logger().set_level(spdlog::level::trace);
117#ifdef POLYFEM_WITH_ITR
118 wmtk::logger().set_level(spdlog::level::trace);
119#endif
120
121 set_log_level(log_level);
122 }
123
124 void State::set_log_level(const spdlog::level::level_enum log_level)
125 {
126 spdlog::set_level(log_level);
127 if (console_sink_)
128 {
129 // Set only the level of the console
130 console_sink_->set_level(log_level); // Shared by all loggers
131 }
132 else
133 {
134 // Set the level of all sinks
135 logger().set_level(log_level);
136 ipc::logger().set_level(log_level);
137 }
138 }
139
140 void State::init(const json &p_args_in, const bool strict_validation)
141 {
142 json args_in = p_args_in; // mutable copy
143
144 has_constraints_ = p_args_in.contains("constraints");
145
146 apply_common_params(args_in);
147
148 // CHECK validity json
149 json rules;
150 jse::JSE jse;
151 {
152 jse.strict = strict_validation;
153 rules = jse::embed::polyfem_spec::polyfem::spec();
154
155 polysolve::linear::Solver::apply_default_solver(rules, "/solver/linear");
156 polysolve::linear::Solver::apply_default_solver(rules, "/solver/adjoint_linear");
157 }
158
159 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["linear"], logger());
160 if (args_in["solver"]["adjoint_linear"].is_null())
161 args_in["solver"]["adjoint_linear"] = args_in["solver"]["linear"];
162 else
163 polysolve::linear::Solver::select_valid_solver(args_in["solver"]["adjoint_linear"], logger());
164
165 // Use the /solver/nonlinear settings as the default for /solver/augmented_lagrangian/nonlinear
166 if (args_in.contains("/solver/nonlinear"_json_pointer))
167 {
168 if (args_in.contains("/solver/augmented_lagrangian/nonlinear"_json_pointer))
169 {
170 assert(args_in["solver"]["augmented_lagrangian"]["nonlinear"].is_object());
171 // Merge the augmented lagrangian settings into the nonlinear settings,
172 // and then replace the augmented lagrangian settings with the merged settings.
173 json nonlinear = args_in["solver"]["nonlinear"]; // copy
174 nonlinear.merge_patch(args_in["solver"]["augmented_lagrangian"]["nonlinear"]);
175 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = nonlinear;
176 }
177 else
178 {
179 // Copy the nonlinear settings to the augmented_lagrangian settings
180 args_in["solver"]["augmented_lagrangian"]["nonlinear"] = args_in["solver"]["nonlinear"];
181 }
182 }
183 const bool valid_input = jse.verify_json(args_in, rules);
184
185 if (!valid_input)
186 {
187 logger().error("invalid input json:\n{}", jse.log2str());
188 throw std::runtime_error("Invalid input json file");
189 }
190 // end of check
191
192 this->args = jse.inject_defaults(args_in, rules);
194 // Legacy State still uses the old restart fields internally. They are no
195 // longer part of the public schema, so provide their inert defaults only
196 // after validation instead of exposing them to non-legacy configurations.
197 if (!this->args.contains("/input/data/state"_json_pointer))
198 this->args["input"]["data"]["state"] = "";
199 if (!this->args.contains("/input/data/reorder"_json_pointer))
200 this->args["input"]["data"]["reorder"] = false;
201 if (!this->args.contains("/output/data/state"_json_pointer))
202 this->args["output"]["data"]["state"] = "";
203 if (!this->args.contains("/output/data/rest_mesh"_json_pointer))
204 this->args["output"]["data"]["rest_mesh"] = "";
205 if (!this->args.contains("/output/data/file_index_offset"_json_pointer))
206 this->args["output"]["data"]["file_index_offset"] = 0;
207 if (!this->args.contains("/output/restart_json"_json_pointer))
208 this->args["output"]["restart_json"] = "";
209 const polyfem::io::FileSystemIO resources(root_path());
210 units.init(this->args["units"]);
211
212 if (!args_in.contains("/space/advanced/bc_method"_json_pointer) && this->args["space"]["basis_type"] != "Lagrange")
213 {
214 logger().warn("Setting bc method to lsq for non-Lagrange basis");
215 this->args["space"]["advanced"]["bc_method"] = "lsq";
216 }
217
218 // Save output directory and resolve output paths dynamically
219 const std::string output_dir = resolve_input_path(this->args["output"]["directory"]);
220 if (!output_dir.empty())
221 {
222 std::filesystem::create_directories(output_dir);
223 }
224 this->output_dir = output_dir;
225
226 std::string out_path_log = this->args["output"]["log"]["path"];
227 if (!out_path_log.empty())
228 {
229 out_path_log = resolve_output_path(out_path_log);
230 }
231
232 for (auto &path : this->args["constraints"]["hard"])
233 {
234 path = resolve_input_path(path);
235 }
236 for (auto &path : this->args["constraints"]["soft"])
237 {
238 path["data"] = resolve_input_path(path["data"]);
239 }
240
242 out_path_log,
243 this->args["output"]["log"]["level"],
244 this->args["output"]["log"]["file_level"],
245 this->args["output"]["log"]["quiet"]);
246
247 logger().info("Saving output to {}", output_dir);
248
249 const unsigned int thread_in = this->args["solver"]["max_threads"];
250 set_max_threads(thread_in);
251
252 has_dhat = args_in["contact"].contains("dhat");
253
254 init_time();
255
256 if (is_contact_enabled())
257 {
258 if (args["solver"]["contact"]["friction_iterations"] == 0)
259 {
260 logger().info("specified friction_iterations is 0; disabling friction");
261 args["contact"]["friction_coefficient"] = 0.0;
262 }
263 else if (args["solver"]["contact"]["friction_iterations"] < 0)
264 {
265 args["solver"]["contact"]["friction_iterations"] = std::numeric_limits<int>::max();
266 }
267 if (args["contact"]["friction_coefficient"] == 0.0)
268 {
269 args["solver"]["contact"]["friction_iterations"] = 0;
270 }
271 }
272 else
273 {
274 args["solver"]["contact"]["friction_iterations"] = 0;
275 args["contact"]["friction_coefficient"] = 0;
276 args["contact"]["periodic"] = false;
277 }
278
279 const std::string formulation = this->formulation();
281 assert(assembler->name() == formulation);
282 mass_matrix_assembler = std::make_shared<assembler::Mass>();
283 pure_mass_matrix_assembler = std::make_shared<assembler::HRZMass>();
285
286 if (!other_name.empty())
287 {
290 }
291
292 if (args["solver"]["advanced"]["check_inversion"] == "Conservative")
293 {
294 if (auto elastic_assembler = std::dynamic_pointer_cast<assembler::ElasticityAssembler>(assembler))
295 elastic_assembler->set_use_robust_jacobian();
296 }
297
298 if (!args.contains("preset_problem"))
299 {
300 if (!assembler->is_tensor())
301 problem = std::make_shared<assembler::GenericScalarProblem>("GenericScalar");
302 else
303 problem = std::make_shared<assembler::GenericTensorProblem>("GenericTensor");
304
305 problem->clear();
306 if (!args["time"].is_null())
307 {
308 const auto tmp = R"({"is_time_dependent": true})"_json;
309 problem->set_parameters(tmp, resources);
310 }
311 // important for the BC
312
313 auto bc = args["boundary_conditions"];
314 problem->set_parameters(bc, resources);
315 problem->set_parameters(args["initial_conditions"], resources);
316
317 problem->set_parameters(args["output"], resources);
318 }
319 else
320 {
321 if (args["preset_problem"]["type"] == "Kernel")
322 {
323 problem = std::make_shared<KernelProblem>("Kernel", *assembler);
324 problem->clear();
325 KernelProblem &kprob = *dynamic_cast<KernelProblem *>(problem.get());
326 }
327 else
328 {
329 problem = ProblemFactory::factory().get_problem(args["preset_problem"]["type"]);
330 problem->clear();
331 }
332 // important for the BC
333 problem->set_parameters(args["preset_problem"], resources);
334 }
335
336 problem->set_units(*assembler, units);
337 }
338
339 void State::set_max_threads(const int max_threads)
340 {
341 NThread::get().set_num_threads(max_threads);
342 }
343
345 {
346 if (!is_param_valid(args, "time"))
347 return;
348
349 const double t0 = Units::convert(args["time"]["t0"], units.time());
350 double tend, dt;
351 int time_steps;
352
353 // from "tend", "dt", "time_steps" only two can be used at a time
354 const int num_valid = is_param_valid(args["time"], "tend")
355 + is_param_valid(args["time"], "dt")
356 + is_param_valid(args["time"], "time_steps");
357 if (num_valid < 2)
358 {
359 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
360 }
361 else if (num_valid == 2)
362 {
363 if (is_param_valid(args["time"], "tend"))
364 {
365 tend = Units::convert(args["time"]["tend"], units.time());
366 assert(tend > t0);
367 if (is_param_valid(args["time"], "dt"))
368 {
369 dt = Units::convert(args["time"]["dt"], units.time());
370 assert(dt > 0);
371 time_steps = int(ceil((tend - t0) / dt));
372 assert(time_steps > 0);
373 }
374 else if (is_param_valid(args["time"], "time_steps"))
375 {
376 time_steps = args["time"]["time_steps"];
377 assert(time_steps > 0);
378 dt = (tend - t0) / time_steps;
379 assert(dt > 0);
380 }
381 else
382 {
383 throw std::runtime_error("This code should be unreachable!");
384 }
385 }
386 else if (is_param_valid(args["time"], "dt"))
387 {
388 // tend is already confirmed to be invalid, so time_steps must be valid
389 assert(is_param_valid(args["time"], "time_steps"));
390
391 dt = Units::convert(args["time"]["dt"], units.time());
392 assert(dt > 0);
393
394 time_steps = args["time"]["time_steps"];
395 assert(time_steps > 0);
396
397 tend = t0 + time_steps * dt;
398 }
399 else
400 {
401 // tend and dt are already confirmed to be invalid
402 throw std::runtime_error("This code should be unreachable!");
403 }
404 }
405 else if (num_valid == 3)
406 {
407 tend = Units::convert(args["time"]["tend"], units.time());
408 dt = Units::convert(args["time"]["dt"], units.time());
409 time_steps = args["time"]["time_steps"];
410
411 // Check that all parameters agree
412 if (abs(t0 + dt * time_steps - tend) > 1e-12)
413 {
414 log_and_throw_error("Exactly two of (tend, dt, time_steps) must be specified");
415 }
416 }
417
418 // Store these for use later
419 args["time"]["tend"] = tend;
420 args["time"]["dt"] = dt;
421 args["time"]["time_steps"] = time_steps;
422
424
425 logger().info("t0={}, dt={}, tend={}", t0, dt, tend);
426 }
427
428 void State::set_materials(std::vector<std::shared_ptr<assembler::Assembler>> &assemblers) const
429 {
430 const int size = (assembler->is_tensor() || assembler->is_fluid()) ? mesh->dimension() : 1;
431 for (auto &a : assemblers)
432 a->set_size(size);
433
434 if (!utils::is_param_valid(args, "materials"))
435 return;
436
437 std::vector<int> body_ids(mesh->n_elements());
438 for (int i = 0; i < mesh->n_elements(); ++i)
439 body_ids[i] = mesh->get_body_id(i);
440
441 const polyfem::io::FileSystemIO resources(root_path());
442 for (auto &a : assemblers)
443 a->set_materials(body_ids, args["materials"], units, resources);
444 }
445
447 {
448 const int size = (this->assembler->is_tensor() || this->assembler->is_fluid()) ? this->mesh->dimension() : 1;
449 assembler.set_size(size);
450
451 if (!utils::is_param_valid(args, "materials"))
452 return;
453
454 std::vector<int> body_ids(mesh->n_elements());
455 for (int i = 0; i < mesh->n_elements(); ++i)
456 body_ids[i] = mesh->get_body_id(i);
457
458 const polyfem::io::FileSystemIO resources(root_path());
459 assembler.set_materials(body_ids, args["materials"], units, resources);
460 }
461
462} // namespace polyfem::legacy
void init(const json &json)
Definition Units.cpp:9
double characteristic_length() const
Definition Units.hpp:22
static double convert(const json &val, const std::string &unit_type)
Definition Units.cpp:35
const std::string & time() const
Definition Units.hpp:21
virtual bool is_tensor() const
virtual void set_size(const int size)
Definition Assembler.hpp:67
virtual bool is_fluid() const
static std::shared_ptr< MixedAssembler > make_mixed_assembler(const std::string &formulation)
static std::string other_assembler_name(const std::string &formulation)
static std::shared_ptr< Assembler > make_assembler(const std::string &formulation)
std::shared_ptr< assembler::Problem > problem
current problem, it contains rhs and bc
Definition State.hpp:208
void init(const json &args, const bool strict_validation)
initialize the polyfem solver with a json settings
bool has_dhat
stores if input json contains dhat
Definition State.hpp:715
std::string resolve_input_path(const std::string &path, const bool only_if_exists=false) const
Resolve input path relative to root_path() if the path is not absolute.
std::shared_ptr< assembler::Mass > mass_matrix_assembler
Definition State.hpp:196
std::unique_ptr< mesh::Mesh > mesh
current mesh, it can be a Mesh2D or Mesh3D
Definition State.hpp:578
json args
main input arguments containing all defaults
Definition State.hpp:140
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
std::string resolve_output_path(const std::string &path) const
Resolve output path relative to output_dir if the path is not absolute.
std::string root_path() const
Get the root path for the state (e.g., args["root_path"] or ".")
std::shared_ptr< assembler::Assembler > pressure_assembler
Definition State.hpp:200
std::string formulation() const
return the formulation (checks if the problem is scalar or not and deals with multiphysics)
Definition State.cpp:337
std::shared_ptr< assembler::Assembler > assembler
assemblers
Definition State.hpp:194
void set_materials(std::vector< std::shared_ptr< assembler::Assembler > > &assemblers) const
set the material and the problem dimension
polyfem::io::FileSystemIO rhs_resources_
Definition State.hpp:117
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
std::string output_dir
Directory for output files.
Definition State.hpp:722
void init_time()
initialize time settings if args contains "time"
spdlog::sink_ptr console_sink_
logger sink to stdout
Definition State.hpp:181
std::shared_ptr< assembler::HRZMass > pure_mass_matrix_assembler
Definition State.hpp:197
spdlog::sink_ptr file_sink_
Definition State.hpp:182
bool is_contact_enabled() const
does the simulation have contact
Definition State.hpp:692
std::shared_ptr< assembler::MixedAssembler > mixed_assembler
Definition State.hpp:199
State()
Constructor.
Definition StateInit.cpp:59
static const ProblemFactory & factory()
std::shared_ptr< assembler::Problem > get_problem(const std::string &problem) const
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
std::unique_ptr< const io::ResourceIO > apply_common_params(json &args, const io::ResourceIO &resources)
Definition JSONUtils.cpp:24
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
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