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 const BCLumpingMode lumping)
19 : boundary_nodes_(boundary_nodes),
20 local_boundary_(&local_boundary),
21 local_neumann_boundary_(&local_neumann_boundary),
22 n_boundary_samples_(n_boundary_samples),
23 rhs_assembler_(&rhs_assembler),
24 is_time_dependent_(is_time_dependent),
25 n_dofs_(ndof)
26 {
27 lumping_ = lumping;
28 init_masked_lumped_mass(mass, obstacle_ndof);
29 update_target(t); // initialize b_
30 }
31
33 const std::vector<int> &boundary_nodes,
34 const StiffnessMatrix &mass,
35 const size_t obstacle_ndof,
36 const Eigen::MatrixXd &target_x)
37 : boundary_nodes_(boundary_nodes),
38 local_boundary_(nullptr),
39 local_neumann_boundary_(nullptr),
40 n_boundary_samples_({{0, 0}}),
41 rhs_assembler_(nullptr),
42 is_time_dependent_(false),
43 n_dofs_(ndof)
44 {
45 init_masked_lumped_mass(mass, obstacle_ndof);
46
47 b_ = target_x;
48 b_proj_ = b_;
49 igl::slice(b_, constraints_, 1, b_);
50 }
51
53 const StiffnessMatrix &mass,
54 const size_t obstacle_ndof)
55 {
56 std::vector<Eigen::Triplet<double>> A_triplets;
57
58 constraints_.resize(boundary_nodes_.size());
59 for (int i = 0; i < boundary_nodes_.size(); ++i)
60 {
61 const int bn = boundary_nodes_[i];
62
63 constraints_[i] = bn;
64 A_triplets.emplace_back(i, bn, 1.0);
65 }
66 A_.resize(boundary_nodes_.size(), n_dofs_);
67 A_.setFromTriplets(A_triplets.begin(), A_triplets.end());
68 A_.makeCompressed();
69
70 const auto is_ill_conditioned = [](const StiffnessMatrix &lumped) {
71 double min_diag = std::numeric_limits<double>::max();
72 double max_diag = 0;
73 for (int k = 0; k < lumped.outerSize(); ++k)
74 {
75 for (StiffnessMatrix::InnerIterator it(lumped, k); it; ++it)
76 {
77 if (it.col() == it.row())
78 {
79 min_diag = std::min(min_diag, it.value());
80 max_diag = std::max(max_diag, it.value());
81 }
82 }
83 }
84 return max_diag <= 0 || min_diag <= 0 || min_diag / max_diag < 1e-16;
85 };
86
87 if (mass.size() == 0)
89 else if (lumping_ == BCLumpingMode::HRZ)
91 else
92 {
94 if (is_ill_conditioned(masked_lumped_mass_))
95 {
96 // row-sum lumping is invalid for bases of order >= 2 (rows of the
97 // consistent mass can sum to zero or negative values)
98 logger().warn("Row-sum lumped mass ill-conditioned (expected for order >= 2); using HRZ-style diagonal-scaling lumping for the BC penalty metric.");
100 }
101 }
102 // last resort: only reject NONPOSITIVE metrics (a large diagonal spread
103 // is physical grading, not damage), and keep mass units by scaling the
104 // identity with the average nodal mass
105 if (mass.size() != 0)
106 {
107 double min_diag = std::numeric_limits<double>::max();
108 double diag_sum = 0;
109 for (int k = 0; k < masked_lumped_mass_.outerSize(); ++k)
110 for (StiffnessMatrix::InnerIterator it(masked_lumped_mass_, k); it; ++it)
111 if (it.col() == it.row())
112 {
113 min_diag = std::min(min_diag, it.value());
114 diag_sum += it.value();
115 }
116 if (min_diag <= 0)
117 {
118 const double avg_mass = diag_sum > 0 ? diag_sum / n_dofs_ : 1.0;
119 logger().warn("Lumped mass matrix has nonpositive entries; using identity scaled by the average nodal mass ({}).", avg_mass);
121 }
122 }
123
124 assert(n_dofs_ == masked_lumped_mass_.rows() && n_dofs_ == masked_lumped_mass_.cols());
125 // Give the collision obstacles a entry in the lumped mass matrix
126 if (obstacle_ndof != 0)
127 {
128 const int n_fe_dof = n_dofs_ - obstacle_ndof;
129 const double avg_mass = masked_lumped_mass_.diagonal().head(n_fe_dof).mean();
130 for (int i = n_fe_dof; i < n_dofs_; ++i)
131 {
132 masked_lumped_mass_.coeffRef(i, i) = avg_mass;
133 }
134 }
135
138 assert(boundary_nodes_.size() == masked_lumped_mass_.rows() && boundary_nodes_.size() == masked_lumped_mass_.cols());
139
141 std::vector<Eigen::Triplet<double>> tmp_triplets;
142 tmp_triplets.reserve(masked_lumped_mass_.nonZeros());
143 for (int k = 0; k < masked_lumped_mass_.outerSize(); ++k)
144 {
145 for (StiffnessMatrix::InnerIterator it(masked_lumped_mass_, k); it; ++it)
146 {
147 assert(it.col() == k);
148 tmp_triplets.emplace_back(it.row(), it.col(), sqrt(it.value()));
149 }
150 }
151
152 masked_lumped_mass_sqrt_.setFromTriplets(tmp_triplets.begin(), tmp_triplets.end());
153 masked_lumped_mass_sqrt_.makeCompressed();
154
155 std::vector<bool> is_contraints(n_dofs_, false);
156 for (int i = 0; i < boundary_nodes_.size(); ++i)
157 is_contraints[boundary_nodes_[i]] = true;
158
159 int index = 0;
160 A_triplets.clear();
162 old_to_new_.assign(n_dofs_, -1);
163 for (int i = 0; i < n_dofs_; ++i)
164 {
165 if (is_contraints[i])
166 continue;
167
168 A_triplets.emplace_back(i, index, 1.0);
169 not_constraints_[index] = i;
170 old_to_new_[i] = index;
171 index++;
172 }
173 A_proj_.resize(n_dofs_, index);
174 A_proj_.setFromTriplets(A_triplets.begin(), A_triplets.end());
175 A_proj_.makeCompressed();
176
177 lagr_mults_.resize(boundary_nodes_.size());
178 lagr_mults_.setZero();
179 }
180
181 bool BCLagrangianForm::can_project() const { return true; }
182
183 void BCLagrangianForm::project_gradient(Eigen::VectorXd &grad) const
184 {
185 // Assumes not_constraints_ is sorted
186 for (int i = 0; i < not_constraints_.size(); ++i)
187 grad[i] = grad[not_constraints_[i]];
188 grad.conservativeResize(not_constraints_.size());
189 }
190
191 void BCLagrangianForm::project_diag(Eigen::VectorXd &diag) const
192 {
193 // Assumes not_constraints_ is sorted
194 for (int i = 0; i < not_constraints_.size(); ++i)
195 diag[i] = diag[not_constraints_[i]];
196 diag.conservativeResize(not_constraints_.size());
197 }
198
200 {
201 // Drop rows and columns whose indices are constrained DOFs in a single
202 // linear scan over the ColMajor CSC storage, avoiding two back-to-back
203 // igl::slice calls (each of which builds a triplet list and runs a
204 // full setFromTriplets sort). On a 3D mat-twist run this cut the
205 // call from ~63ms to ~5ms (~12x) and reduced NLProblem::hessian by
206 // ~40%.
207 assert(hessian.rows() == n_dofs_ && hessian.cols() == n_dofs_);
208 hessian.makeCompressed();
209
210 const int n_red = static_cast<int>(not_constraints_.size());
211
212 // Pass 1: count total nnz of the reduced matrix.
213 StiffnessMatrix::StorageIndex total_nnz = 0;
214 for (int k = 0; k < hessian.outerSize(); ++k)
215 {
216 if (old_to_new_[k] < 0)
217 continue;
218 for (StiffnessMatrix::InnerIterator it(hessian, k); it; ++it)
219 {
220 if (old_to_new_[it.row()] >= 0)
221 ++total_nnz;
222 }
223 }
224
225 // Pass 2: fill column-by-column in compressed CSC order. Because
226 // not_constraints_ is sorted ascending, old_to_new_ is monotonic on
227 // its non-negative entries, so the filtered rows come out ascending
228 // within each column — which is what insertBack requires.
229 StiffnessMatrix out(n_red, n_red);
230 out.reserve(total_nnz);
231 for (int k = 0; k < hessian.outerSize(); ++k)
232 {
233 const int new_col = old_to_new_[k];
234 if (new_col < 0)
235 continue;
236 out.startVec(new_col);
237 for (StiffnessMatrix::InnerIterator it(hessian, k); it; ++it)
238 {
239 const int new_row = old_to_new_[it.row()];
240 if (new_row < 0)
241 continue;
242 out.insertBack(new_row, new_col) = it.value();
243 }
244 }
245 out.finalize();
246
247 hessian = std::move(out);
248 }
249
250 double BCLagrangianForm::value_unweighted(const Eigen::VectorXd &x) const
251 {
252 const Eigen::VectorXd dist = A_ * x - b_;
253 const double L_penalty = -lagr_mults_.transpose() * masked_lumped_mass_sqrt_ * dist;
254 const double A_penalty = 0.5 * dist.transpose() * masked_lumped_mass_ * dist;
255
256 return L_weight() * L_penalty + A_weight() * A_penalty;
257 }
258
259 void BCLagrangianForm::first_derivative_unweighted(const Eigen::VectorXd &x, Eigen::VectorXd &gradv) const
260 {
261 gradv = L_weight() * A_.transpose() * (-(masked_lumped_mass_sqrt_ * lagr_mults_) + A_weight() * (masked_lumped_mass_ * (A_ * x - b_)));
262 }
263
264 void BCLagrangianForm::second_derivative_unweighted(const Eigen::VectorXd &x, StiffnessMatrix &hessian) const
265 {
266 hessian = A_weight() * A_.transpose() * masked_lumped_mass_ * A_;
267 }
268
269 void BCLagrangianForm::update_quantities(const double t, const Eigen::VectorXd &)
270 {
272 update_target(t);
273 }
274
275 double BCLagrangianForm::compute_error(const Eigen::VectorXd &x) const
276 {
277 // return (b_ - x).transpose() * A_ * (b_ - x);
278 const Eigen::VectorXd res = A_ * x - b_;
279 return res.squaredNorm();
280 }
281
283 {
284 assert(rhs_assembler_ != nullptr);
285 assert(local_boundary_ != nullptr);
286 assert(local_neumann_boundary_ != nullptr);
287 b_.setZero(n_dofs_, 1);
290 *local_neumann_boundary_, b_, Eigen::MatrixXd(), t);
291 b_proj_ = b_;
292 b_ = igl::slice(b_, constraints_, 1);
293 }
294
295 void BCLagrangianForm::update_lagrangian(const Eigen::VectorXd &x, const double k_al)
296 {
297 k_al_ = k_al;
298 lagr_mults_ -= k_al * masked_lumped_mass_sqrt_ * (A_ * x - b_);
299 }
300} // 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_
virtual void project_diag(Eigen::VectorXd &diag) const override
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, const BCLumpingMode lumping=BCLumpingMode::ROW_SUM)
Construct a new BCLagrangianForm object with a time dependent Dirichlet boundary.
BCLumpingMode lumping_
lumping mode for the mass metric
StiffnessMatrix masked_lumped_mass_
mass matrix masked by the AL dofs
const std::vector< int > & boundary_nodes_
Eigen::VectorXi constraints_
Constraints.
BCLumpingMode
Lumping mode for the mass metric of the BC penalty.
@ HRZ
Hinton-Rock-Zienkiewicz diagonal-scaling lumping.
Eigen::SparseMatrix< double > lump_matrix(const Eigen::SparseMatrix< double > &M)
Lump each row of a matrix into the diagonal.
Eigen::SparseMatrix< double > lump_matrix_hrz(const Eigen::SparseMatrix< double > &M)
Lump a (mass) matrix HRZ-style: keep the diagonal, scaled by a common factor so the total (sum of all...
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