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