PolyFEM
Loading...
Searching...
No Matches
DirichletBoundaryVariableToSimulation.cpp
Go to the documentation of this file.
2
3#include <polyfem/Common.hpp>
11
12#include <Eigen/Core>
13
14#include <string>
15#include <algorithm>
16#include <unordered_map>
17#include <utility>
18#include <vector>
19#include <cassert>
20
21namespace polyfem::solver
22{
23
25 StatePtrs states,
26 DiffCachePtrs diff_caches,
27 CompositeParametrization parametrizations,
28 Eigen::VectorXi active_boundary_ids,
29 Eigen::VectorXi active_time_slices)
30 : dim_(states[0]->mesh->dimension()),
31 time_steps_(0),
32 states_(std::move(states)),
33 diff_caches_(std::move(diff_caches)),
34 parametrization_(std::move(parametrizations)),
35 active_boundary_ids_(std::move(active_boundary_ids)),
36 active_time_slices_(std::move(active_time_slices))
37 {
38 assert(!states_.empty());
39 assert(states_.size() == diff_caches_.size());
40
41 // Static problem support is not implemented.
42 for (auto &s : states_)
43 {
44 if (!s->problem->is_time_dependent())
45 {
46 log_and_throw_adjoint_error("Fail to construct dirichlet boundary variable to simulation. Reason: only transient simulations supported.");
47 }
48 }
49
50 time_steps_ = states_[0]->args["time"]["time_steps"].get<int>();
51
52 // Expand implicit all-active boundary id selection.
53 if (active_boundary_ids_.size() == 0)
54 {
55 // PolyFEM core lacks dedicate API to query all boundary ids. Use API for boundary
56 // active dimension to collect boundary ids.
57
58 // boundary_dims is a map [boundary id, active dim].
59 auto boundary_dims = states_[0]->boundary_conditions_ids("dirichlet_boundary");
60 active_boundary_ids_.resize(boundary_dims.size());
61 int i = 0;
62 for (auto [id, _] : boundary_dims)
63 {
65 ++i;
66 }
67 std::sort(active_boundary_ids_.begin(), active_boundary_ids_.end());
68 }
69 // Expand implicit all-active time slice selection.
70 if (active_time_slices_.size() == 0)
71 {
72 active_time_slices_ = Eigen::VectorXi::LinSpaced(time_steps_, 0, time_steps_ - 1);
73 }
74
75 // Validate expanded active selections against every state.
76 std::string reason;
78 {
79 log_and_throw_adjoint_error("Fail to construct dirichlet boundary variable to simulation. Reason: {}", reason);
80 }
82 {
83 log_and_throw_adjoint_error("Fail to construct dirichlet boundary variable to simulation. Reason: {}", reason);
84 }
85
87 }
88
90 {
91 return "dirichlet-boundary";
92 }
93
98
100 {
101 for (auto &s : states_)
102 {
103 if (s.get() == &target)
104 {
105 return true;
106 }
107 }
108 return false;
109 }
110
112 {
113 Eigen::VectorXd y = parametrization_.eval(x);
114 assert(y.size() == para_out_dof());
115
116 int boundary_num = active_boundary_ids_.size();
117 for (auto &s : states_)
118 {
119 auto tensor_problem = std::dynamic_pointer_cast<polyfem::assembler::GenericTensorProblem>(s->problem);
120 if (!tensor_problem)
121 {
122 log_and_throw_adjoint_error("Only tensor problems are supported.");
123 }
124
125 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
126 {
127 int t = active_time_slices_(ti) + 1;
128
129 for (int bi = 0; bi < boundary_num; ++bi)
130 {
131 int boundary_id = active_boundary_ids_(bi);
132 int offset = (ti * boundary_num + bi) * dim_;
133 tensor_problem->update_dirichlet_boundary(boundary_id, t, y.segment(offset, dim_));
134 }
135 }
136 }
137 }
138
139 void DirichletBoundaryVariableToSimulation::update_state_variables(const Eigen::VectorXd &x, Eigen::VectorXd &state_variables) const
140 {
141 // This is not implemented because in practice, we only call this method for ShapeVariableToSimulation.
142 log_and_throw_adjoint_error("update_state_variables not implemented in DirichletBoundaryVariableToSimulation.");
143 }
144
145 Eigen::VectorXd DirichletBoundaryVariableToSimulation::compute_adjoint_term(const Eigen::VectorXd &x) const
146 {
147 Eigen::VectorXd term = Eigen::VectorXd::Zero(para_out_dof());
148
149 for (int si = 0; si < states_.size(); ++si)
150 {
151 auto &state = states_[si];
152 auto &diff_cache = diff_caches_[si];
153
154 Eigen::MatrixXd adjoint_p = get_adjoint_mat(*state, *diff_cache, 0);
155 Eigen::MatrixXd adjoint_nu = get_adjoint_mat(*state, *diff_cache, 1);
156
157 Eigen::VectorXd node_term;
158 AdjointTools::dJ_dirichlet_transient_adjoint_term(*state, adjoint_nu, adjoint_p, node_term);
159
160 int boundary_node_num = state->boundary_nodes.size();
161 assert(node_term.size() == time_steps_ * boundary_node_num);
162
163 // dJ_dirichlet_transient_adjoint_term compute adjoint terms per boundary node.
164 // Gather FE node value into boundary selection value.
165 const BoundaryNodeMap &map = boundary_node_maps_[si];
166 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
167 {
168 auto seg = node_term.segment(active_time_slices_(ti) * boundary_node_num, boundary_node_num);
169 for (int bi = 0; bi < active_boundary_ids_.size(); ++bi)
170 {
171 for (int d = 0; d < dim_; ++d)
172 {
173 double sum = 0;
174 for (int offset : map[bi][d])
175 {
176 sum += seg(offset);
177 }
178 int boundary_count = ti * active_boundary_ids_.size() + bi;
179 term(boundary_count * dim_ + d) += sum;
180 }
181 }
182 }
183 }
184
185 assert(term.size() == para_out_dof());
186 return parametrization_.apply_jacobian(term, x);
187 }
188
193
195 {
196 Eigen::VectorXd y = Eigen::VectorXd::Zero(para_out_dof());
197 std::vector<json> boundary_jsons =
198 utils::json_as_array(states_[0]->args["boundary_conditions"]["dirichlet_boundary"]);
199
200 for (int bi = 0; bi < active_boundary_ids_.size(); ++bi)
201 {
202 int boundary_id = active_boundary_ids_(bi);
203 auto pred = [boundary_id](const json &bc) { return bc["id"].get<int>() == boundary_id; };
204 auto iter = std::find_if(boundary_jsons.begin(), boundary_jsons.end(), pred);
205 if (iter == boundary_jsons.end())
206 {
207 log_and_throw_adjoint_error("Cannot find boundary id {} in JSON.", boundary_id);
208 }
209
210 // User can specify dirichlet boundary value via list of value, const value, expression, or file.
211 // We only support list of value.
212 const json &value = (*iter)["value"];
213 Eigen::MatrixXd dirichlet_mat;
214 try
215 {
216 dirichlet_mat = value;
217 }
218 catch (std::exception &err)
219 {
220 }
221
222 int required_cols = time_steps_ + 1;
223 if (dirichlet_mat.rows() != dim_ || dirichlet_mat.cols() != required_cols)
224 {
225 logger().warn("Unsupported value type for dirichlet boundary id {}; inverse_eval falling back to zero.", boundary_id);
226 dirichlet_mat = Eigen::MatrixXd::Zero(dim_, time_steps_ + 1);
227 }
228
229 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
230 {
231 int slice = active_time_slices_(ti);
232 for (int c = 0; c < dim_; ++c)
233 {
234 int boundary_count = ti * active_boundary_ids_.size() + bi;
235 y(boundary_count * dim_ + c) = dirichlet_mat(c, slice + 1);
236 }
237 }
238 }
239
241 }
242
243 Eigen::VectorXd DirichletBoundaryVariableToSimulation::apply_parametrization_jacobian(const Eigen::VectorXd &, const Eigen::VectorXd &) const
244 {
245 // Not implemented because there's no user
246 log_and_throw_adjoint_error("apply_parametrization_jacobian is not implemented in {} variable to simulation.", name());
247 }
248
253
255 {
256 // Map active boundary id to it's offset in active_boundary_ids_ vector.
257 std::unordered_map<int, int> active_boundary_id_offset;
258 for (int i = 0; i < active_boundary_ids_.size(); ++i)
259 {
260 active_boundary_id_offset[active_boundary_ids_(i)] = i;
261 }
262
263 boundary_node_maps_.clear();
264 boundary_node_maps_.resize(states_.size());
265
266 for (int si = 0; si < states_.size(); ++si)
267 {
268 const legacy::State &state = *states_[si];
269
270 // Map boundary node (FE space dof) to offset in boundary_nodes vector.
271 std::unordered_map<int, int> boundary_node_offset;
272 for (int p = 0; p < state.boundary_nodes.size(); ++p)
273 {
274 boundary_node_offset[state.boundary_nodes[p]] = p;
275 }
276
277 BoundaryNodeMap map(active_boundary_ids_.size(), std::vector<std::vector<int>>(dim_));
278
279 // - LocalBoundary stores boundary primitives (edge/face) per element.
280 // - Each primitive can be tagged with a boundary id (selection id).
281 // - Each primitive can be associated with mutiple geometric nodes (vertices).
282 // - Basis maps each geometric node to FE dof.
283 // - boundary_nodes stores all boundary dof in FE space.
284 //
285 // So to map boundary id to offsets in boundary_nodes, we have to
286 // 1. Find primitives selected by boundary id.
287 // 2. Map primitives to geoemtric nodes.
288 // 3. Map geometric nodes to FE dof.
289 // 4. Map FE dof to offset in boundary_nodes.
290 for (auto &lb : state.local_boundary)
291 {
292 int e = lb.element_id();
293 const basis::ElementBases &bs = state.bases[e];
294
295 for (int i = 0; i < lb.size(); ++i)
296 {
297 int primitive_global_id = lb.global_primitive_id(i);
298 int boundary_id = state.mesh->get_boundary_id(primitive_global_id);
299
300 // 1. Find primitives selected by active boundary id.
301 auto iter = active_boundary_id_offset.find(boundary_id);
302 if (iter == active_boundary_id_offset.end())
303 {
304 continue;
305 }
306 int boundary_offset = iter->second;
307
308 // 2. Map primitives to geometric nodes.
309 Eigen::VectorXi geom_nodes = bs.local_nodes_for_primitive(primitive_global_id, *state.mesh);
310 for (int geom_node : geom_nodes)
311 {
312 // 3. Map geometric nodes to FE dof.
313 auto &local_to_globals = bs.bases[geom_node].global();
314 for (auto &lg : local_to_globals)
315 {
316 for (int c = 0; c < dim_; ++c)
317 {
318 int fe_dof = lg.index * dim_ + c;
319
320 // 4. Map FE dof to offset in boundary_nodes.
321 auto iter = boundary_node_offset.find(fe_dof);
322 assert(iter != boundary_node_offset.end() && "Expect boundary dof to exist in boundary_nodes");
323 map[boundary_offset][c].push_back(iter->second);
324 }
325 }
326 }
327 }
328 }
329
330 boundary_node_maps_[si] = std::move(map);
331 }
332 }
333
334} // namespace polyfem::solver
int y
int x
Stores the basis functions for a given element in a mesh (facet in 2d, cell in 3d).
Eigen::VectorXi local_nodes_for_primitive(const int local_index, const mesh::Mesh &mesh) const
std::vector< Basis > bases
one basis function per node in the element
main class that contains the polyfem solver and all its state
Definition State.hpp:114
std::vector< mesh::LocalBoundary > local_boundary
mapping from elements to nodes for dirichlet boundary conditions
Definition State.hpp:561
std::unique_ptr< mesh::Mesh > mesh
current mesh, it can be a Mesh2D or Mesh3D
Definition State.hpp:594
std::vector< basis::ElementBases > bases
FE bases, the size is #elements.
Definition State.hpp:207
std::vector< int > boundary_nodes
list of boundary nodes
Definition State.hpp:555
Eigen::VectorXd apply_jacobian(const Eigen::VectorXd &grad_full, const Eigen::VectorXd &x) const override
Apply jacobian for chain rule.
Eigen::VectorXd inverse_eval(const Eigen::VectorXd &y) const override
Eval x = f^-1 (y).
int inverse_size(int y_size) const override
Compute DOF of x given DOF of y.
Eigen::VectorXd eval(const Eigen::VectorXd &x) const override
Eval y = f(x).
std::vector< BoundaryNodeMap > boundary_node_maps_
boundary node map per state.
void update(const Eigen::VectorXd &x) override
Update forward simulation states from optimization variables.
Eigen::VectorXd apply_parametrization_jacobian(const Eigen::VectorXd &term, const Eigen::VectorXd &x) const override
Apply parametrization jacobian to compute the gradient w.r.t.
int inverse_dof() const override
Compute optimization variables dof.
bool affect_state(const legacy::State &target) const override
Return true if current var2sim maps to target state.
Eigen::VectorXd inverse_eval() const override
Compute optimization variables from forward simulation legacy::State.
Eigen::VectorXd compute_adjoint_term(const Eigen::VectorXd &x) const override
Compute adjoint contribution of objective gradient.
std::vector< std::vector< std::vector< int > > > BoundaryNodeMap
boundary order in this var2sim -> component (dim) -> offset in state.boundary_nodes
DirichletBoundaryVariableToSimulation(StatePtrs states, DiffCachePtrs diff_caches, CompositeParametrization parametrizations, Eigen::VectorXi active_boundary_ids, Eigen::VectorXi active_time_slices)
Construct DirichletBoundaryVariableToSimulation.
void update_state_variables(const Eigen::VectorXd &x, Eigen::VectorXd &state_variables) const override
Update state variables from optimization variables.
void dJ_dirichlet_transient_adjoint_term(const legacy::State &state, const Eigen::MatrixXd &adjoint_nu, const Eigen::MatrixXd &adjoint_p, Eigen::VectorXd &one_form)
bool is_active_time_slices_valid(const Eigen::VectorXi &active_time_slices, const std::vector< std::shared_ptr< legacy::State > > &states, std::string &reason)
Validate active time slices selection given states.
bool is_active_dirichlet_boundary_ids_valid(const Eigen::VectorXi &active_boundary_ids, const std::vector< std::shared_ptr< legacy::State > > &states, std::string &reason)
Validate active Dirichlet boundary ids selection given states.
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:38
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_adjoint_error(const std::string &msg)
Definition Logger.cpp:79
Eigen::MatrixXd get_adjoint_mat(const legacy::State &state, const DiffCache &diff_cache, int type)
Get adjoint parameter nu or p.