PolyFEM
Loading...
Searching...
No Matches
Optimizations.cpp
Go to the documentation of this file.
1#include "Optimizations.hpp"
2
4#include <jse/jse.h>
5
7
16
19
21
25
26#include <polysolve/nonlinear/BoxConstraintSolver.hpp>
27
28namespace spdlog::level
29{
31 spdlog::level::level_enum,
32 {{spdlog::level::level_enum::trace, "trace"},
33 {spdlog::level::level_enum::debug, "debug"},
34 {spdlog::level::level_enum::info, "info"},
35 {spdlog::level::level_enum::warn, "warning"},
36 {spdlog::level::level_enum::err, "error"},
37 {spdlog::level::level_enum::critical, "critical"},
38 {spdlog::level::level_enum::off, "off"},
39 {spdlog::level::level_enum::trace, 0},
40 {spdlog::level::level_enum::debug, 1},
41 {spdlog::level::level_enum::info, 2},
42 {spdlog::level::level_enum::warn, 3},
43 {spdlog::level::level_enum::err, 3},
44 {spdlog::level::level_enum::critical, 4},
45 {spdlog::level::level_enum::off, 5}})
46}
47
48namespace polyfem::solver
49{
50 namespace
51 {
52 bool load_json(const std::string &json_file, json &out)
53 {
54 std::ifstream file(json_file);
55
56 if (!file.is_open())
57 return false;
58
59 file >> out;
60
61 out["root_path"] = json_file;
62
63 return true;
64 }
65 } // namespace
66
67 std::shared_ptr<polysolve::nonlinear::Solver> AdjointOptUtils::make_nl_solver(const json &solver_params, const json &linear_solver_params, const double characteristic_length)
68 {
69 auto names = polysolve::nonlinear::Solver::available_solvers();
70 if (std::find(names.begin(), names.end(), solver_params["solver"]) != names.end())
71 return polysolve::nonlinear::Solver::create(solver_params, linear_solver_params, characteristic_length, adjoint_logger());
72
73 names = polysolve::nonlinear::BoxConstraintSolver::available_solvers();
74 if (std::find(names.begin(), names.end(), solver_params["solver"]) != names.end())
75 return polysolve::nonlinear::BoxConstraintSolver::create(solver_params, linear_solver_params, characteristic_length, adjoint_logger());
76
77 log_and_throw_adjoint_error("Invalid nonlinear solver name!");
78 }
79
80 std::shared_ptr<AdjointForm> AdjointOptUtils::create_form(const json &args, const VariableToSimulationGroup &var2sim, const std::vector<std::shared_ptr<State>> &states)
81 {
82 std::shared_ptr<AdjointForm> obj;
83 if (args.is_array())
84 {
85 std::vector<std::shared_ptr<AdjointForm>> forms;
86 for (const auto &arg : args)
87 forms.push_back(create_form(arg, var2sim, states));
88
89 obj = std::make_shared<SumCompositeForm>(var2sim, forms);
90 }
91 else
92 {
93 const std::string type = args["type"];
94 if (type == "transient_integral")
95 {
96 std::shared_ptr<StaticForm> static_obj = std::dynamic_pointer_cast<StaticForm>(create_form(args["static_objective"], var2sim, states));
97 if (!static_obj)
98 log_and_throw_adjoint_error("Transient integral objective must have a static objective!");
99 const auto &state = states[args["state"]];
100 obj = std::make_shared<TransientForm>(var2sim, state->args["time"]["time_steps"], state->args["time"]["dt"], args["integral_type"], args["steps"].get<std::vector<int>>(), static_obj);
101 }
102 else if (type == "power")
103 {
104 std::shared_ptr<AdjointForm> obj_aux = create_form(args["objective"], var2sim, states);
105 obj = std::make_shared<PowerForm>(obj_aux, args["power"]);
106 }
107 else if (type == "divide")
108 {
109 std::shared_ptr<AdjointForm> obj1 = create_form(args["objective"][0], var2sim, states);
110 std::shared_ptr<AdjointForm> obj2 = create_form(args["objective"][1], var2sim, states);
111 std::vector<std::shared_ptr<AdjointForm>> objs({obj1, obj2});
112 obj = std::make_shared<DivideForm>(objs);
113 }
114 else if (type == "plus-const")
115 {
116 obj = std::make_shared<PlusConstCompositeForm>(create_form(args["objective"], var2sim, states), args["value"]);
117 }
118 else if (type == "compliance")
119 {
120 obj = std::make_shared<ComplianceForm>(var2sim, *(states[args["state"]]), args);
121 }
122 else if (type == "acceleration")
123 {
124 obj = std::make_shared<AccelerationForm>(var2sim, *(states[args["state"]]), args);
125 }
126 else if (type == "kinetic")
127 {
128 obj = std::make_shared<AccelerationForm>(var2sim, *(states[args["state"]]), args);
129 }
130 else if (type == "target")
131 {
132 std::shared_ptr<TargetForm> tmp = std::make_shared<TargetForm>(var2sim, *(states[args["state"]]), args);
133 auto reference_cached = args["reference_cached_body_ids"].get<std::vector<int>>();
134 tmp->set_reference(states[args["target_state"]], std::set(reference_cached.begin(), reference_cached.end()));
135 obj = tmp;
136 }
137 else if (type == "center-target")
138 {
139 obj = std::make_shared<BarycenterTargetForm>(var2sim, args, states[args["state"]], states[args["target_state"]]);
140 }
141 else if (type == "function-target")
142 {
143 std::shared_ptr<TargetForm> tmp = std::make_shared<TargetForm>(var2sim, *(states[args["state"]]), args);
144 tmp->set_reference(args["target_function"], args["target_function_gradient"]);
145 obj = tmp;
146 }
147 else if (type == "position")
148 {
149 obj = std::make_shared<PositionForm>(var2sim, *(states[args["state"]]), args);
150 }
151 else if (type == "stress")
152 {
153 obj = std::make_shared<StressForm>(var2sim, *(states[args["state"]]), args);
154 }
155 else if (type == "stress_norm")
156 {
157 obj = std::make_shared<StressNormForm>(var2sim, *(states[args["state"]]), args);
158 }
159 else if (type == "elastic_energy")
160 {
161 obj = std::make_shared<ElasticEnergyForm>(var2sim, *(states[args["state"]]), args);
162 }
163 else if (type == "max_stress")
164 {
165 obj = std::make_shared<MaxStressForm>(var2sim, *(states[args["state"]]), args);
166 }
167 else if (type == "volume")
168 {
169 obj = std::make_shared<VolumeForm>(var2sim, *(states[args["state"]]), args);
170 }
171 else if (type == "soft_constraint")
172 {
173 std::vector<std::shared_ptr<AdjointForm>> forms({create_form(args["objective"], var2sim, states)});
174 Eigen::VectorXd bounds = args["soft_bound"];
175 obj = std::make_shared<InequalityConstraintForm>(forms, bounds, args["power"]);
176 }
177 else if (type == "AMIPS")
178 {
179 obj = std::make_shared<AMIPSForm>(var2sim, *(states[args["state"]]));
180 }
181 else if (type == "boundary_smoothing")
182 {
183 obj = std::make_shared<BoundarySmoothingForm>(var2sim, *(states[args["state"]]), args["scale_invariant"], args["power"]);
184 }
185 else if (type == "collision_barrier")
186 {
187 obj = std::make_shared<CollisionBarrierForm>(var2sim, *(states[args["state"]]), args["dhat"]);
188 }
189 else if (type == "deformed_collision_barrier")
190 {
191 obj = std::make_shared<DeformedCollisionBarrierForm>(var2sim, *(states[args["state"]]), args["dhat"]);
192 }
193 else if (type == "parametrized_product")
194 {
195 if (args["parametrization"].contains("parameter_index"))
196 log_and_throw_adjoint_error("Parametrizations in parametrized forms don't support parameter_index!");
197
198 std::vector<std::shared_ptr<Parametrization>> map_list;
199 for (const auto &arg : args["parametrization"])
200 map_list.push_back(create_parametrization(arg, states, {}));
201 obj = std::make_shared<ParametrizedProductForm>(CompositeParametrization(std::move(map_list)));
202 }
203 else
204 log_and_throw_adjoint_error("Objective not implemented!");
205
206 obj->set_weight(args["weight"]);
207 if (args["print_energy"].get<std::string>() != "")
208 obj->enable_energy_print(args["print_energy"]);
209 }
210
211 return obj;
212 }
213
214 std::shared_ptr<Parametrization> AdjointOptUtils::create_parametrization(const json &args, const std::vector<std::shared_ptr<State>> &states, const std::vector<int> &variable_sizes)
215 {
216 std::shared_ptr<Parametrization> map;
217 const std::string type = args["type"];
218 if (type == "per-body-to-per-elem")
219 {
220 map = std::make_shared<PerBody2PerElem>(*(states[args["state"]]->mesh));
221 }
222 else if (type == "per-body-to-per-node")
223 {
224 map = std::make_shared<PerBody2PerNode>(*(states[args["state"]]->mesh), states[args["state"]]->bases, states[args["state"]]->n_bases);
225 }
226 else if (type == "E-nu-to-lambda-mu")
227 {
228 map = std::make_shared<ENu2LambdaMu>(args["is_volume"]);
229 }
230 else if (type == "slice")
231 {
232 if (args.contains("from") && args.contains("to"))
233 {
234 assert(args["from"] != -1);
235 assert(args["to"] != -1);
236 map = std::make_shared<SliceMap>(args["from"], args["to"], args["last"]);
237 }
238 else if (args.contains("parameter_index"))
239 {
240 assert(args["parameter_index"] != -1);
241 int idx = args["parameter_index"].get<int>();
242 int from, to, last;
243 int cumulative = 0;
244 for (int i = 0; i < variable_sizes.size(); ++i)
245 {
246 if (i == idx)
247 {
248 from = cumulative;
249 to = from + variable_sizes[i];
250 }
251 cumulative += variable_sizes[i];
252 }
253 last = cumulative;
254 map = std::make_shared<SliceMap>(from, to, last);
255 }
256 else
257 log_and_throw_adjoint_error("Incorrect spec for SliceMap!");
258 }
259 else if (type == "exp")
260 {
261 map = std::make_shared<ExponentialMap>();
262 }
263 else if (type == "scale")
264 {
265 map = std::make_shared<Scaling>(args["value"]);
266 }
267 else if (type == "power")
268 {
269 map = std::make_shared<PowerMap>(args["power"]);
270 }
271 else if (type == "append-values")
272 {
273 Eigen::VectorXd vals = args["values"];
274 map = std::make_shared<InsertConstantMap>(vals);
275 }
276 else if (type == "append-const")
277 {
278 map = std::make_shared<InsertConstantMap>(args["size"], args["value"], args["start"]);
279 }
280 else if (type == "linear-filter")
281 {
282 map = std::make_shared<LinearFilter>(*(states[args["state"]]->mesh), args["radius"]);
283 }
284 else
285 log_and_throw_adjoint_error("Unkown parametrization!");
286
287 return map;
288 }
289
290 std::unique_ptr<VariableToSimulation> AdjointOptUtils::create_variable_to_simulation(const json &args, const std::vector<std::shared_ptr<State>> &states, const std::vector<int> &variable_sizes)
291 {
292 const std::string type = args["type"];
293
294 std::vector<std::shared_ptr<State>> cur_states;
295 if (args["state"].is_array())
296 for (int i : args["state"])
297 cur_states.push_back(states[i]);
298 else
299 cur_states.push_back(states[args["state"]]);
300
301 const std::string composite_map_type = args["composite_map_type"];
302 Eigen::VectorXi output_indexing;
303 if (composite_map_type == "none")
304 {
305 }
306 else if (composite_map_type == "interior")
307 {
308 assert(type == "shape");
309 VariableToInteriorNodes variable_to_node(*cur_states[0], args["volume_selection"][0]);
310 output_indexing = variable_to_node.get_output_indexing();
311 }
312 else if (composite_map_type == "boundary")
313 {
314 assert(type == "shape");
315 VariableToBoundaryNodes variable_to_node(*cur_states[0], args["surface_selection"][0]);
316 output_indexing = variable_to_node.get_output_indexing();
317 }
318 else if (composite_map_type == "boundary_excluding_surface")
319 {
320 assert(type == "shape");
321 const std::vector<int> excluded_surfaces = args["surface_selection"];
322 VariableToBoundaryNodesExclusive variable_to_node(*cur_states[0], excluded_surfaces);
323 output_indexing = variable_to_node.get_output_indexing();
324 }
325 else if (composite_map_type == "indices")
326 {
327 if (args["composite_map_indices"].is_string())
328 {
329 Eigen::MatrixXi tmp_mat;
330 polyfem::io::read_matrix(states[0]->resolve_input_path(args["composite_map_indices"].get<std::string>()), tmp_mat);
331 output_indexing = tmp_mat;
332 }
333 else if (args["composite_map_indices"].is_array())
334 output_indexing = args["composite_map_indices"];
335 else
336 log_and_throw_adjoint_error("Invalid composite map indices type!");
337 }
338
339 std::vector<std::shared_ptr<Parametrization>> map_list;
340 for (const auto &arg : args["composition"])
341 map_list.push_back(create_parametrization(arg, states, variable_sizes));
342
343 std::unique_ptr<VariableToSimulation> var2sim = VariableToSimulation::create(type, cur_states, CompositeParametrization(std::move(map_list)));
344 if (type == "shape")
345 var2sim->set_output_indexing(output_indexing);
346
347 return var2sim;
348 }
349
350 Eigen::VectorXd AdjointOptUtils::inverse_evaluation(const json &args, const int ndof, const std::vector<int> &variable_sizes, VariableToSimulationGroup &var2sim)
351 {
352 Eigen::VectorXd x;
353 x.setZero(ndof);
354 int accumulative = 0;
355 int var = 0;
356 for (const auto &arg : args)
357 {
358 const auto &arg_initial = arg["initial"];
359 Eigen::VectorXd tmp(variable_sizes[var]);
360 if (arg_initial.is_array() && arg_initial.size() > 0)
361 {
362 tmp = arg_initial;
363 x.segment(accumulative, tmp.size()) = tmp;
364 }
365 else if (arg_initial.is_number())
366 {
367 tmp.setConstant(arg_initial.get<double>());
368 x.segment(accumulative, tmp.size()) = tmp;
369 }
370 else // arg["initial"] is empty array
371 x += var2sim[var]->inverse_eval();
372
373 accumulative += tmp.size();
374 var++;
375 }
376
377 return x;
378 }
379
380 std::shared_ptr<State> AdjointOptUtils::create_state(const json &args, CacheLevel level, const size_t max_threads)
381 {
382 std::shared_ptr<State> state = std::make_shared<State>();
383 state->set_max_threads(max_threads);
384
385 json in_args = args;
386 in_args["solver"]["max_threads"] = max_threads;
387 if (!args.contains("output") || !args["output"].contains("log") || !args["output"]["log"].contains("level"))
388 {
389 const json tmp = R"({
390 "output": {
391 "log": {
392 "level": "error"
393 }
394 }
395 })"_json;
396
397 in_args.merge_patch(tmp);
398 }
399
400 state->optimization_enabled = level;
401 state->init(in_args, true);
402 state->load_mesh();
403 Eigen::MatrixXd sol, pressure;
404 state->build_basis();
405 state->assemble_rhs();
406 state->assemble_mass_mat();
407
408 return state;
409 }
410
411 std::vector<std::shared_ptr<State>> AdjointOptUtils::create_states(const json &state_args, const CacheLevel &level, const size_t max_threads)
412 {
413 std::vector<std::shared_ptr<State>> states(state_args.size());
414 int i = 0;
415 for (const json &args : state_args)
416 {
417 json cur_args;
418 if (!load_json(args["path"], cur_args))
419 log_and_throw_adjoint_error("Can't find json for State {}", i);
420
421 states[i++] = AdjointOptUtils::create_state(cur_args, level, max_threads);
422 }
423 return states;
424 }
425
427 {
428 state.assemble_rhs();
429 state.assemble_mass_mat();
430 Eigen::MatrixXd sol, pressure;
431 state.solve_problem(sol, pressure);
432 }
433
434 void apply_objective_json_spec(json &args, const json &rules)
435 {
436 if (args.is_array())
437 {
438 for (auto &arg : args)
439 apply_objective_json_spec(arg, rules);
440 }
441 else if (args.is_object())
442 {
443 jse::JSE jse;
444 const bool valid_input = jse.verify_json(args, rules);
445
446 if (!valid_input)
447 {
448 logger().error("invalid objective json:\n{}", jse.log2str());
449 throw std::runtime_error("Invald objective json file");
450 }
451
452 args = jse.inject_defaults(args, rules);
453
454 for (auto &it : args.items())
455 {
456 if (it.key().find("objective") != std::string::npos)
457 apply_objective_json_spec(it.value(), rules);
458 }
459 }
460 }
461
462 json AdjointOptUtils::apply_opt_json_spec(const json &input_args, bool strict_validation)
463 {
464 json args_in = input_args;
465
466 // CHECK validity json
467 json rules;
468 jse::JSE jse;
469 {
470 jse.strict = strict_validation;
471 std::ifstream file(POLYFEM_OPT_INPUT_SPEC);
472
473 if (file.is_open())
474 file >> rules;
475 else
476 {
477 logger().error("unable to open {} rules", POLYFEM_OPT_INPUT_SPEC);
478 throw std::runtime_error("Invald spec file");
479 }
480
481 jse.include_directories.push_back(POLYFEM_JSON_SPEC_DIR);
482 jse.include_directories.push_back(POLYSOLVE_JSON_SPEC_DIR);
483 rules = jse.inject_include(rules);
484
485 // polysolve::linear::Solver::apply_default_solver(rules, "/solver/linear");
486 }
487
488 // polysolve::linear::Solver::select_valid_solver(args_in["solver"]["linear"], logger());
489
490 const bool valid_input = jse.verify_json(args_in, rules);
491
492 if (!valid_input)
493 {
494 logger().error("invalid input json:\n{}", jse.log2str());
495 throw std::runtime_error("Invald input json file");
496 }
497
498 json args = jse.inject_defaults(args_in, rules);
499
500 json obj_rules;
501 {
502 const std::string polyfem_objective_spec = POLYFEM_OBJECTIVE_INPUT_SPEC;
503 std::ifstream file(polyfem_objective_spec);
504
505 if (file.is_open())
506 file >> obj_rules;
507 else
508 {
509 logger().error("unable to open {} rules", polyfem_objective_spec);
510 throw std::runtime_error("Invald spec file");
511 }
512 }
513 apply_objective_json_spec(args["functionals"], obj_rules);
514
515 if (args.contains("stopping_conditions"))
516 apply_objective_json_spec(args["stopping_conditions"], obj_rules);
517
518 return args;
519 }
520
521 int AdjointOptUtils::compute_variable_size(const json &args, const std::vector<std::shared_ptr<State>> &states)
522 {
523 if (args["number"].is_number())
524 {
525 return args["number"].get<int>();
526 }
527 else if (args["number"].is_null() && args["initial"].size() > 0)
528 {
529 return args["initial"].size();
530 }
531 else if (args["number"].is_object())
532 {
533 auto selection = args["number"];
534 if (selection.contains("surface_selection"))
535 {
536 auto surface_selection = selection["surface_selection"].get<std::vector<int>>();
537 auto state_id = selection["state"];
538 std::set<int> node_ids = {};
539 for (const auto &surface : surface_selection)
540 {
541 std::vector<int> ids;
542 states[state_id]->compute_surface_node_ids(surface, ids);
543 for (const auto &i : ids)
544 node_ids.insert(i);
545 }
546 return node_ids.size() * states[state_id]->mesh->dimension();
547 }
548 else if (selection.contains("volume_selection"))
549 {
550 auto volume_selection = selection["volume_selection"].get<std::vector<int>>();
551 auto state_id = selection["state"];
552 std::set<int> node_ids = {};
553 for (const auto &volume : volume_selection)
554 {
555 std::vector<int> ids;
556 states[state_id]->compute_volume_node_ids(volume, ids);
557 for (const auto &i : ids)
558 node_ids.insert(i);
559 }
560
561 if (selection["exclude_boundary_nodes"])
562 {
563 std::vector<int> ids;
564 states[state_id]->compute_total_surface_node_ids(ids);
565 for (const auto &i : ids)
566 node_ids.erase(i);
567 }
568
569 return node_ids.size() * states[state_id]->mesh->dimension();
570 }
571 }
572
573 log_and_throw_adjoint_error("Incorrect specification for parameters.");
574 return -1;
575 }
576} // namespace polyfem::solver
ElementAssemblyValues vals
Definition Assembler.cpp:22
StiffnessMatrix tmp_mat
int x
main class that contains the polyfem solver and all its state
Definition State.hpp:79
void assemble_mass_mat()
assemble mass, step 4 of solve build mass matrix based on defined basis modifies mass (and maybe more...
Definition State.cpp:1530
void solve_problem(Eigen::MatrixXd &sol, Eigen::MatrixXd &pressure)
solves the problems
Definition State.cpp:1726
void assemble_rhs()
compute rhs, step 3 of solve build rhs vector based on defined basis and given rhs of the problem mod...
Definition State.cpp:1654
const Eigen::VectorXi & get_output_indexing() const
A collection of VariableToSimulation.
static std::unique_ptr< VariableToSimulation > create(const std::string &type, const std::vector< std::shared_ptr< State > > &states, CompositeParametrization &&parametrization)
bool load_json(const std::string &json_file, json &out)
Definition main.cpp:26
bool read_matrix(const std::string &path, Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &mat)
Reads a matrix from a file. Determines the file format based on the path's extension.
Definition MatrixIO.cpp:18
NLOHMANN_JSON_SERIALIZE_ENUM(CollisionProxyTessellation, {{CollisionProxyTessellation::REGULAR, "regular"}, {CollisionProxyTessellation::IRREGULAR, "irregular"}})
void apply_objective_json_spec(json &args, const json &rules)
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:42
spdlog::logger & adjoint_logger()
Retrieves the current logger for adjoint.
Definition Logger.cpp:28
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:77
static std::shared_ptr< AdjointForm > create_form(const json &args, const VariableToSimulationGroup &var2sim, const std::vector< std::shared_ptr< State > > &states)
static std::shared_ptr< polysolve::nonlinear::Solver > make_nl_solver(const json &solver_params, const json &linear_solver_params, const double characteristic_length)
static std::unique_ptr< VariableToSimulation > create_variable_to_simulation(const json &args, const std::vector< std::shared_ptr< State > > &states, const std::vector< int > &variable_sizes)
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 void solve_pde(State &state)
static std::shared_ptr< Parametrization > create_parametrization(const json &args, const std::vector< std::shared_ptr< State > > &states, const std::vector< int > &variable_sizes)
static int compute_variable_size(const json &args, const std::vector< std::shared_ptr< State > > &states)
static std::vector< std::shared_ptr< State > > create_states(const json &state_args, const CacheLevel &level, const size_t max_threads)
static std::shared_ptr< State > create_state(const json &args, CacheLevel level, const size_t max_threads)