PolyFEM
Loading...
Searching...
No Matches
OptState.cpp
Go to the documentation of this file.
1#include "OptState.hpp"
2
3#include <polyfem/Common.hpp>
4
9
17
20
21#include <polysolve/nonlinear/Solver.hpp>
22
23#include <spdlog/sinks/stdout_color_sinks.h>
24#include <spdlog/sinks/basic_file_sink.h>
25#include <spdlog/sinks/ostream_sink.h>
26
27#include <Eigen/Core>
28
29#include <algorithm>
30#include <filesystem>
31#include <fstream>
32#include <functional>
33#include <limits>
34#include <memory>
35#include <optional>
36#include <string>
37#include <vector>
38
39namespace spdlog::level
40{
42 spdlog::level::level_enum,
43 {{spdlog::level::level_enum::trace, "trace"},
44 {spdlog::level::level_enum::debug, "debug"},
45 {spdlog::level::level_enum::info, "info"},
46 {spdlog::level::level_enum::warn, "warning"},
47 {spdlog::level::level_enum::err, "error"},
48 {spdlog::level::level_enum::critical, "critical"},
49 {spdlog::level::level_enum::off, "off"},
50 {spdlog::level::level_enum::trace, 0},
51 {spdlog::level::level_enum::debug, 1},
52 {spdlog::level::level_enum::info, 2},
53 {spdlog::level::level_enum::warn, 3},
54 {spdlog::level::level_enum::err, 3},
55 {spdlog::level::level_enum::critical, 4},
56 {spdlog::level::level_enum::off, 5}})
57}
58
59namespace polyfem
60{
61 namespace
62 {
63 bool is_failed_status(const polysolve::nonlinear::Status status)
64 {
65 using Status = polysolve::nonlinear::Status;
66 return status == Status::NanEncountered
67 || status == Status::NotDescentDirection
68 || status == Status::LineSearchFailed
69 || status == Status::UpdateDirectionFailed
70 || status == Status::NotStarted
71 || status == Status::Continue;
72 }
73
74 std::string parse_remeshing_trigger(const json &args)
75 {
76 const json &remeshing = args["remeshing"];
77 if (remeshing.is_null())
78 return "none";
79
80 const json &trigger = remeshing["trigger"];
81 if (trigger.is_null())
82 return "none";
83
84 const bool periodic = trigger.contains("periodic") && trigger["periodic"].is_object();
85 const bool scaled_jacobian = trigger.contains("scaled_jacobian") && trigger["scaled_jacobian"].is_object();
86 if (periodic && scaled_jacobian)
87 return "multiple";
88 if (periodic)
89 return "periodic";
90 if (scaled_jacobian)
91 return "scaled_jacobian";
92 return "none";
93 }
94
98 std::vector<json> load_state_jsons(const std::string &root_path, const json &args)
99 {
100 std::vector<json> result;
101 for (int i = 0; i < args.size(); ++i)
102 {
103 json state_args;
104 const std::string state_path = utils::resolve_path(args[i]["path"], root_path, false);
105 std::ifstream file(state_path);
106 if (!file.is_open())
107 log_and_throw_adjoint_error("Can't find json for varform::DifferentiableVarForm {}", i);
108 file >> state_args;
109 state_args["root_path"] = state_path;
110 result.push_back(std::move(state_args));
111 }
112 return result;
113 }
114
115 void validate_remeshing_json(const json &args)
116 {
117 const std::string trigger = parse_remeshing_trigger(args);
118 if (trigger == "multiple")
119 log_and_throw_error("Optimization remeshing requires exactly one trigger: periodic or scaled_jacobian.");
120 const json &remeshing = args["remeshing"];
121 const int state = remeshing["state"];
122 if (state >= args["states"].size())
123 log_and_throw_error("Optimization remeshing state index {} is out of range.", state);
124
125 for (const auto &variable : args["variable_to_simulation"])
126 {
127 if (variable["type"] != "shape")
128 continue;
129
130 const json &variable_state = variable["state"];
131 const bool targets_state = variable_state.is_array()
132 ? std::find(variable_state.begin(), variable_state.end(), state) != variable_state.end()
133 : variable_state.get<int>() == state;
134 if (!targets_state)
135 continue;
136
137 const json &selection = variable["active_geometry_nodes"];
138 const bool selects_all_nodes = selection.is_array() && selection.empty();
139 if (selects_all_nodes || selection.is_object())
140 continue;
141
143 "Optimization remeshing changes mesh vertex numbering. "
144 "Shape variable {} targeting state {} must use a geometry-based "
145 "active_geometry_nodes selector (interior, boundary, or "
146 "boundary_excluding_surface), or an empty selector for all nodes.",
147 variable.value("name", "shape"), state);
148 }
149 }
150
151 void validate_remeshing_geometry(const json &state_args, const int geometry)
152 {
153 if (geometry < 0 || geometry >= state_args["geometry"].size())
154 log_and_throw_error("Optimization remeshing geometry index {} is out of range.", geometry);
155 if (state_args["geometry"][geometry].value("is_obstacle", false))
156 log_and_throw_error("Optimization remeshing geometry {} is an obstacle.", geometry);
157 }
158
160 double minimum_scaled_jacobian(
161 const Eigen::MatrixXd &vertices,
162 const Eigen::MatrixXi &elements)
163 {
164 Eigen::VectorXd quality;
165 solver::AdjointTools::scaled_jacobian(vertices, elements, quality);
166 return quality.minCoeff();
167 }
168
170 double minimum_scaled_jacobian(const varform::DifferentiableVarForm &varform)
171 {
172 Eigen::MatrixXd vertices;
173 Eigen::MatrixXi elements;
174 varform.get_vertices(vertices);
175 varform.get_elements(elements);
176 return minimum_scaled_jacobian(vertices, elements);
177 }
178
185 bool remesh_and_write(
186 const OptState &opt_state,
187 const int state,
188 const int body_id,
189 const std::filesystem::path &path,
190 const std::optional<double> quality_threshold)
191 {
192 mesh::MmgOptions options;
193 options.optim = true;
194
195 Eigen::MatrixXd vertices;
196 Eigen::MatrixXi elements;
197 opt_state.varforms[state]->get_vertices(vertices);
198 opt_state.varforms[state]->get_elements(elements);
199
200 Eigen::MatrixXd remeshed_vertices;
201 Eigen::MatrixXi remeshed_boundary;
202 Eigen::MatrixXi remeshed_elements;
203 bool has_shape_variable = false;
204 for (const auto &var2sim : opt_state.variable_to_simulations.data)
205 {
206 const auto shape_v2s = std::dynamic_pointer_cast<solver::ShapeVariableToSimulation>(var2sim);
207 if (!shape_v2s || !shape_v2s->affects_varform(*opt_state.varforms[state]))
208 continue;
209 has_shape_variable = true;
210
211 for (int other_state = 0; other_state < opt_state.varforms.size(); ++other_state)
212 {
213 if (other_state != state
214 && shape_v2s->affects_varform(*opt_state.varforms[other_state]))
215 {
217 "Optimization remeshing cannot remesh state {} while its shape variable also affects state {}. This is not supported currently.",
218 state, other_state);
219 }
220 }
221 }
222 if (!has_shape_variable)
223 {
224 logger().error("Can not remesh state {} because it's not affected by shape variables. No reason to remesh if you are not doing shape optimization.", state);
225 return false;
226 }
227
228 const mesh::Mesh &mesh = opt_state.varforms[state]->get_mesh();
229 bool success = false;
230 if (mesh.dimension() == 2)
231 {
232 success = mesh::remesh_2d(
233 vertices, elements, remeshed_vertices, remeshed_elements,
234 options);
235 }
236 else
237 {
238 success = mesh::remesh_3d(
239 vertices, elements, remeshed_vertices, remeshed_boundary,
240 remeshed_elements, options);
241 }
242 if (!success)
243 {
244 return false;
245 }
246
247 const double input_quality = minimum_scaled_jacobian(vertices, elements);
248 const double output_quality = minimum_scaled_jacobian(remeshed_vertices, remeshed_elements);
249 logger().info(
250 "Optimization remeshing minimum scaled Jacobian: {} -> {}.",
251 input_quality, output_quality);
252 // If trigger mode is scaled jacobian, check quality immediately after remeshing.
253 if (quality_threshold.has_value() && output_quality <= *quality_threshold)
254 {
256 "MMG remeshing failed to satisfy minimum scaled Jacobian threshold: "
257 "input={}, output={}, required > {}. This is because MMG use a different convergence criteria, please switch remeshing trigger to periodic.",
258 input_quality, output_quality, *quality_threshold);
259 }
260
262 path.string(), remeshed_vertices, remeshed_elements,
263 std::vector<int>(remeshed_elements.rows(), body_id),
264 opt_state.varforms[state]->get_mesh().is_volume(), false);
265 return true;
266 }
267 } // namespace
268
270 {
271 }
272
277
278 int OptState::run(json input_args, const bool strict_validation)
279 {
280 input_args = solver::AdjointOptUtils::apply_opt_json_spec(input_args, strict_validation);
281
282 std::string mode = parse_remeshing_trigger(input_args);
283 bool remeshing_enabled = mode != "none";
284 if (remeshing_enabled)
285 validate_remeshing_json(input_args);
286
287 std::filesystem::path output_root = utils::resolve_path(
288 input_args["output"]["directory"], input_args["root_path"], false);
289 int max_restarts = remeshing_enabled
290 ? input_args["remeshing"]["max_restarts"].get<int>()
291 : 0;
292 state_args = load_state_jsons(input_args["root_path"], input_args["states"]);
293
294 int state = 0;
295 int geometry = 0;
296 int body_id = 0;
297 if (remeshing_enabled)
298 {
299 state = input_args["remeshing"]["state"].get<int>();
300 geometry = input_args["remeshing"]["geometry"].get<int>();
301 validate_remeshing_geometry(state_args[state], geometry);
302 body_id = state_args[state]["geometry"][geometry].value("volume_selection", 0);
303 }
304
305 int remesh_count = 0;
306 while (true)
307 {
308 remeshing_requested_ = false;
309
310 // Each remeshing round use a unique output directory.
311 json round_args = input_args;
312 if (remeshing_enabled)
313 {
314 const std::filesystem::path round_dir = output_root / fmt::format("remesh_round_{:d}", remesh_count);
315 round_args["output"]["directory"] = round_dir.string();
316 state_args[state]["output"]["directory"] = round_dir.string();
317 }
318
319 init(round_args, strict_validation);
320 create_varforms(args["solver"]["max_threads"].get<int>());
323
324 Eigen::VectorXd x;
326 // Dry run mode. Compute objective and exit immediately.
327 if (args["compute_objective"].get<bool>())
328 {
329 logger().info("Objective is {}", eval(x));
330 return EXIT_SUCCESS;
331 }
332
333 const polysolve::nonlinear::Status status = solve(x);
334 if (is_failed_status(status))
335 {
336 logger().error("Optimization failed: {}.", polysolve::nonlinear::status_message(status));
337 return EXIT_FAILURE;
338 }
340 {
341 return EXIT_SUCCESS;
342 }
343 if (remesh_count >= max_restarts)
344 {
345 logger().info("Reached the optimization remeshing limit of {} restart(s).", max_restarts);
346 return EXIT_SUCCESS;
347 }
348
349 const std::filesystem::path round_dir = output_root / fmt::format("remesh_round_{:d}", remesh_count);
350 const std::filesystem::path remeshed_path = std::filesystem::absolute(round_dir / "remeshed.msh");
351 const std::optional<double> quality_threshold = mode == "scaled_jacobian"
352 ? std::optional<double>(args["remeshing"]["trigger"]["scaled_jacobian"]["quality_threshold"].get<double>())
353 : std::nullopt;
354 if (!remesh_and_write(
355 *this, state, body_id,
356 remeshed_path, quality_threshold))
357 {
358 logger().error("MMG failed to produce a valid optimization restart mesh.");
359 return EXIT_FAILURE;
360 }
361
362 state_args[state]["geometry"][geometry]["mesh"] = remeshed_path.string();
363 logger().info("Restarting optimization from remeshed mesh {}.", remeshed_path.string());
364 ++remesh_count;
365 }
366 }
367
369 const std::string &log_file,
370 const spdlog::level::level_enum log_level,
371 const spdlog::level::level_enum file_log_level,
372 const bool is_quiet)
373 {
374 std::vector<spdlog::sink_ptr> sinks;
375
376 if (!is_quiet)
377 {
378 console_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
379 sinks.emplace_back(console_sink_);
380 }
381
382 if (!log_file.empty())
383 {
384 file_sink_ = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file, /*truncate=*/true);
385 // Set the file sink separately from the console so it can save all messages
386 file_sink_->set_level(file_log_level);
387 sinks.push_back(file_sink_);
388 }
389
390 init_logger(sinks, log_level);
391 spdlog::flush_every(std::chrono::seconds(3));
392 }
393
394 void OptState::init_logger(std::ostream &os, const spdlog::level::level_enum log_level)
395 {
396 std::vector<spdlog::sink_ptr> sinks;
397 sinks.emplace_back(std::make_shared<spdlog::sinks::ostream_sink_mt>(os, false));
398 init_logger(sinks, log_level);
399 }
400
402 const std::vector<spdlog::sink_ptr> &sinks,
403 const spdlog::level::level_enum log_level)
404 {
405 set_adjoint_logger(std::make_shared<spdlog::logger>("adjoint-polyfem", sinks.begin(), sinks.end()));
406
407 // Set the logger at the lowest level, so all messages are passed to the sinks
408 adjoint_logger().set_level(spdlog::level::trace);
409 set_log_level(log_level);
410 }
411
412 void OptState::set_log_level(const spdlog::level::level_enum log_level)
413 {
414 adjoint_logger().set_level(log_level);
415 if (console_sink_)
416 console_sink_->set_level(log_level); // Shared by all loggers
417 }
418
419 void OptState::init(const json &p_args_in, const bool strict_validation)
420 {
421 strict_validation_ = strict_validation;
422 json args_in = p_args_in; // mutable copy
423 args = solver::AdjointOptUtils::apply_opt_json_spec(args_in, strict_validation);
424
425 // Save output directory and resolve output paths dynamically
426 const std::string output_dir = utils::resolve_path(args["output"]["directory"], root_path(), false);
427 if (!output_dir.empty())
428 {
429 std::filesystem::create_directories(output_dir);
430 }
431 this->output_dir = output_dir;
432
433 std::string out_path_log = args["output"]["log"]["path"];
434 if (!out_path_log.empty())
435 {
436 out_path_log = utils::resolve_path(out_path_log, root_path(), false);
437 }
438
440 out_path_log,
441 args["output"]["log"]["level"],
442 args["output"]["log"]["file_level"],
443 args["output"]["log"]["quiet"]);
444
445 adjoint_logger().info("Saving adjoint output to {}", output_dir);
446
447 const int thread_in = args["solver"]["max_threads"];
449 }
450
451 void OptState::create_varforms(const int max_threads)
452 {
453 if (state_args.empty())
454 state_args = load_state_jsons(root_path(), args["states"]);
455
456 size_t threads = max_threads <= 0
457 ? std::numeric_limits<unsigned int>::max()
458 : max_threads;
459 varforms.clear();
460 for (int i = 0; i < state_args.size(); ++i)
461 {
462 json cur_args = state_args[i];
463 if (!args["output"]["log"].empty())
464 cur_args["output"]["log"].merge_patch(args["output"]["log"]);
465 varforms.push_back(from_json::build_differentiable_varform(cur_args, threads));
466 }
467
468 diff_caches.resize(varforms.size());
469 for (auto &diff_cache : diff_caches)
470 {
471 diff_cache = std::make_shared<DiffCache>();
472 }
473
475
477 }
478
480 {
481 for (int i = 0; i < varforms.size(); ++i)
482 {
483 const varform::DifferentiableVarForm &varform = *varforms[i];
484 if (!varform.solve_data())
485 {
487 "varform::DifferentiableVarForm {} ({}) does not expose solve data required by optimization.",
488 i, varform.name());
489 }
490
491 // No transient linear support.
492 if (varform.get_problem().is_time_dependent() && varform.is_problem_linear())
493 {
495 "varform::DifferentiableVarForm {}: transient linear problem is not supported in optimization.", i);
496 }
497
498 if (varform.is_contact_enabled())
499 {
500 // No non-convergent contact formulation support.
501 if (!varform.get_args()["contact"]["use_gcp_formulation"].get<bool>()
502 && !varform.get_args()["contact"]["use_convergent_formulation"].get<bool>())
503 {
505 "varform::DifferentiableVarForm {}: non-convergent contact formulation is not supported in optimization.", i);
506 }
507
508 // No non-const barrier stiffness support.
509 if (varform.get_args()["/solver/contact/barrier_stiffness"_json_pointer].is_string())
510 {
512 "varform::DifferentiableVarForm {}: only constant barrier stiffness is supported in optimization.", i);
513 }
514 }
515
516 // No non-const boundary support.
517 if (varform.get_args().contains("boundary_conditions") && varform.get_args()["boundary_conditions"].contains("rhs"))
518 {
519 const json &rhs = varform.get_args()["boundary_conditions"]["rhs"];
520 if (rhs.is_string() || (rhs.is_array() && rhs.size() > 0 && rhs[0].is_string()))
521 {
523 "varform::DifferentiableVarForm {}: only constant rhs over space is supported in optimization.", i);
524 }
525 }
526
527 // No high order geometric basis support.
528 for (const auto &element_bases : varform.primary_space().geometry_basis_list())
529 {
530 for (const auto &basis : element_bases.bases)
531 {
532 if (basis.order() > 1)
533 {
535 "varform::DifferentiableVarForm {}: high-order geometry basis is not supported in optimization.", i);
536 }
537 }
538 }
539 }
540 }
541
543 {
544 const json &parameters = args["parameters"];
545 bool is_auto = parameters.is_string() && parameters.get<std::string>() == "auto";
546
547 // Auto mode.
548 // In auto mode optimization parameters dof is inferred. No need to parse json.
549 if (is_auto)
550 {
551 if (args["variable_to_simulation"].size() != 1)
552 {
554 "Auto parameters are only supported with a single variable to simulation.");
555 }
556
557 for (auto &composition : utils::json_as_array(args["variable_to_simulation"][0]["composition"]))
558 {
559 if (composition["type"].get<std::string>() == "slice")
560 {
561 log_and_throw_adjoint_error("Auto parameters do not support slice maps in composition.");
562 }
563 }
564
566
567 ndof = variable_to_simulations.data[0]->inverse_dof();
569
570 return;
571 }
572
573 // Manual mode.
574 // We need to parse optimization parameter blocks to load dof first.
575 variable_sizes.clear();
576 ndof = 0;
577 for (const auto &arg : args["parameters"])
578 {
580 ndof += size;
581 variable_sizes.push_back(size);
582 }
583
584 /* variable to simulations */
586 args["variable_to_simulation"], varforms, diff_caches, variable_sizes);
587
588 // Verify varaible dof.
589 for (int i = 0; i < variable_to_simulations.data.size(); ++i)
590 {
591 auto &var2sim = variable_to_simulations.data[i];
592 int inv_dof = var2sim->inverse_dof();
593 if (inv_dof != ndof)
594 {
596 "VariableToSimulation {} (type {}) expects {} DOF, but parameters define {} DOF.",
597 i, var2sim->name(), inv_dof, ndof);
598 }
599 }
600 }
601
603 {
604 /* forms */
605 std::shared_ptr<solver::AdjointForm> obj = from_json::build_form(
607
608 /* stopping conditions */
609 std::vector<std::shared_ptr<solver::AdjointForm>> stopping_conditions;
610 for (const auto &arg : args["stopping_conditions"])
611 stopping_conditions.push_back(
613
614 std::function<bool()> remeshing_trigger;
615 const std::string mode = parse_remeshing_trigger(args);
616 if (mode == "periodic")
617 {
618 int period = args["remeshing"]["trigger"]["periodic"]["period"];
619 // this capture is for remeshing_requested_ specifically.
620 remeshing_trigger = [this, period, iter = 0]() mutable {
621 remeshing_requested_ = (iter % period) == 0;
622 ++iter;
624 adjoint_logger().debug(
625 "Periodic optimization remeshing triggered after {} accepted iteration(s).", iter);
627 };
628 }
629 else if (mode == "scaled_jacobian")
630 {
631 int state = args["remeshing"]["state"];
632 double threshold = args["remeshing"]["trigger"]["scaled_jacobian"]["quality_threshold"];
633 auto varform = varforms[state];
634 // this capture is for remeshing_requested_ specifically.
635 remeshing_trigger = [this, varform, threshold]() {
636 double quality = minimum_scaled_jacobian(*varform);
637 adjoint_logger().debug(
638 "Minimum scaled Jacobian: {} (remesh threshold: {}).",
639 quality, threshold);
640 remeshing_requested_ = quality <= threshold;
642 };
643 }
644
645 nl_problem = std::make_unique<solver::AdjointNLProblem>(
646 obj, stopping_conditions, variable_to_simulations, varforms, diff_caches,
647 args, std::move(remeshing_trigger));
648 }
649
656
657 double OptState::eval(Eigen::VectorXd &x) const
658 {
659 nl_problem->solution_changed(x);
660 return nl_problem->value(x);
661 }
662
663 polysolve::nonlinear::Status OptState::solve(Eigen::VectorXd &x)
664 {
666 args["solver"]["nonlinear"],
667 args["solver"]["linear"],
668 args["solver"]["advanced"]["characteristic_length"],
670 nl_problem->normalize_forms();
671 nl_solver->minimize(*nl_problem, x);
672 return nl_solver->status();
673 }
674} // namespace polyfem
int x
void initial_guess(Eigen::VectorXd &x)
Definition OptState.cpp:650
void set_log_level(const spdlog::level::level_enum log_level)
change log level
Definition OptState.cpp:412
json args
main input arguments containing all defaults
Definition OptState.hpp:48
std::vector< std::shared_ptr< varform::DifferentiableVarForm > > varforms
Variational formulations used by the optimization.
Definition OptState.hpp:89
void init(const json &args, const bool strict_validation)
initialize the polyfem solver with a json settings
Definition OptState.cpp:419
int run(json args, const bool strict_validation)
Run optimization, including remeshing restarts when the selected trigger requests them.
Definition OptState.cpp:278
std::vector< std::shared_ptr< DiffCache > > diff_caches
Definition OptState.hpp:91
spdlog::sink_ptr file_sink_
Definition OptState.hpp:117
std::string root_path() const
Definition OptState.hpp:102
void check_unsupported() const
Check and throw if any forward simulation varform::DifferentiableVarForm is not supported.
Definition OptState.cpp:479
double eval(Eigen::VectorXd &x) const
Definition OptState.cpp:657
std::vector< int > variable_sizes
variables
Definition OptState.hpp:94
std::unique_ptr< solver::AdjointNLProblem > nl_problem
Definition OptState.hpp:99
std::string output_dir
Directory for output files.
Definition OptState.hpp:128
std::vector< json > state_args
Definition OptState.hpp:90
spdlog::sink_ptr console_sink_
logger sink to stdout
Definition OptState.hpp:116
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 OptState.cpp:368
solver::VariableToSimulationGroup variable_to_simulations
Definition OptState.hpp:97
void create_varforms(const int max_threads=-1)
Create the optimization variational formulations.
Definition OptState.cpp:451
polysolve::nonlinear::Status solve(Eigen::VectorXd &x)
Definition OptState.cpp:663
void init_variables()
init variables
Definition OptState.cpp:542
OptState()
Constructor.
Definition OptState.cpp:273
virtual bool is_time_dependent() const
Definition Problem.hpp:62
static void write(const std::string &path, const mesh::Mesh &mesh, const bool binary)
saves the mesh
Definition MshWriter.cpp:7
std::vector< std::shared_ptr< VariableToSimulation > > data
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
Optimization-facing interface implemented by differentiated VarForm adapters.
virtual std::string name() const =0
virtual solver::SolveData * solve_data()=0
virtual assembler::Problem & get_problem()=0
virtual bool is_contact_enabled() const =0
virtual const FESpace & primary_space() const =0
const std::vector< basis::ElementBases > & geometry_basis_list() const
Definition FESpace.hpp:115
list vertices
Definition p_bases.py:238
std::shared_ptr< solver::AdjointForm > build_form(const json &args, const solver::VariableToSimulationGroup &var2sim, const std::vector< std::shared_ptr< varform::DifferentiableVarForm > > &varforms, const std::vector< std::shared_ptr< DiffCache > > &diff_caches)
std::shared_ptr< varform::DifferentiableVarForm > build_differentiable_varform(const json &args, const size_t max_threads)
solver::VariableToSimulationGroup build_variable_to_simulation_group(const json &args, const std::vector< std::shared_ptr< varform::DifferentiableVarForm > > &varforms, const std::vector< std::shared_ptr< DiffCache > > &diff_caches, const std::vector< int > &variable_sizes)
bool scaled_jacobian(Mesh3DStorage &hmi, Mesh_Quality &mq)
NLOHMANN_JSON_SERIALIZE_ENUM(CollisionProxyTessellation, {{CollisionProxyTessellation::REGULAR, "regular"}, {CollisionProxyTessellation::IRREGULAR, "irregular"}})
void scaled_jacobian(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, Eigen::VectorXd &quality)
std::string resolve_path(const std::string &path, const std::string &input_file_path, const bool only_if_exists=false)
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:41
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
spdlog::logger & adjoint_logger()
Retrieves the current logger for adjoint.
Definition Logger.cpp:30
nlohmann::json json
Definition Common.hpp:9
void set_adjoint_logger(std::shared_ptr< spdlog::logger > p_logger)
Setup a logger object to be used by adjoint Polyfem.
Definition Logger.cpp:68
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:79
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73
static int compute_variable_size(const json &args, const std::vector< std::shared_ptr< varform::DifferentiableVarForm > > &varforms)
static json apply_opt_json_spec(const json &input_args, bool strict_validation)
static Eigen::VectorXd inverse_evaluation(const json &args, const int ndof, const std::vector< int > &variable_sizes, VariableToSimulationGroup &var2sim)
static std::shared_ptr< polysolve::nonlinear::Solver > make_nl_solver(const json &solver_params, const json &linear_solver_params, const double characteristic_length, const bool strict_validation=true)