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