PolyFEM
Loading...
Searching...
No Matches
BCLagrangianForm.cpp
Go to the documentation of this file.
2
4#include <igl/slice.h>
5
6namespace polyfem::solver
7{
9 const std::vector<int> &boundary_nodes,
10 const std::vector<mesh::LocalBoundary> &local_boundary,
11 const std::vector<mesh::LocalBoundary> &local_neumann_boundary,
12 const QuadratureOrders &n_boundary_samples,
13 const StiffnessMatrix &mass,
14 const assembler::RhsAssembler &rhs_assembler,
15 const size_t obstacle_ndof,
16 const bool is_time_dependent,
17 const double t)
18 : boundary_nodes_(boundary_nodes),
19 local_boundary_(&local_boundary),
20 local_neumann_boundary_(&local_neumann_boundary),
21 n_boundary_samples_(n_boundary_samples),
22 rhs_assembler_(&rhs_assembler),
23 is_time_dependent_(is_time_dependent),
24 n_dofs_(ndof)
25 {
26 init_masked_lumped_mass(mass, obstacle_ndof);
27 update_target(t); // initialize b_
28 }
29
31 const std::vector<int> &boundary_nodes,
32 const StiffnessMatrix &mass,
33 const size_t obstacle_ndof,
34 const Eigen::MatrixXd &target_x)
35 : boundary_nodes_(boundary_nodes),
36 local_boundary_(nullptr),
37 local_neumann_boundary_(nullptr),
38 n_boundary_samples_({{0, 0}}),
39 rhs_assembler_(nullptr),
40 is_time_dependent_(false),
41 n_dofs_(ndof)
42 {
43 init_masked_lumped_mass(mass, obstacle_ndof);
44
45 b_ = target_x;
46 b_proj_ = b_;
47 igl::slice(b_, constraints_, 1, b_);
48 }
49
51 const StiffnessMatrix &mass,
52 const size_t obstacle_ndof)
53 {
54 std::vector<Eigen::Triplet<double>> A_triplets;
55
56 constraints_.resize(boundary_nodes_.size());
57 for (int i = 0; i < boundary_nodes_.size(); ++i)
58 {
59 const int bn = boundary_nodes_[i];
60
61 constraints_[i] = bn;
62 A_triplets.emplace_back(i, bn, 1.0);
63 }
64 A_.resize(boundary_nodes_.size(), n_dofs_);
65 A_.setFromTriplets(A_triplets.begin(), A_triplets.end());
66 A_.makeCompressed();
67
69 {
70 double min_diag = std::numeric_limits<double>::max();
71 double max_diag = 0;
72 for (int k = 0; k < masked_lumped_mass_.outerSize(); ++k)
73 {
74 for (StiffnessMatrix::InnerIterator it(masked_lumped_mass_, k); it; ++it)
75 {
76 if (it.col() == it.row())
77 {
78 min_diag = std::min(min_diag, it.value());
79 max_diag = std::max(max_diag, it.value());
80 }
81 }
82 }
83 if (max_diag <= 0 || min_diag <= 0 || min_diag / max_diag < 1e-16)
84 {
85 logger().warn("Lumped mass matrix ill-conditioned. Setting lumped mass matrix to identity.");
87 }
88 }
89
90 assert(n_dofs_ == masked_lumped_mass_.rows() && n_dofs_ == masked_lumped_mass_.cols());
91 // Give the collision obstacles a entry in the lumped mass matrix
92 if (obstacle_ndof != 0)
93 {
94 const int n_fe_dof = n_dofs_ - obstacle_ndof;
95 const double avg_mass = masked_lumped_mass_.diagonal().head(n_fe_dof).mean();
96 for (int i = n_fe_dof; i < n_dofs_; ++i)
97 {
98 masked_lumped_mass_.coeffRef(i, i) = avg_mass;
99 }
100 }
101
104 assert(boundary_nodes_.size() == masked_lumped_mass_.rows() && boundary_nodes_.size() == masked_lumped_mass_.cols());
105
107 std::vector<Eigen::Triplet<double>> tmp_triplets;
108 tmp_triplets.reserve(masked_lumped_mass_.nonZeros());
109 for (int k = 0; k < masked_lumped_mass_.outerSize(); ++k)
110 {
111 for (StiffnessMatrix::InnerIterator it(masked_lumped_mass_, k); it; ++it)
112 {
113 assert(it.col() == k);
114 tmp_triplets.emplace_back(it.row(), it.col(), sqrt(it.value()));
115 }
116 }
117
118 masked_lumped_mass_sqrt_.setFromTriplets(tmp_triplets.begin(), tmp_triplets.end());
119 masked_lumped_mass_sqrt_.makeCompressed();
120
121 std::vector<bool> is_contraints(n_dofs_, false);
122 for (int i = 0; i < boundary_nodes_.size(); ++i)
123 is_contraints[boundary_nodes_[i]] = true;
124
125 int index = 0;
126 A_triplets.clear();
128 old_to_new_.assign(n_dofs_, -1);
129 for (int i = 0; i < n_dofs_; ++i)
130 {
131 if (is_contraints[i])
132 continue;
133
134 A_triplets.emplace_back(i, index, 1.0);
135 not_constraints_[index] = i;
136 old_to_new_[i] = index;
137 index++;
138 }
139 A_proj_.resize(n_dofs_, index);
140 A_proj_.setFromTriplets(A_triplets.begin(), A_triplets.end());
141 A_proj_.makeCompressed();
142
143 lagr_mults_.resize(boundary_nodes_.size());
144 lagr_mults_.setZero();
145 }
146
147 bool BCLagrangianForm::can_project() const { return true; }
148
149 void BCLagrangianForm::project_gradient(Eigen::VectorXd &grad) const
150 {
151 // Assumes not_constraints_ is sorted
152 for (int i = 0; i < not_constraints_.size(); ++i)
153 grad[i] = grad[not_constraints_[i]];
154 grad.conservativeResize(not_constraints_.size());
155 }
156
158 {
159 // Drop rows and columns whose indices are constrained DOFs in a single
160 // linear scan over the ColMajor CSC storage, avoiding two back-to-back
161 // igl::slice calls (each of which builds a triplet list and runs a
162 // full setFromTriplets sort). On a 3D mat-twist run this cut the
163 // call from ~63ms to ~5ms (~12x) and reduced NLProblem::hessian by
164 // ~40%.
165 assert(hessian.rows() == n_dofs_ && hessian.cols() == n_dofs_);
166 hessian.makeCompressed();
167
168 const int n_red = static_cast<int>(not_constraints_.size());
169
170 // Pass 1: count total nnz of the reduced matrix.
171 StiffnessMatrix::StorageIndex total_nnz = 0;
172 for (int k = 0; k < hessian.outerSize(); ++k)
173 {
174 if (old_to_new_[k] < 0)
175 continue;
176 for (StiffnessMatrix::InnerIterator it(hessian, k); it; ++it)
177 {
178 if (old_to_new_[it.row()] >= 0)
179 ++total_nnz;
180 }
181 }
182
183 // Pass 2: fill column-by-column in compressed CSC order. Because
184 // not_constraints_ is sorted ascending, old_to_new_ is monotonic on
185 // its non-negative entries, so the filtered rows come out ascending
186 // within each column — which is what insertBack requires.
187 StiffnessMatrix out(n_red, n_red);
188 out.reserve(total_nnz);
189 for (int k = 0; k < hessian.outerSize(); ++k)
190 {
191 const int new_col = old_to_new_[k];
192 if (new_col < 0)
193 continue;
194 out.startVec(new_col);
195 for (StiffnessMatrix::InnerIterator it(hessian, k); it; ++it)
196 {
197 const int new_row = old_to_new_[it.row()];
198 if (new_row < 0)
199 continue;
200 out.insertBack(new_row, new_col) = it.value();
201 }
202 }
203 out.finalize();
204
205 hessian = std::move(out);
206 }
207
208 double BCLagrangianForm::value_unweighted(const Eigen::VectorXd &x) const
209 {
210 const Eigen::VectorXd dist = A_ * x - b_;
211 const double L_penalty = -lagr_mults_.transpose() * masked_lumped_mass_sqrt_ * dist;
212 const double A_penalty = 0.5 * dist.transpose() * masked_lumped_mass_ * dist;
213
214 return L_weight() * L_penalty + A_weight() * A_penalty;
215 }
216
217 void BCLagrangianForm::first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
218 {
219 gradv = L_weight() * A_.transpose() * (-(masked_lumped_mass_sqrt_ * lagr_mults_) + A_weight() * (masked_lumped_mass_ * (A_ * x - b_)));
220 }
221
222 void BCLagrangianForm::second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const
223 {
224 hessian = A_weight() * A_.transpose() * masked_lumped_mass_ * A_;
225 }
226
227 void BCLagrangianForm::update_quantities(const double t, const Eigen::VectorXd &)
228 {
230 update_target(t);
231 }
232
233 double BCLagrangianForm::compute_error(const Eigen::VectorXd &x) const
234 {
235 // return (b_ - x).transpose() * A_ * (b_ - x);
236 const Eigen::VectorXd res = A_ * x - b_;
237 return res.squaredNorm();
238 }
239
241 {
242 assert(rhs_assembler_ != nullptr);
243 assert(local_boundary_ != nullptr);
244 assert(local_neumann_boundary_ != nullptr);
245 b_.setZero(n_dofs_, 1);
248 *local_neumann_boundary_, b_, Eigen::MatrixXd(), t);
249 b_proj_ = b_;
250 b_ = igl::slice(b_, constraints_, 1);
251 }
252
253 void BCLagrangianForm::update_lagrangian(const Eigen::VectorXd &x, const double k_al)
254 {
255 k_al_ = k_al;
256 lagr_mults_ -= k_al * masked_lumped_mass_sqrt_ * (A_ * x - b_);
257 }
258} // namespace polyfem::solver
int x
void set_bc(const std::vector< mesh::LocalBoundary > &local_boundary, const std::vector< int > &bounday_nodes, const QuadratureOrders &resolution, const std::vector< mesh::LocalBoundary > &local_neumann_boundary, Eigen::MatrixXd &rhs, const Eigen::MatrixXd &displacement=Eigen::MatrixXd(), const double t=1) const
Eigen::VectorXd lagr_mults_
vector of lagrange multipliers
StiffnessMatrix A_proj_
Constraints projection matrix.
Eigen::MatrixXd b_proj_
Constraints projection value.
Eigen::VectorXi not_constraints_
Not Constraints.
void first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const override
Compute the first derivative of the value wrt x.
const QuadratureOrders n_boundary_samples_
void update_quantities(const double t, const Eigen::VectorXd &x) override
Update time dependent quantities.
void update_target(const double t)
Update target x to the Dirichlet boundary values at time t.
std::vector< int > old_to_new_
Map from full DOF index to reduced DOF index (-1 if constrained).
virtual bool can_project() const override
void update_lagrangian(const Eigen::VectorXd &x, const double k_al) override
double compute_error(const Eigen::VectorXd &x) const override
const assembler::RhsAssembler * rhs_assembler_
Reference to the RHS assembler.
void second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const override
Compute the second derivative of the value wrt x.
virtual void project_gradient(Eigen::VectorXd &grad) const override
void init_masked_lumped_mass(const StiffnessMatrix &mass, const size_t obstacle_ndof)
Initialize the masked lumped mass matrix.
const std::vector< mesh::LocalBoundary > * local_neumann_boundary_
const std::vector< mesh::LocalBoundary > * local_boundary_
StiffnessMatrix masked_lumped_mass_sqrt_
sqrt mass matrix masked by the AL dofs
double value_unweighted(const Eigen::VectorXd &x) const override
Compute the value of the form.
virtual void project_hessian(StiffnessMatrix &hessian) const override
BCLagrangianForm(const int ndof, const std::vector< int > &boundary_nodes, const std::vector< mesh::LocalBoundary > &local_boundary, const std::vector< mesh::LocalBoundary > &local_neumann_boundary, const QuadratureOrders &n_boundary_samples, const StiffnessMatrix &mass, const assembler::RhsAssembler &rhs_assembler, const size_t obstacle_ndof, const bool is_time_dependent, const double t)
Construct a new BCLagrangianForm object with a time dependent Dirichlet boundary.
StiffnessMatrix masked_lumped_mass_
mass matrix masked by the AL dofs
const std::vector< int > & boundary_nodes_
Eigen::VectorXi constraints_
Constraints.
Eigen::SparseMatrix< double > lump_matrix(const Eigen::SparseMatrix< double > &M)
Lump each row of a matrix into the diagonal.
Eigen::SparseMatrix< double > sparse_identity(int rows, int cols)
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:44
std::array< int, 2 > QuadratureOrders
Definition Types.hpp:19
Eigen::SparseMatrix< double, Eigen::ColMajor > StiffnessMatrix
Definition Types.hpp:24