43 using Matrix=
typename DirectSolverType::MatrixType;
46 static_assert(std::is_same_v<Matrix,typename IterativeSolverType::MatrixType>,
47 "WeakProblem solvers must use the same matrix type.");
48 static_assert(std::is_same_v<typename Matrix::Scalar,double>);
53 std::vector<Eigen::Index> reduced;
56 Eigen::MatrixXd modes,affineNullspace,globalRows;
57 Vector affineOffset,globalValues;
58 std::map<size_t,double> dirichlet;
61 std::shared_ptr<const void> fieldIdentity;
62 std::function<std::vector<Triplet>()> assemble;
63 std::function<
Vector()> force;
64 std::function<Eigen::MatrixXd()> penalty;
65 std::function<Constraints()> constraints;
66 std::function<void(
const Vector&)> commit;
67 std::vector<Triplet> fullTriplets;
68 std::vector<Eigen::Index> mapping;
69 Eigen::MatrixXd cachedModes;
72 Eigen::MatrixXd fullPenalty;
73 Eigen::MatrixXd reducedPenalty;
75 Eigen::MatrixXd affineModes;
77 Eigen::MatrixXd linearRows;
78 Eigen::MatrixXd reducedRows;
80 DirectSolverType direct;
81 IterativeSolverType iterative;
83 bool factorized=
false;
84 size_t factorizations=0;
86 static bool sameMatrix(
const Eigen::MatrixXd&
a,
const Eigen::MatrixXd&
b)
88 return a.rows()==
b.rows() &&
a.cols()==
b.cols() && (
a.array()==
b.array()).all();
92 Constraints makeConstraints(
const Field& field)
const
94 const auto& fe=field.fe();
95 constexpr int components=Field::dofPerNode;
96 const Eigen::Index n=field.gSize();
97 const Eigen::Index
k=affineModes.cols();
98 if(
k>0 && affineModes.rows()!=n)
100 throw std::invalid_argument(
"Affine FEM modes have the wrong number of DOFs.");
103 result.offset=Vector::Zero(n);
104 result.modes=Eigen::MatrixXd::Zero(n,
k);
109 result.reduced.resize(n,-1);
110 std::vector<size_t>
root(n);
111 for(
size_t node=0;node<fe.nodeSize();++node)
113 const size_t representative=fe.periodicRepresentative(node);
114 if(representative>=fe.nodeSize())
116 throw std::runtime_error(
"Invalid FEM periodic representative.");
118 const auto jump=(
field.periodicGradient()*fe.periodicShift(node)).eval();
119 for(
int c=0;
c<components;++
c)
121 root[node*components+
c]=representative*components+
c;
122 result.offset(node*components+
c)=jump(
c);
123 if(
field.periodicNodalOffset().size())
125 result.offset(node*components+
c)+=
field.periodicNodalOffset()(node*components+
c)
126 -
field.periodicNodalOffset()(representative*components+
c);
131 std::map<size_t,size_t> anchors;
132 for(
const auto&
bc :
field.dirichletConditions())
134 if(
bc.first>=
root.size() || !std::isfinite(
bc.second))
136 throw std::invalid_argument(
"Invalid FEM Dirichlet DOF or value.");
139 anchors.emplace(root[
bc.first],
bc.first);
141 Eigen::MatrixXd coupling=Eigen::MatrixXd::Zero(
result.dirichlet.size()-anchors.size(),
k);
142 Vector values=Vector::Zero(coupling.rows());
144 for(
const auto&
bc :
result.dirichlet)
146 const size_t anchor=anchors.at(root[
bc.first]);
153 coupling.row(row)=affineModes.row(
bc.first)-affineModes.row(anchor);
155 values(row)=
bc.second-prescribedOffset(
bc.first)-
result.dirichlet.at(anchor)+prescribedOffset(anchor);
159 result.affineOffset=Vector::Zero(
k);
160 result.affineNullspace=Eigen::MatrixXd::Identity(
k,
k);
161 if(
k>0 && coupling.rows()>0)
163 Eigen::JacobiSVD<Eigen::MatrixXd> svd(coupling,Eigen::ComputeThinU|Eigen::ComputeFullV);
164 svd.setThreshold(options.constraintTolerance);
165 result.affineOffset=svd.solve(values);
166 result.affineNullspace=svd.matrixV().rightCols(
k-svd.rank());
168 const Vector mismatch=coupling*
result.affineOffset-values;
169 for(Eigen::Index
i=0;
i<mismatch.size();++
i)
171 const double scale=std::max({1.0,std::abs(values(
i)),coupling.row(
i).norm()*
result.affineOffset.norm()});
172 if(std::abs(mismatch(
i))>options.constraintTolerance*scale)
174 throw std::invalid_argument(
"Incompatible affine/periodic and nodal Dirichlet conditions.");
177 std::map<size_t,Eigen::Index> independent;
178 for(Eigen::Index
i=0;
i<
n;++
i)
180 const auto anchor=anchors.find(root[
i]);
181 if(anchor!=anchors.end())
183 const size_t j=anchor->second;
187 result.modes.row(
i)-=affineModes.row(
j);
192 const auto entry=independent.emplace(root[
i],
result.size);
197 result.reduced[
i]=entry.first->second;
203 if(!
result.offset.allFinite() || !
result.modes.allFinite())
205 throw std::invalid_argument(
"Nonfinite FEM constraint elimination.");
212 result.globalValues=Vector::Zero(0);
213 if(linearRows.rows()>0)
215 if(linearRows.cols()!=n || linearValues.size()!=linearRows.rows())
217 throw std::invalid_argument(
"Invalid global FEM constraint size.");
219 Eigen::MatrixXd
rows=Eigen::MatrixXd::Zero(linearRows.rows(),
result.size);
220 for(Eigen::Index
i=0;
i<
n;++
i)
232 Vector rhs=linearValues-originalOffset;
233 Vector normalizedValues=linearValues;
234 Vector normalizedOffset=originalOffset;
235 for(Eigen::Index
i=0;
i<
rows.rows();++
i)
237 const double scale=linearRows.row(
i).norm();
242 normalizedValues(
i)/=scale;
243 normalizedOffset(
i)/=scale;
246 Eigen::MatrixXd
left=Eigen::MatrixXd::Identity(
rows.rows(),
rows.rows());
250 Eigen::JacobiSVD<Eigen::MatrixXd> svd(rows,Eigen::ComputeFullU);
251 const double largest=svd.singularValues().size()>0 ? svd.singularValues()(0) : 0.0;
256 svd.setThreshold(options.constraintTolerance*std::max(1.0,
rows.norm())/largest);
261 if((
left.rightCols(
left.cols()-rank).transpose()*rhs).norm()>
262 options.constraintTolerance*std::max({1.0,normalizedValues.norm(),normalizedOffset.norm()}))
264 throw std::invalid_argument(
"Incompatible global affine constraints and nodal Dirichlet conditions.");
267 result.globalValues=
left.leftCols(rank).transpose()*rhs;
272 bool prepare(
const Constraints&
c)
276 auto entries=assemble();
277 for(
const auto& entry : entries)
279 if(entry.row()<0 || entry.col()<0 || entry.row()>=
c.offset.size() ||
280 entry.col()>=
c.offset.size() || !std::isfinite(entry.value()))
282 throw std::invalid_argument(
"Invalid FEM matrix triplet.");
285 fullTriplets=std::move(entries);
288 if(factorized && mapping==
c.reduced && sameMatrix(cachedModes,
c.modes) && sameMatrix(reducedRows,
c.globalRows))
293 fullPenalty=penalty();
294 if(fullPenalty.cols()!=
c.offset.size() || !fullPenalty.allFinite())
296 throw std::invalid_argument(
"Invalid low-rank FEM penalty.");
298 reducedPenalty=Eigen::MatrixXd::Zero(fullPenalty.rows(),
c.size);
299 for(Eigen::Index
i=0;
i<
c.offset.size();++
i)
303 reducedPenalty.col(
c.reduced[
i])+=fullPenalty.col(
i);
306 const Eigen::Index modes=
c.modes.cols();
307 const Eigen::Index nodal=
c.size-modes;
310 reducedPenalty.rightCols(modes)=fullPenalty*
c.modes;
312 if(!reducedPenalty.allFinite())
314 throw std::runtime_error(
"Nonfinite reduced FEM penalty.");
316 std::vector<Triplet> entries;
317 entries.reserve(fullTriplets.size());
318 for(
const auto& entry : fullTriplets)
320 const auto row=
c.reduced[entry.row()];
321 const auto col=
c.reduced[entry.col()];
324 entries.emplace_back(row,col,entry.value());
329 Eigen::MatrixXd action=Eigen::MatrixXd::Zero(
c.offset.size(),modes);
330 Eigen::MatrixXd transposeAction=action;
331 for(
const auto& entry : fullTriplets)
333 action.row(entry.row())+=entry.value()*
c.modes.row(entry.col());
334 transposeAction.row(entry.col())+=entry.value()*
c.modes.row(entry.row());
336 for(Eigen::Index
i=0;
i<
c.offset.size();++
i)
340 for(Eigen::Index
j=0;
j<modes;++
j)
342 entries.emplace_back(
c.reduced[
i],nodal+
j,action(
i,
j));
343 entries.emplace_back(nodal+
j,
c.reduced[
i],transposeAction(
i,
j));
347 const Eigen::MatrixXd block=
c.modes.transpose()*action;
348 for(Eigen::Index
i=0;
i<modes;++
i)
350 for(Eigen::Index
j=0;
j<modes;++
j)
352 entries.emplace_back(nodal+
i,nodal+
j,block(
i,
j));
356 reducedA.resize(
c.size,
c.size);
357 reducedA.setFromTriplets(entries.begin(),entries.end());
358 reducedA.prune([](Eigen::Index,Eigen::Index,
double value)
362 reducedA.makeCompressed();
363 for(Eigen::Index
k=0;
k<reducedA.nonZeros();++
k)
365 if(!std::isfinite(reducedA.valuePtr()[
k]))
367 throw std::runtime_error(
"Nonfinite reduced FEM matrix.");
372 for(Eigen::Index outer=0;outer<reducedA.outerSize();++outer)
374 for(
typename Matrix::InnerIterator it(reducedA,outer);it;++it)
376 entries.emplace_back(it.row(),it.col(),it.value());
379 for(Eigen::Index row=0;row<reducedPenalty.rows();++row)
381 entries.emplace_back(
c.size+row,
c.size+row,-1.0);
382 for(Eigen::Index col=0;col<
c.size;++col)
384 if(reducedPenalty(row,col)!=0.0)
386 entries.emplace_back(
c.size+row,col,reducedPenalty(row,col));
387 entries.emplace_back(col,
c.size+row,reducedPenalty(row,col));
391 reducedRows=
c.globalRows;
392 for(Eigen::Index
i=0;
i<reducedRows.rows();++
i)
394 for(Eigen::Index
j=0;
j<
c.size;++
j)
396 entries.emplace_back(
c.size+reducedPenalty.rows()+
i,
j,reducedRows(
i,
j));
397 entries.emplace_back(
j,
c.size+reducedPenalty.rows()+
i,reducedRows(
i,
j));
400 const Eigen::Index systemSize=
c.size+reducedPenalty.rows()+reducedRows.rows();
401 solverA.resize(systemSize,systemSize);
402 solverA.setFromTriplets(entries.begin(),entries.end());
403 solverA.makeCompressed();
406 if(solverA.nonZeros()==0)
408 throw std::runtime_error(
"WeakProblem has a zero matrix and unconstrained DOFs.");
410 if(options.useDirectSolver)
412 direct.compute(solverA);
413 if(direct.info()!=Eigen::Success)
415 throw std::runtime_error(
"WeakProblem direct factorization failed; check constraints and null modes.");
420 iterative.compute(solverA);
421 if(iterative.info()!=Eigen::Success)
423 throw std::runtime_error(
"WeakProblem iterative setup failed.");
435 template<
class BWF,
class LWF>
443 throw std::invalid_argument(
"Invalid WeakProblem form or options.");
445 fieldIdentity=bwf->field().ownership();
446 assemble=[bwf]() { return bwf->globalTriplets(); };
447 penalty=[bwf]() { return lowRankBilinearRows(*bwf); };
448 constraints=[
this,bwf]() { return makeConstraints(bwf->field()); };
449 commit=[bwf](
const Vector&
x) { bwf->field().setFullDofVector(x); };
450 iterative.setTolerance(options.
tolerance);
462 if(!lwf || lwf->field().ownership()!=fieldIdentity)
464 throw std::invalid_argument(
"WeakProblem forms must refer to the same trial instance.");
466 force=[lwf]() {
return lwf->globalVector(); };
473 if(!modes.allFinite())
475 throw std::invalid_argument(
"Nonfinite affine FEM modes.");
482 if(!rows.allFinite())
484 throw std::invalid_argument(
"Nonfinite global FEM constraints.");
487 linearValues=Vector::Zero(rows.rows());
492 if(values.size()!=linearRows.rows() || !values.allFinite())
494 throw std::invalid_argument(
"Invalid global FEM constraint values.");
500 return affineSolution;
513 const auto c=constraints();
515 if(
b.size()!=
c.offset.size() || !
b.allFinite())
517 throw std::invalid_argument(
"Invalid FEM right-hand side.");
520 result.reusedFactorization=prepare(
c) &&
c.size>0;
522 for(
const auto& entry : fullTriplets)
524 lifted(entry.row())-=entry.value()*
c.offset(entry.col());
526 reducedB=Vector::Zero(
c.size);
527 for(Eigen::Index
i=0;
i<
b.size();++
i)
531 reducedB(
c.reduced[
i])+=lifted(
i);
536 reducedB.tail(
c.modes.cols())=
c.modes.transpose()*lifted;
538 if(!reducedB.allFinite())
540 throw std::runtime_error(
"Nonfinite reduced FEM force vector.");
542 const Vector penaltyOffset=fullPenalty*
c.offset;
543 Vector solverB(
c.size+fullPenalty.rows()+reducedRows.rows());
544 solverB.head(
c.size)=reducedB;
545 solverB.segment(
c.size,fullPenalty.rows())=-penaltyOffset;
546 if(reducedRows.rows()>0)
548 solverB.tail(reducedRows.rows())=
c.globalValues;
550 reducedB-=reducedPenalty.transpose()*penaltyOffset;
551 if(!solverB.allFinite() || !reducedB.allFinite())
553 throw std::runtime_error(
"Nonfinite low-rank FEM force correction.");
555 Vector solution=Vector::Zero(solverB.size());
558 if(options.useDirectSolver)
560 solution=direct.solve(solverB);
561 if(direct.info()!=Eigen::Success)
563 throw std::runtime_error(
"WeakProblem direct solve failed.");
568 solution=iterative.solve(solverB);
569 result.iterations=iterative.iterations();
570 if(iterative.info()!=Eigen::Success)
572 throw std::runtime_error(
"WeakProblem iterative solve did not converge.");
575 result.relativeResidual=(solverA*solution-solverB).norm()/std::max(1.0,solverB.norm());
576 if(!solution.allFinite() || !std::isfinite(
result.relativeResidual) ||
577 result.relativeResidual>options.tolerance)
579 throw std::runtime_error(
"WeakProblem residual check failed.");
583 for(Eigen::Index
i=0;
i<full.size();++
i)
587 full(
i)+=solution(
c.reduced[
i]);
590 const Vector coefficients=solution.segment(
c.size-
c.modes.cols(),
c.modes.cols());
593 full+=
c.modes*coefficients;
595 if(linearRows.rows()>0 && (linearRows*full-linearValues).norm()>options.tolerance*std::max(1.0,linearValues.norm()))
597 throw std::runtime_error(
"Global FEM constraint residual check failed.");
599 for(
const auto&
bc :
c.dirichlet)
601 if(std::abs(full(
bc.first)-
bc.second)>options.constraintTolerance*std::max(1.0,std::abs(
bc.second)))
603 throw std::runtime_error(
"Nodal Dirichlet residual check failed.");
606 const Vector recovered=
c.affineOffset+
c.affineNullspace*coefficients;
608 affineSolution=recovered;
615 if(reducedPenalty.rows()==0)
619 const Eigen::MatrixXd product=reducedPenalty.transpose()*reducedPenalty;
632 return factorizations;