PolyFEM
Loading...
Searching...
No Matches
AdjointNLProblem.cpp
Go to the documentation of this file.
2
4#include <polyfem/Common.hpp>
7#ifdef POLYFEM_WITH_INFLATOR
9#endif
19
20#include <Eigen/Core>
21#include <spdlog/fmt/fmt.h>
22
23#include <list>
24#include <stack>
25#include <fstream>
26#include <iomanip>
27#include <memory>
28#include <string>
29#include <vector>
30
31namespace polyfem::solver
32{
33 namespace
34 {
35
36 Eigen::VectorXd get_updated_mesh_nodes(const VariableToSimulationGroup &variables_to_simulation, const std::shared_ptr<varform::DifferentiableVarForm> &current_varform, const Eigen::VectorXd &x)
37 {
38 Eigen::MatrixXd V;
39 current_varform->get_vertices(V);
40 Eigen::VectorXd X = utils::flatten(V);
41
42 variables_to_simulation.compute_state_variable(ParameterType::Shape, *current_varform, x, X);
43 variables_to_simulation.compute_state_variable(ParameterType::PeriodicShape, *current_varform, x, X);
44
45 return X;
46 }
47
48 // Class to represent a graph
49 class Graph
50 {
51 int V; // No. of vertices'
52
53 // adjacency lists
54 std::vector<std::list<int>> adj;
55
56 // A function used by topologicalSort
57 void topologicalSortUtil(int v, std::vector<bool> &visited, std::stack<int> &Stack);
58
59 public:
60 Graph(int V); // Constructor
61
62 // function to add an edge to graph
63 void addEdge(int v, int w);
64
65 // prints a Topological Sort of the complete graph
66 std::vector<int> topologicalSort();
67 };
68
69 Graph::Graph(int V)
70 {
71 this->V = V;
72 adj.resize(V);
73 }
74
75 void Graph::addEdge(int v, int w)
76 {
77 adj[v].push_back(w); // Add w to v’s list.
78 }
79
80 // A recursive function used by topologicalSort
81 void Graph::topologicalSortUtil(int v, std::vector<bool> &visited,
82 std::stack<int> &Stack)
83 {
84 // Mark the current node as visited.
85 visited[v] = true;
86
87 // Recur for all the vertices adjacent to this vertex
88 std::list<int>::iterator i;
89 for (i = adj[v].begin(); i != adj[v].end(); ++i)
90 if (!visited[*i])
91 topologicalSortUtil(*i, visited, Stack);
92
93 // Push current vertex to stack which stores result
94 Stack.push(v);
95 }
96
97 // The function to do Topological Sort. It uses recursive
98 // topologicalSortUtil()
99 std::vector<int> Graph::topologicalSort()
100 {
101 std::stack<int> Stack;
102
103 // Mark all the vertices as not visited
104 std::vector<bool> visited(V, false);
105
106 // Call the recursive helper function to store Topological
107 // Sort starting from all vertices one by one
108 for (int i = 0; i < V; i++)
109 if (visited[i] == false)
110 topologicalSortUtil(i, visited, Stack);
111
112 // Print contents of stack
113 std::vector<int> sorted;
114 while (Stack.empty() == false)
115 {
116 sorted.push_back(Stack.top());
117 Stack.pop();
118 }
119
120 return sorted;
121 }
122 } // namespace
123
124 AdjointNLProblem::AdjointNLProblem(std::shared_ptr<AdjointForm> form,
125 const VariableToSimulationGroup &variables_to_simulation,
126 const std::vector<std::shared_ptr<varform::DifferentiableVarForm>> &all_varforms,
127 const std::vector<std::shared_ptr<DiffCache>> &all_diff_caches,
128 const json &args,
129 std::function<bool()> remeshing_trigger)
130 : FullNLProblem({form}),
131 form_(form),
132 variables_to_simulation_(variables_to_simulation),
133 all_varforms_(all_varforms),
134 all_diff_caches_(all_diff_caches),
135 save_freq(args["output"]["save_frequency"]),
136 enable_slim(args["solver"]["advanced"]["enable_slim"]),
137 smooth_line_search(args["solver"]["advanced"]["smooth_line_search"]),
138 solve_in_parallel(args["solver"]["advanced"]["solve_in_parallel"]),
139 remeshing_trigger_(std::move(remeshing_trigger))
140 {
141 cur_grad.setZero(0);
142#ifdef POLYFEM_WITH_INFLATOR
143 // Inflation can change connectivity, so fixed-connectivity mesh smoothing cannot be applied.
144 for (const auto &mapping : variables_to_simulation_.data)
145 {
146 if (dynamic_cast<InflatedPeriodicShapeVariableToSimulation *>(mapping.get()) && (enable_slim || smooth_line_search))
147 {
148 log_and_throw_adjoint_error("Inflated meshes require enable_slim=false and smooth_line_search=false.");
149 }
150 }
151#endif
152
153 if (enable_slim && args["solver"]["nonlinear"]["advanced"]["apply_gradient_fd"] != "None")
154 adjoint_logger().warn("SLIM may affect the finite difference result!");
155
156 if (enable_slim && smooth_line_search)
157 adjoint_logger().warn("Both in-line-search SLIM and after-line-search SLIM are ON!");
158
159 if (args["output"]["solution"] != "")
160 {
161 solution_ostream.open(args["output"]["solution"].get<std::string>(), std::ofstream::out);
162 if (!solution_ostream.is_open())
163 adjoint_logger().error("Cannot open solution file for writing!");
164 }
165
166 solve_in_order.clear();
167
168 // Parse initial guess source. This is for incremental load where the initial solution
169 // of certain state comes from another state.
170 initial_guess_sources_.assign(all_varforms.size(), -1);
171 for (int index = 0; index < all_varforms.size(); ++index)
172 {
173 int source = args["states"][index]["initial_guess"].get<int>();
174 initial_guess_sources_[index] = source;
175 }
176 {
177 Graph G(all_varforms.size());
178 for (int k = 0; k < all_varforms.size(); k++)
179 {
180 auto &arg = args["states"][k];
181 if (arg["initial_guess"].get<int>() >= 0)
182 G.addEdge(arg["initial_guess"].get<int>(), k);
183 }
184
185 solve_in_order = G.topologicalSort();
186 }
187
188 active_varform_mask.assign(all_varforms_.size(), false);
189 for (int i = 0; i < all_varforms_.size(); i++)
190 {
191 for (const auto &v2sim : variables_to_simulation_.data)
192 {
193 if (v2sim->affects_varform(*all_varforms_[i]))
194 {
195 active_varform_mask[i] = true;
196 break;
197 }
198 }
199 }
200 // A changed initial guess can select a different equilibrium branch even if the target's parameters are unchanged.
201 for (const int index : solve_in_order)
202 if (initial_guess_sources_[index] >= 0 && active_varform_mask[initial_guess_sources_[index]])
203 active_varform_mask[index] = true;
204 }
205
207 std::shared_ptr<AdjointForm> form,
208 const std::vector<std::shared_ptr<AdjointForm>> &stopping_conditions,
209 const VariableToSimulationGroup &variables_to_simulation,
210 const std::vector<std::shared_ptr<varform::DifferentiableVarForm>> &all_varforms,
211 const std::vector<std::shared_ptr<DiffCache>> &all_diff_caches,
212 const json &args,
213 std::function<bool()> remeshing_trigger)
215 form, variables_to_simulation, all_varforms, all_diff_caches, args,
216 std::move(remeshing_trigger))
217 {
218 stopping_conditions_ = stopping_conditions;
219 }
220
221 void AdjointNLProblem::hessian(const Eigen::VectorXd &x, StiffnessMatrix &hessian)
222 {
223 log_and_throw_adjoint_error("Hessian not supported!");
224 }
225
226 double AdjointNLProblem::value(const Eigen::VectorXd &x)
227 {
228 return form_->value(x);
229 }
230
231 void AdjointNLProblem::gradient(const Eigen::VectorXd &x, Eigen::VectorXd &gradv)
232 {
233 if (cur_grad.size() == x.size())
234 gradv = cur_grad;
235 else
236 {
237 gradv.setZero(x.size());
238
239 {
240 POLYFEM_SCOPED_TIMER("adjoint solve");
241 for (int i = 0; i < all_varforms_.size(); i++)
242 solve_adjoint_cached(*all_varforms_[i], *all_diff_caches_[i], form_->compute_reduced_adjoint_rhs(x, *all_varforms_[i], *all_diff_caches_[i]));
243 }
244
245 {
246 POLYFEM_SCOPED_TIMER("gradient assembly");
247 form_->first_derivative(x, gradv);
248 if (x.size() < 10)
249 {
250 adjoint_logger().trace("x {}", x.transpose());
251 adjoint_logger().trace("gradient {}", gradv.transpose());
252 }
253 }
254
255 cur_grad = gradv;
256 }
257 }
258
259 bool AdjointNLProblem::is_step_valid(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
260 {
261 bool need_rebuild_basis = false;
262
263 // update to new parameter and check if the new parameter is valid to solve
264 for (const auto &v : variables_to_simulation_.data)
265 if (v->parameter_type() == ParameterType::Shape || v->parameter_type() == ParameterType::PeriodicShape)
266 need_rebuild_basis = true;
267
268 if (need_rebuild_basis && smooth_line_search)
269 {
270 Eigen::MatrixXd X, V0, V1;
271 Eigen::MatrixXi F;
272
273 for (auto varform : all_varforms_)
274 {
275
276 V1 = utils::unflatten(
277 get_updated_mesh_nodes(variables_to_simulation_, varform, x1),
278 varform->get_mesh().dimension());
279 varform->get_vertices(V0);
280 varform->get_elements(F);
281
282 Eigen::MatrixXd V_smooth;
283 bool slim_success = polyfem::mesh::apply_slim(V0, F, V1, V_smooth);
284 if (!slim_success)
285 {
286 adjoint_logger().info("SLIM failed, step not valid!");
287 return false;
288 }
289
290 V1 = V_smooth;
291
292 bool flipped = utils::is_flipped(V1, F);
293 if (flipped)
294 {
295 adjoint_logger().info("Found flipped element in LS, step not valid!");
296 return false;
297 }
298 }
299 }
300
301 return form_->is_step_valid(x0, x1);
302 }
303
304 bool AdjointNLProblem::is_step_collision_free(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
305 {
306 return form_->is_step_collision_free(x0, x1);
307 }
308
309 double AdjointNLProblem::max_step_size(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
310 {
311 return form_->max_step_size(x0, x1);
312 }
313
314 void AdjointNLProblem::line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
315 {
316 form_->line_search_begin(x0, x1);
317 }
318
320 {
321 form_->line_search_end();
322 }
323
324 void AdjointNLProblem::post_step(const polysolve::nonlinear::PostStepData &data)
325 {
326 save_to_file(save_iter++, data.x);
327
328 form_->post_step(data);
329 }
330
331 void AdjointNLProblem::save_to_file(const int iter_num, const Eigen::VectorXd &x0)
332 {
333 int id = 0;
334
335 if (solution_ostream.is_open())
336 {
337 adjoint_logger().debug("Save solution at iteration {} to file...", iter_num);
338 solution_ostream << iter_num << ": " << std::setprecision(16) << x0.transpose() << std::endl;
339 solution_ostream.flush();
340 }
341
342 if (iter_num % save_freq != 0)
343 return;
344 adjoint_logger().info("Saving iteration {}", iter_num);
345 for (int i = 0; i < all_varforms_.size(); ++i)
346 {
347 auto &varform = all_varforms_[i];
348 auto &diff_cache = all_diff_caches_[i];
349
350 bool save_vtu = true;
351 bool save_rest_mesh = true;
352
353 std::string vis_mesh_path = varform->output_file_path(fmt::format("opt_state_{:d}_iter_{:d}.vtu", id, iter_num));
354 std::string mesh_ext = varform->get_mesh().is_volume() ? ".msh" : ".obj";
355 std::string rest_mesh_path = varform->output_file_path(fmt::format("opt_state_{:d}_iter_{:d}" + mesh_ext, id, iter_num));
356 id++;
357
358 if (!save_vtu)
359 continue;
360 adjoint_logger().debug("Save final vtu to file {} ...", vis_mesh_path);
361
362 double tend = varform->get_args().value("tend", 1.0);
363 double dt = 1;
364 if (!varform->get_args()["time"].is_null())
365 dt = varform->get_args()["time"]["dt"];
366
367 Eigen::MatrixXd sol = diff_cache->u(-1);
368
369 varform->save_vtu(vis_mesh_path, sol, tend, dt);
370
371 if (!save_rest_mesh)
372 continue;
373 adjoint_logger().debug("Save rest mesh to file {} ...", rest_mesh_path);
374
375 // If shape opt, save rest meshes as well
376 Eigen::MatrixXd V;
377 Eigen::MatrixXi F;
378 varform->get_vertices(V);
379 varform->get_elements(F);
380 if (varform->get_mesh().is_volume())
381 io::MshWriter::write(rest_mesh_path, V, F, varform->get_mesh().get_body_ids(), true, false);
382 else
383 io::OBJWriter::write(rest_mesh_path, V, F);
384 }
385 }
386
387 void AdjointNLProblem::solution_changed(const Eigen::VectorXd &newX)
388 {
389 bool need_rebuild_basis = false;
390
391 // update to new parameter and check if the new parameter is valid to solve
392 for (const auto &v : variables_to_simulation_.data)
393 {
394 v->update(newX);
395 if (v->parameter_type() == ParameterType::Shape || v->parameter_type() == ParameterType::PeriodicShape)
396 need_rebuild_basis = true;
397 }
398
399 if (need_rebuild_basis)
400 {
401 for (const auto &varform : all_varforms_)
402 varform->prepare();
403 }
404
405 // solve PDE
406 solve_pde();
407
408 form_->solution_changed(newX);
409
410 curr_x = newX;
411 }
412
413 bool AdjointNLProblem::after_line_search_custom_operation(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1)
414 {
415 if (!enable_slim)
416 return false;
417
418 // SLIM smoothing for shape optimization.
419
420 std::vector<Eigen::MatrixXd> V_old;
421 std::vector<Eigen::MatrixXd> V_new;
422 for (const auto &varform : all_varforms_)
423 {
424 V_old.push_back(utils::unflatten(
425 get_updated_mesh_nodes(variables_to_simulation_, varform, x0),
426 varform->get_mesh().dimension()));
427 V_new.push_back(utils::unflatten(
428 get_updated_mesh_nodes(variables_to_simulation_, varform, x1),
429 varform->get_mesh().dimension()));
430 }
431
432 std::vector<Eigen::MatrixXd> V_smooth;
433 V_smooth.reserve(all_varforms_.size());
434 for (int i = 0; i < all_varforms_.size(); ++i)
435 {
436 const auto &varform = all_varforms_[i];
437 Eigen::MatrixXd V_out;
438 Eigen::MatrixXi F;
439 varform->get_elements(F);
440
441 if (!polyfem::mesh::apply_slim(V_old[i], F, V_new[i], V_out, 50))
442 {
443 adjoint_logger().warn("SLIM failed; keeping the accepted unsmoothed step.");
444 return false;
445 }
446 V_smooth.push_back(std::move(V_out));
447 }
448
449 for (int i = 0; i < all_varforms_.size(); ++i)
450 all_varforms_[i]->set_vertex_positions(V_smooth[i]);
451
452 adjoint_logger().debug("SLIM succeeded!");
453
454 return true;
455 }
456
458 {
460 {
461 adjoint_logger().info("Run simulations in parallel...");
462
463 utils::maybe_parallel_for(all_varforms_.size(), [&](int start, int end, int thread_id) {
464 for (int i = start; i < end; i++)
465 {
466 auto &varform = all_varforms_[i];
467 auto &diff_cache = all_diff_caches_[i];
468 if (active_varform_mask[i] || diff_cache->size() == 0)
469 {
470 const auto *initial_conditions = diff_cache->initial_condition_override ? &*diff_cache->initial_condition_override : nullptr;
471 const varform::ForwardStepCallback post_step = [varform, diff_cache](const int step, const Eigen::MatrixXd &solution) {
472 diff_cache->cache_transient(step, *varform, solution, nullptr);
473 };
474 Eigen::MatrixXd solution;
475 varform->solve(solution, initial_conditions, post_step, true);
476 }
477 }
478 });
479 }
480 else
481 {
482 adjoint_logger().info("Run simulations in serial...");
483
484 for (int i : solve_in_order)
485 {
486 auto &varform = all_varforms_[i];
487 auto &diff_cache = all_diff_caches_[i];
488 if (active_varform_mask[i] || diff_cache->size() == 0)
489 {
490 const auto *initial_conditions = diff_cache->initial_condition_override ? &*diff_cache->initial_condition_override : nullptr;
492 const int source = initial_guess_sources_[i];
493 if (source >= 0)
494 {
495 Eigen::MatrixXd target_vertices;
496 Eigen::MatrixXd source_vertices;
497 Eigen::MatrixXi target_elements;
498 Eigen::MatrixXi source_elements;
499 varform->get_vertices(target_vertices);
500 all_varforms_[source]->get_vertices(source_vertices);
501 varform->get_elements(target_elements);
502 all_varforms_[source]->get_elements(source_elements);
503 if (target_vertices.rows() != source_vertices.rows() || target_vertices.cols() != source_vertices.cols()
504 || target_elements.rows() != source_elements.rows() || target_elements.cols() != source_elements.cols()
505 || target_vertices != source_vertices || target_elements != source_elements
506 || varform->primary_space().disc_orders != all_varforms_[source]->primary_space().disc_orders
507 || varform->primary_space().n_bases != all_varforms_[source]->primary_space().n_bases)
508 log_and_throw_adjoint_error("initial_guess states must use identical meshes and finite-element spaces.");
509 // Continue from the source equilibrium; the target still solves its own prescribed macro strain.
510 continuation.solution = all_diff_caches_[source]->u(0);
511 continuation.displacement_gradient = all_diff_caches_[source]->disp_grad();
512 initial_conditions = &continuation;
513 }
514 const varform::ForwardStepCallback post_step = [varform, diff_cache](const int step, const Eigen::MatrixXd &solution) {
515 diff_cache->cache_transient(step, *varform, solution, nullptr);
516 };
517 Eigen::MatrixXd solution;
518 varform->solve(solution, initial_conditions, post_step, true);
519 }
520 }
521 }
522
523 cur_grad.resize(0);
524 }
525
526 bool AdjointNLProblem::stop(const TVector &x)
527 {
528 if (remeshing_trigger_ && remeshing_trigger_())
529 return true;
530
531 if (stopping_conditions_.size() == 0)
532 return false;
533
534 for (auto &obj : stopping_conditions_)
535 {
536 obj->solution_changed(x);
537 if (obj->value(x) > 0)
538 return false;
539 }
540 return true;
541 }
542
543} // namespace polyfem::solver
std::vector< std::list< int > > adj
int V
StiffnessMatrix source
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
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
AdjointNLProblem(std::shared_ptr< AdjointForm > form, const VariableToSimulationGroup &variables_to_simulation, const std::vector< std::shared_ptr< varform::DifferentiableVarForm > > &all_varforms, const std::vector< std::shared_ptr< DiffCache > > &all_diff_caches, const json &args, std::function< bool()> remeshing_trigger={})
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
std::vector< std::shared_ptr< varform::DifferentiableVarForm > > all_varforms_
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::shared_ptr< AdjointForm > form_
void line_search_begin(const Eigen::VectorXd &x0, const Eigen::VectorXd &x1) override
Regenerate periodic meshes from [cell width, inflator parameters] and map shape derivatives back to d...
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)
std::function< void(int step, const Eigen::MatrixXd &solution)> ForwardStepCallback
Definition VarForm.hpp:55
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 varform::DifferentiableVarForm &varform, DiffCache &diff_cache, const Eigen::MatrixXd &rhs)
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:24
Eigen::MatrixXd displacement_gradient
Macro G for decomposing solution as u = fluctuation + G X; empty when absent.
Definition VarForm.hpp:51