PolyFEM
Loading...
Searching...
No Matches
AdjointNLProblem.cpp
Go to the documentation of this file.
2
3#include <polyfem/State.hpp>
4#include <polyfem/Common.hpp>
16
17#include <Eigen/Core>
18#include <spdlog/fmt/fmt.h>
19
20#include <list>
21#include <stack>
22#include <fstream>
23#include <iomanip>
24#include <memory>
25#include <string>
26#include <vector>
27
29{
30 namespace
31 {
32
33 Eigen::VectorXd get_updated_mesh_nodes(const VariableToSimulationGroup &variables_to_simulation, const std::shared_ptr<State> &curr_state, const Eigen::VectorXd &x)
34 {
35 Eigen::MatrixXd V;
36 curr_state->get_vertices(V);
37 Eigen::VectorXd X = utils::flatten(V);
38
39 for (auto &p : variables_to_simulation.data)
40 {
41 for (const auto &state : p->states)
42 if (state.get() != curr_state.get())
43 continue;
44 if (p->get_parameter_type() != ParameterType::Shape)
45 continue;
46 auto state_variable = p->parametrization.eval(x);
47 auto output_indexing = p->get_output_indexing(x);
48 for (int i = 0; i < output_indexing.size(); ++i)
49 X(output_indexing(i)) = state_variable(i);
50 }
51
52 return X;
53 }
54
55 // Class to represent a graph
56 class Graph
57 {
58 int V; // No. of vertices'
59
60 // adjacency lists
61 std::vector<std::list<int>> adj;
62
63 // A function used by topologicalSort
64 void topologicalSortUtil(int v, std::vector<bool> &visited, std::stack<int> &Stack);
65
66 public:
67 Graph(int V); // Constructor
68
69 // function to add an edge to graph
70 void addEdge(int v, int w);
71
72 // prints a Topological Sort of the complete graph
73 std::vector<int> topologicalSort();
74 };
75
76 Graph::Graph(int V)
77 {
78 this->V = V;
79 adj.resize(V);
80 }
81
82 void Graph::addEdge(int v, int w)
83 {
84 adj[v].push_back(w); // Add w to v’s list.
85 }
86
87 // A recursive function used by topologicalSort
88 void Graph::topologicalSortUtil(int v, std::vector<bool> &visited,
89 std::stack<int> &Stack)
90 {
91 // Mark the current node as visited.
92 visited[v] = true;
93
94 // Recur for all the vertices adjacent to this vertex
95 std::list<int>::iterator i;
96 for (i = adj[v].begin(); i != adj[v].end(); ++i)
97 if (!visited[*i])
98 topologicalSortUtil(*i, visited, Stack);
99
100 // Push current vertex to stack which stores result
101 Stack.push(v);
102 }
103
104 // The function to do Topological Sort. It uses recursive
105 // topologicalSortUtil()
106 std::vector<int> Graph::topologicalSort()
107 {
108 std::stack<int> Stack;
109
110 // Mark all the vertices as not visited
111 std::vector<bool> visited(V, false);
112
113 // Call the recursive helper function to store Topological
114 // Sort starting from all vertices one by one
115 for (int i = 0; i < V; i++)
116 if (visited[i] == false)
117 topologicalSortUtil(i, visited, Stack);
118
119 // Print contents of stack
120 std::vector<int> sorted;
121 while (Stack.empty() == false)
122 {
123 sorted.push_back(Stack.top());
124 Stack.pop();
125 }
126
127 return sorted;
128 }
129 } // namespace
130
131 AdjointNLProblem::AdjointNLProblem(std::shared_ptr<AdjointForm> form,
132 const VariableToSimulationGroup &variables_to_simulation,
133 const std::vector<std::shared_ptr<State>> &all_states,
134 const std::vector<std::shared_ptr<DiffCache>> &all_diff_caches,
135 const json &args)
136 : FullNLProblem({form}),
137 form_(form),
138 variables_to_simulation_(variables_to_simulation),
139 all_states_(all_states),
140 all_diff_caches_(all_diff_caches),
141 save_freq(args["output"]["save_frequency"]),
142 enable_slim(args["solver"]["advanced"]["enable_slim"]),
143 smooth_line_search(args["solver"]["advanced"]["smooth_line_search"]),
144 solve_in_parallel(args["solver"]["advanced"]["solve_in_parallel"])
145 {
146 cur_grad.setZero(0);
147
148 if (enable_slim && args["solver"]["nonlinear"]["advanced"]["apply_gradient_fd"] != "None")
149 adjoint_logger().warn("SLIM may affect the finite difference result!");
150
151 if (enable_slim && smooth_line_search)
152 adjoint_logger().warn("Both in-line-search SLIM and after-line-search SLIM are ON!");
153
154 if (args["output"]["solution"] != "")
155 {
156 solution_ostream.open(args["output"]["solution"].get<std::string>(), std::ofstream::out);
157 if (!solution_ostream.is_open())
158 adjoint_logger().error("Cannot open solution file for writing!");
159 }
160
161 solve_in_order.clear();
162 {
163 Graph G(all_states.size());
164 for (int k = 0; k < all_states.size(); k++)
165 {
166 auto &arg = args["states"][k];
167 if (arg["initial_guess"].get<int>() >= 0)
168 G.addEdge(arg["initial_guess"].get<int>(), k);
169 }
170
171 solve_in_order = G.topologicalSort();
172 }
173
174 active_state_mask.assign(all_states_.size(), false);
175 for (int i = 0; i < all_states_.size(); i++)
176 {
177 for (const auto &v2sim : variables_to_simulation_.data)
178 {
179 for (const auto &state : v2sim->states)
180 {
181 if (all_states_[i].get() == state.get())
182 {
183 active_state_mask[i] = true;
184 break;
185 }
186 }
187 }
188 }
189 }
190
191 AdjointNLProblem::AdjointNLProblem(std::shared_ptr<AdjointForm> form,
192 const std::vector<std::shared_ptr<AdjointForm>> &stopping_conditions,
193 const VariableToSimulationGroup &variables_to_simulation,
194 const std::vector<std::shared_ptr<State>> &all_states,
195 const std::vector<std::shared_ptr<DiffCache>> &all_diff_caches,
196 const json &args) : AdjointNLProblem(form, variables_to_simulation, all_states, all_diff_caches, args)
197 {
198 stopping_conditions_ = stopping_conditions;
199 }
200
201 void AdjointNLProblem::hessian(const Eigen::VectorXd &x, StiffnessMatrix &hessian)
202 {
203 log_and_throw_adjoint_error("Hessian not supported!");
204 }
205
206 double AdjointNLProblem::value(const Eigen::VectorXd &x)
207 {
208 return form_->value(x);
209 }
210
211 void AdjointNLProblem::gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv)
212 {
213 if (cur_grad.size() == x.size())
214 gradv = cur_grad;
215 else
216 {
217 gradv.setZero(x.size());
218
219 {
220 POLYFEM_SCOPED_TIMER("adjoint solve");
221 for (int i = 0; i < all_states_.size(); i++)
222 solve_adjoint_cached(*all_states_[i], *all_diff_caches_[i], form_->compute_reduced_adjoint_rhs(x, *all_states_[i], *all_diff_caches_[i]));
223 }
224
225 {
226 POLYFEM_SCOPED_TIMER("gradient assembly");
227 form_->first_derivative(x, gradv);
228 if (x.size() < 10)
229 {
230 adjoint_logger().trace("x {}", x.transpose());
231 adjoint_logger().trace("gradient {}", gradv.transpose());
232 }
233 }
234
235 cur_grad = gradv;
236 }
237 }
238
239 bool AdjointNLProblem::is_step_valid(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
240 {
241 bool need_rebuild_basis = false;
242
243 // update to new parameter and check if the new parameter is valid to solve
244 for (const auto &v : variables_to_simulation_.data)
245 if (v->get_parameter_type() == ParameterType::Shape || v->get_parameter_type() == ParameterType::PeriodicShape)
246 need_rebuild_basis = true;
247
248 if (need_rebuild_basis && smooth_line_search)
249 {
250 Eigen::MatrixXd X, V0, V1;
251 Eigen::MatrixXi F;
252
253 int state_count = 0;
254 for (auto state_ : all_states_)
255 {
256
258 get_updated_mesh_nodes(variables_to_simulation_, state_, x1),
259 state_->mesh->dimension());
260 state_->get_vertices(V0);
261 state_->get_elements(F);
262
263 Eigen::MatrixXd V_smooth;
264 bool slim_success = polyfem::mesh::apply_slim(V0, F, V1, V_smooth);
265 if (!slim_success)
266 {
267 adjoint_logger().info("SLIM failed, step not valid!");
268 return false;
269 }
270
271 V1 = V_smooth;
272
273 bool flipped = utils::is_flipped(V1, F);
274 if (flipped)
275 {
276 adjoint_logger().info("Found flipped element in LS, step not valid!");
277 return false;
278 }
279
280 state_count++;
281 }
282 }
283
284 return form_->is_step_valid(x0, x1);
285 }
286
287 bool AdjointNLProblem::is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
288 {
289 return form_->is_step_collision_free(x0, x1);
290 }
291
292 double AdjointNLProblem::max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
293 {
294 return form_->max_step_size(x0, x1);
295 }
296
297 void AdjointNLProblem::line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
298 {
299 form_->line_search_begin(x0, x1);
300 }
301
303 {
304 form_->line_search_end();
305 }
306
307 void AdjointNLProblem::post_step(const polysolve::nonlinear::PostStepData &data)
308 {
309 save_to_file(save_iter++, data.x);
310
311 form_->post_step(data);
312 }
313
314 void AdjointNLProblem::save_to_file(const int iter_num, const Eigen::VectorXd &x0)
315 {
316 int id = 0;
317
318 if (solution_ostream.is_open())
319 {
320 adjoint_logger().debug("Save solution at iteration {} to file...", iter_num);
321 solution_ostream << iter_num << ": " << std::setprecision(16) << x0.transpose() << std::endl;
322 solution_ostream.flush();
323 }
324
325 if (iter_num % save_freq != 0)
326 return;
327 adjoint_logger().info("Saving iteration {}", iter_num);
328 for (int i = 0; i < all_states_.size(); ++i)
329 {
330 auto &state = all_states_[i];
331 auto &diff_cache = all_diff_caches_[i];
332
333 bool save_vtu = true;
334 bool save_rest_mesh = true;
335
336 std::string vis_mesh_path = state->resolve_output_path(fmt::format("opt_state_{:d}_iter_{:d}.vtu", id, iter_num));
337 std::string mesh_ext = state->mesh->is_volume() ? ".msh" : ".obj";
338 std::string rest_mesh_path = state->resolve_output_path(fmt::format("opt_state_{:d}_iter_{:d}" + mesh_ext, id, iter_num));
339 id++;
340
341 if (!save_vtu)
342 continue;
343 adjoint_logger().debug("Save final vtu to file {} ...", vis_mesh_path);
344
345 double tend = state->args.value("tend", 1.0);
346 double dt = 1;
347 if (!state->args["time"].is_null())
348 dt = state->args["time"]["dt"];
349
350 Eigen::MatrixXd sol = diff_cache->u(-1);
351
352 state->out_geom.save_vtu(
353 vis_mesh_path,
354 *state,
355 sol,
356 Eigen::MatrixXd::Zero(state->n_pressure_bases, 1),
357 tend, dt,
359 state->mesh->is_linear(),
360 state->mesh->has_prism(),
361 state->problem->is_scalar()),
362 state->is_contact_enabled());
363
364 if (!save_rest_mesh)
365 continue;
366 adjoint_logger().debug("Save rest mesh to file {} ...", rest_mesh_path);
367
368 // If shape opt, save rest meshes as well
369 Eigen::MatrixXd V;
370 Eigen::MatrixXi F;
371 state->get_vertices(V);
372 state->get_elements(F);
373 if (state->mesh->is_volume())
374 io::MshWriter::write(rest_mesh_path, V, F, state->mesh->get_body_ids(), true, false);
375 else
376 io::OBJWriter::write(rest_mesh_path, V, F);
377 }
378 }
379
380 void AdjointNLProblem::solution_changed(const Eigen::VectorXd &newX)
381 {
382 bool need_rebuild_basis = false;
383
384 // update to new parameter and check if the new parameter is valid to solve
385 for (const auto &v : variables_to_simulation_.data)
386 {
387 v->update(newX);
388 if (v->get_parameter_type() == ParameterType::Shape || v->get_parameter_type() == ParameterType::PeriodicShape)
389 need_rebuild_basis = true;
390 }
391
392 if (need_rebuild_basis)
393 {
394 for (const auto &state : all_states_)
395 state->build_basis();
396 }
397
398 // solve PDE
399 solve_pde();
400
401 form_->solution_changed(newX);
402
403 curr_x = newX;
404 }
405
406 bool AdjointNLProblem::after_line_search_custom_operation(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
407 {
408 if (!enable_slim)
409 return false;
410
411 for (const auto &v : variables_to_simulation_.data)
412 v->update(x0);
413
414 std::vector<Eigen::MatrixXd> V_old_list;
415 for (auto state : all_states_)
416 {
417 Eigen::MatrixXd V;
418 state->get_vertices(V);
419 V_old_list.push_back(V);
420 }
421
422 for (const auto &v : variables_to_simulation_.data)
423 v->update(x1);
424
425 // Apply slim to all states on a frequency
426 int state_num = 0;
427 for (auto state : all_states_)
428 {
429 Eigen::MatrixXd V_new, V_smooth;
430 Eigen::MatrixXi F;
431 state->get_vertices(V_new);
432 state->get_elements(F);
433
434 bool slim_success = polyfem::mesh::apply_slim(V_old_list[state_num++], F, V_new, V_smooth, 50);
435
436 if (!slim_success)
437 log_and_throw_adjoint_error("SLIM cannot succeed");
438
439 for (int i = 0; i < V_smooth.rows(); ++i)
440 state->mesh->set_point(i, V_smooth.row(i));
441 }
442 adjoint_logger().debug("SLIM succeeded!");
443
444 return true;
445 }
446
448 {
450 {
451 adjoint_logger().info("Run simulations in parallel...");
452
453 utils::maybe_parallel_for(all_states_.size(), [&](int start, int end, int thread_id) {
454 for (int i = start; i < end; i++)
455 {
456 auto &state = all_states_[i];
457 auto &diff_cache = all_diff_caches_[i];
458 if (active_state_mask[i] || diff_cache->size() == 0)
459 {
460 const InitialConditionOverride *ic_override = nullptr;
461 if (!diff_cache->initial_condition_override.is_empty())
462 {
463 ic_override = &diff_cache->initial_condition_override;
464 }
465 state->assemble_rhs();
466 state->assemble_mass_mat();
467 Eigen::MatrixXd sol, pressure; // solution is also cached in state
468 auto cache_post_step = [&diff_cache](const int step, State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd *disp_grad, const Eigen::MatrixXd *pressure) {
469 diff_cache->cache_transient(step, state, sol, disp_grad, pressure);
470 };
471 state->solve_problem(sol, pressure, cache_post_step, ic_override);
472 }
473 }
474 });
475 }
476 else
477 {
478 adjoint_logger().info("Run simulations in serial...");
479
480 Eigen::MatrixXd sol, pressure; // solution is also cached in state
481 for (int i : solve_in_order)
482 {
483 auto &state = all_states_[i];
484 auto &diff_cache = all_diff_caches_[i];
485 if (active_state_mask[i] || diff_cache->size() == 0)
486 {
487 const InitialConditionOverride *ic_override = nullptr;
488 if (!diff_cache->initial_condition_override.is_empty())
489 {
490 ic_override = &diff_cache->initial_condition_override;
491 }
492
493 state->assemble_rhs();
494 state->assemble_mass_mat();
495
496 auto cache_post_step = [&](const int step, State &state, const Eigen::MatrixXd &sol, const Eigen::MatrixXd *disp_grad, const Eigen::MatrixXd *pressure) {
497 diff_cache->cache_transient(step, state, sol, disp_grad, pressure);
498 };
499 state->solve_problem(sol, pressure, cache_post_step, ic_override);
500 }
501 }
502 }
503
504 cur_grad.resize(0);
505 }
506
507 bool AdjointNLProblem::stop(const TVector &x)
508 {
509 if (stopping_conditions_.size() == 0)
510 return false;
511
512 for (auto &obj : stopping_conditions_)
513 {
514 obj->solution_changed(x);
515 if (obj->value(x) > 0)
516 return false;
517 }
518 return true;
519 }
520
521} // namespace polyfem::solver
std::vector< std::list< int > > adj
int V
int x
#define POLYFEM_SCOPED_TIMER(...)
Definition Timer.hpp:10
Runtime override for initial-condition histories.
Definition State.hpp:90
main class that contains the polyfem solver and all its state
Definition State.hpp:113
static void write(const std::string &path, const mesh::Mesh &mesh, const bool binary)
saves the mesh
Definition MshWriter.cpp:7
static bool write(const std::string &path, const Eigen::MatrixXd &v, const Eigen::MatrixXi &e, const Eigen::MatrixXi &f)
Definition OBJWriter.cpp:18
bool after_line_search_custom_operation(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
double max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
void gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) override
void save_to_file(const int iter_num, const Eigen::VectorXd &x0)
bool stop(const TVector &x) override
bool is_step_valid(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
std::vector< std::shared_ptr< AdjointForm > > stopping_conditions_
std::vector< std::shared_ptr< DiffCache > > all_diff_caches_
AdjointNLProblem(std::shared_ptr< AdjointForm > form, const VariableToSimulationGroup &variables_to_simulation, const std::vector< std::shared_ptr< State > > &all_states, const std::vector< std::shared_ptr< DiffCache > > &all_diff_caches, const json &args)
void hessian(const Eigen::VectorXd &x, StiffnessMatrix &hessian) override
void post_step(const polysolve::nonlinear::PostStepData &data) override
void solution_changed(const Eigen::VectorXd &new_x) override
bool is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
VariableToSimulationGroup variables_to_simulation_
double value(const Eigen::VectorXd &x) override
std::vector< std::shared_ptr< State > > all_states_
std::shared_ptr< AdjointForm > form_
void line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
A collection of VariableToSimulation.
std::vector< std::shared_ptr< VariableToSimulation > > data
bool apply_slim(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, const Eigen::MatrixXd &V_new, Eigen::MatrixXd &V_smooth, const int max_iters)
bool is_flipped(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F)
Determine if any simplex is inverted or collapses.
Eigen::MatrixXd unflatten(const Eigen::VectorXd &x, int dim)
Unflatten rowwises, so every dim elements in x become a row.
Eigen::VectorXd flatten(const Eigen::MatrixXd &X)
Flatten rowwises.
void maybe_parallel_for(int size, const std::function< void(int, int, int)> &partial_for)
spdlog::logger & adjoint_logger()
Retrieves the current logger for adjoint.
Definition Logger.cpp:30
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:79
void solve_adjoint_cached(const State &state, DiffCache &diff_cache, const Eigen::MatrixXd &rhs)
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:24