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 VarFormPtrs varforms,
26 DiffCachePtrs diff_caches,
27 CompositeParametrization parametrizations,
28 Eigen::VectorXi active_boundary_ids,
29 Eigen::VectorXi active_time_slices)
30 : dim_(varforms[0]->get_mesh().dimension()),
31 time_steps_(0),
32 varforms_(std::move(varforms)),
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(!varforms_.empty());
39 assert(varforms_.size() == diff_caches_.size());
40
41 // Static problem support is not implemented.
42 for (auto &varform : varforms_)
43 {
44 if (!varform->get_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_ = varforms_[0]->get_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 = varforms_[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 varform.
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 &varform : varforms_)
102 {
103 if (varform.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 &varform : varforms_)
118 {
119 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
120 {
121 int t = active_time_slices_(ti) + 1;
122
123 for (int bi = 0; bi < boundary_num; ++bi)
124 {
125 int boundary_id = active_boundary_ids_(bi);
126 int offset = (ti * boundary_num + bi) * dim_;
127 varform->set_dirichlet_boundary(boundary_id, t, y.segment(offset, dim_));
128 }
129 }
130 }
131 }
132
133 void DirichletBoundaryVariableToSimulation::update_state_variables(const Eigen::VectorXd &x, Eigen::VectorXd &state_variables) const
134 {
135 // This is not implemented because in practice, we only call this method for ShapeVariableToSimulation.
136 log_and_throw_adjoint_error("update_state_variables not implemented in DirichletBoundaryVariableToSimulation.");
137 }
138
139 Eigen::VectorXd DirichletBoundaryVariableToSimulation::compute_adjoint_term(const Eigen::VectorXd &x) const
140 {
141 Eigen::VectorXd term = Eigen::VectorXd::Zero(para_out_dof());
142
143 for (int si = 0; si < varforms_.size(); ++si)
144 {
145 auto &varform = varforms_[si];
146 auto &diff_cache = diff_caches_[si];
147
148 Eigen::MatrixXd adjoint_p = get_adjoint_mat(*varform, *diff_cache, 0);
149 Eigen::MatrixXd adjoint_nu = get_adjoint_mat(*varform, *diff_cache, 1);
150
151 Eigen::VectorXd node_term;
152 AdjointTools::dJ_dirichlet_transient_adjoint_term(*varform, adjoint_nu, adjoint_p, node_term);
153
154 int boundary_node_num = varform->boundary_state().boundary_nodes.size();
155 assert(node_term.size() == time_steps_ * boundary_node_num);
156
157 // dJ_dirichlet_transient_adjoint_term compute adjoint terms per boundary node.
158 // Gather FE node value into boundary selection value.
159 const BoundaryNodeMap &map = boundary_node_maps_[si];
160 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
161 {
162 auto seg = node_term.segment(active_time_slices_(ti) * boundary_node_num, boundary_node_num);
163 for (int bi = 0; bi < active_boundary_ids_.size(); ++bi)
164 {
165 for (int d = 0; d < dim_; ++d)
166 {
167 double sum = 0;
168 for (int offset : map[bi][d])
169 {
170 sum += seg(offset);
171 }
172 int boundary_count = ti * active_boundary_ids_.size() + bi;
173 term(boundary_count * dim_ + d) += sum;
174 }
175 }
176 }
177 }
178
179 assert(term.size() == para_out_dof());
180 return parametrization_.apply_jacobian(term, x);
181 }
182
187
189 {
190 Eigen::VectorXd y = Eigen::VectorXd::Zero(para_out_dof());
191 std::vector<json> boundary_jsons =
192 utils::json_as_array(varforms_[0]->get_args()["boundary_conditions"]["dirichlet_boundary"]);
193
194 for (int bi = 0; bi < active_boundary_ids_.size(); ++bi)
195 {
196 int boundary_id = active_boundary_ids_(bi);
197 auto pred = [boundary_id](const json &bc) { return bc["id"].get<int>() == boundary_id; };
198 auto iter = std::find_if(boundary_jsons.begin(), boundary_jsons.end(), pred);
199 if (iter == boundary_jsons.end())
200 {
201 log_and_throw_adjoint_error("Cannot find boundary id {} in JSON.", boundary_id);
202 }
203
204 // User can specify dirichlet boundary value via list of value, const value, expression, or file.
205 // We only support list of value.
206 const json &value = (*iter)["value"];
207 Eigen::MatrixXd dirichlet_mat;
208 try
209 {
210 dirichlet_mat = value;
211 }
212 catch (std::exception &err)
213 {
214 }
215
216 int required_cols = time_steps_ + 1;
217 if (dirichlet_mat.rows() != dim_ || dirichlet_mat.cols() != required_cols)
218 {
219 logger().warn("Unsupported value type for dirichlet boundary id {}; inverse_eval falling back to zero.", boundary_id);
220 dirichlet_mat = Eigen::MatrixXd::Zero(dim_, time_steps_ + 1);
221 }
222
223 for (int ti = 0; ti < active_time_slices_.size(); ++ti)
224 {
225 int slice = active_time_slices_(ti);
226 for (int c = 0; c < dim_; ++c)
227 {
228 int boundary_count = ti * active_boundary_ids_.size() + bi;
229 y(boundary_count * dim_ + c) = dirichlet_mat(c, slice + 1);
230 }
231 }
232 }
233
235 }
236
237 Eigen::VectorXd DirichletBoundaryVariableToSimulation::apply_parametrization_jacobian(const Eigen::VectorXd &, const Eigen::VectorXd &) const
238 {
239 // Not implemented because there's no user
240 log_and_throw_adjoint_error("apply_parametrization_jacobian is not implemented in {} variable to simulation.", name());
241 }
242
247
249 {
250 // Map active boundary id to it's offset in active_boundary_ids_ vector.
251 std::unordered_map<int, int> active_boundary_id_offset;
252 for (int i = 0; i < active_boundary_ids_.size(); ++i)
253 {
254 active_boundary_id_offset[active_boundary_ids_(i)] = i;
255 }
256
257 boundary_node_maps_.clear();
258 boundary_node_maps_.resize(varforms_.size());
259
260 for (int si = 0; si < varforms_.size(); ++si)
261 {
262 const varform::DifferentiableVarForm &varform = *varforms_[si];
263
264 // Map boundary node (FE space dof) to offset in boundary_nodes vector.
265 std::unordered_map<int, int> boundary_node_offset;
266 for (int p = 0; p < varform.boundary_state().boundary_nodes.size(); ++p)
267 {
268 boundary_node_offset[varform.boundary_state().boundary_nodes[p]] = p;
269 }
270
271 BoundaryNodeMap map(active_boundary_ids_.size(), std::vector<std::vector<int>>(dim_));
272
273 // - LocalBoundary stores boundary primitives (edge/face) per element.
274 // - Each primitive can be tagged with a boundary id (selection id).
275 // - Each primitive can be associated with mutiple geometric nodes (vertices).
276 // - Basis maps each geometric node to FE dof.
277 // - boundary_nodes stores all boundary dof in FE space.
278 //
279 // So to map boundary id to offsets in boundary_nodes, we have to
280 // 1. Find primitives selected by boundary id.
281 // 2. Map primitives to geoemtric nodes.
282 // 3. Map geometric nodes to FE dof.
283 // 4. Map FE dof to offset in boundary_nodes.
284 for (auto &lb : varform.boundary_state().local_boundary)
285 {
286 int e = lb.element_id();
287 const basis::ElementBases &bs = varform.primary_space().basis_list()[e];
288
289 for (int i = 0; i < lb.size(); ++i)
290 {
291 int primitive_global_id = lb.global_primitive_id(i);
292 int boundary_id = varform.get_mesh().get_boundary_id(primitive_global_id);
293
294 // 1. Find primitives selected by active boundary id.
295 auto iter = active_boundary_id_offset.find(boundary_id);
296 if (iter == active_boundary_id_offset.end())
297 {
298 continue;
299 }
300 int boundary_offset = iter->second;
301
302 // 2. Map primitives to geometric nodes.
303 Eigen::VectorXi geom_nodes = bs.local_nodes_for_primitive(primitive_global_id, varform.get_mesh());
304 for (int geom_node : geom_nodes)
305 {
306 // 3. Map geometric nodes to FE dof.
307 auto &local_to_globals = bs.bases[geom_node].global();
308 for (auto &lg : local_to_globals)
309 {
310 for (int c = 0; c < dim_; ++c)
311 {
312 int fe_dof = lg.index * dim_ + c;
313
314 // 4. Map FE dof to offset in boundary_nodes.
315 auto iter = boundary_node_offset.find(fe_dof);
316 assert(iter != boundary_node_offset.end() && "Expect boundary dof to exist in boundary_nodes");
317 map[boundary_offset][c].push_back(iter->second);
318 }
319 }
320 }
321 }
322 }
323
324 boundary_node_maps_[si] = std::move(map);
325 }
326 }
327
328} // 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
virtual int get_boundary_id(const int primitive) const
Get the boundary selection of an element (face in 3d, edge in 2d)
Definition Mesh.hpp:499
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 varform.
void update(const Eigen::VectorXd &x) override
Update forward simulation varforms 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 affects_varform(const varform::DifferentiableVarForm &target) const override
Return true if current var2sim maps to target varform.
DirichletBoundaryVariableToSimulation(VarFormPtrs varforms, DiffCachePtrs diff_caches, CompositeParametrization parametrizations, Eigen::VectorXi active_boundary_ids, Eigen::VectorXi active_time_slices)
Construct DirichletBoundaryVariableToSimulation.
Eigen::VectorXd inverse_eval() const override
Compute optimization variables from forward simulation varform::DifferentiableVarForm.
Eigen::VectorXd compute_adjoint_term(const Eigen::VectorXd &x) const override
Compute adjoint contribution of objective gradient.
std::vector< std::shared_ptr< varform::DifferentiableVarForm > > VarFormPtrs
std::vector< std::vector< std::vector< int > > > BoundaryNodeMap
boundary order in this var2sim -> component (dim) -> offset in varform.boundary_state()....
void update_state_variables(const Eigen::VectorXd &x, Eigen::VectorXd &state_variables) const override
Update varform variables from optimization variables.
Optimization-facing interface implemented by differentiated VarForm adapters.
virtual const VarFormBoundaryState & boundary_state() const =0
virtual const mesh::Mesh & get_mesh() const =0
virtual const FESpace & primary_space() const =0
const std::vector< basis::ElementBases > & basis_list() const
Definition FESpace.hpp:109
void dJ_dirichlet_transient_adjoint_term(const varform::DifferentiableVarForm &varform, 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< varform::DifferentiableVarForm > > &varforms, std::string &reason)
Validate active time slices selection given varforms.
bool is_active_dirichlet_boundary_ids_valid(const Eigen::VectorXi &active_boundary_ids, const std::vector< std::shared_ptr< varform::DifferentiableVarForm > > &varforms, std::string &reason)
Validate active Dirichlet boundary ids selection given varforms.
std::vector< T > json_as_array(const json &j)
Return the value of a json object as an array.
Definition JSONUtils.hpp:41
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 varform::DifferentiableVarForm &varform, const DiffCache &diff_cache, int type)
Get adjoint parameter nu or p.
std::vector< mesh::LocalBoundary > local_boundary
Definition FESpace.hpp:155