From 6bba6c7360cc4cc58135ca52b4f58d4dcaf633fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carsten=20Gr=C3=A4ser?= <graeser@math.fau.de> Date: Sun, 16 Feb 2025 11:48:22 +0100 Subject: [PATCH] [constraints] Improve doc of AffineConstraints Document what exactly `AffineConstraints` is supposed to do and how it is intended to be used and rename `constrainMatrixPattern()` to extendMatrixPattern()` since this in fact enlarges the pattern and never reduces it. --- dune/fufem/constraints/affineconstraints.hh | 206 ++++++++++++-------- examples/utilities.hh | 4 +- 2 files changed, 126 insertions(+), 84 deletions(-) diff --git a/dune/fufem/constraints/affineconstraints.hh b/dune/fufem/constraints/affineconstraints.hh index 6dcf45b3..4310053e 100644 --- a/dune/fufem/constraints/affineconstraints.hh +++ b/dune/fufem/constraints/affineconstraints.hh @@ -29,27 +29,68 @@ namespace Dune::Fufem { * * This class handles affine constraints on a subset of DOFs of the form * - * \f[ x_i = \sum_{j} c_{ij} x_j + b_i\f] + * \f[ x_i = \sum_{j} L_{ij} x_j + c_i\f] * - * or in short form + * or in short \f$ x = L x + c\f$ for a square matrix + * \f$ L \in \mathbb{R}^{n \times n} \f$ and a vector \f$c \in \mathbb{R}^n\f$. + * The main purpose is to simplify the handling + * of problems in the constrained affine subspace * - * \f[ x = Cx + b\f] + * \f[ V_{L,c} = \{ x\,|\, x = L x+c\}.\f] * - * for a square matrix \f$C\f$ and a vector \f$b\f$. * We assume that the indices \f$ i \f$ can be classified - * into non-constrained ones, where \f$ c_{ij} = \delta_{ij} \forall j \f$ - * and \f$b_i=0\f$ and constrained ones, where \f$c_{ii}=c_{ij}=0 \f$ - * if \f$x_j\f$ is also constrained. Then any constrained \f$x_i\f$ - * can be interpolated from the non-constrained \f$ x_j, j\neq i\f$ - * and \f$b_i\f$. + * into non-constrained ones, where (using the Kronecker delta) + * \f$ L_{ij} = \delta_{ij} \forall j \f$ + * and \f$c_i=0\f$ and constrained ones, where \f$L_{ii}=L_{ij}=0 \f$ + * if \f$j\f$ is another constrained index. Then any constrained \f$x_i\f$ + * can be interpolated from \f$c_i\f$ and \f$ x_j \f$ + * for the non-constraints indices \f$j\neq i\f$. + * Under these assumptions we have \f$L^2 = L\f$ and \f$L c=0\f$ + * such that the affine map \f$ \Psi:\mathbb{R}^n \to V_{L,c}\f$ + * given by \f$v \mapsto \Psi(v) = L v +c\f$ is surjective projection + * into \f$V_{L,c}\f$. * - * Under these assumptions we furthermore have \f$C^2 = C\f$ and \f$Cb=0\f$ - * such that the affine subspace + * Consider a linear variational problem * - * \f[ V_{C,b} = \{ x\,|\, x = Cx+b\}\f] + * \f[ u \in V_{L,c}: \qquad \langle A u, v \rangle = \langle b, v \rangle \qquad \forall v \in V_{L,0} \f] * - * of all \f$x\f$ satisfying the affine constraints is given by the range - * of the affine map \f$v \mapsto Cv+b\f$. + * in the affine subspace \f$ V_{L,c} = \{ L x +c \,|\, x\in \mathbb{R}^n\}\f$. + * Using of the decomposition \f$u= u_0 + c\f$ with \f$ u_0 = L u_0 = L u \in V_{L,0} \f$ + * this can then be written as + * + * \f[ u_0 \in V_{L,c}: \qquad \langle A u_0, v \rangle = \langle b - A c, v \rangle \qquad \forall v \in V_{L,0}. \f] + * + * which, because \f$L:V_{L,0} \to V_{L,0}\f$ is a linear projection, is equivalent to + * + * \f[ u_0 \in \mathbb{R}^n: \qquad \langle L^T A L u_0, v \rangle = \langle L^T(b - A c), v \rangle \qquad \forall v \in \mathbb{R}^n \f] + * + * The matrix \f$ L^T A L\f$ is obviously singular, since it is + * zero on \f$ker(L) = (V_{L,0})^\perp\f$. Hence this + * system does not have a unique solution. + * Assuming that \f$A\f$ is regular on \f$V_{L,0}\f$, i.e. + * \f$ker (A) \subset ker(L)\f$ we know + * that the solution is at least unique in \f$V_{L,0}\f$. + * To regularize the problem we consider the reduced identity matrix + * \f$ \hat{I} \in \mathbb{R}^{n \times n}\f$ which is zero for all non-constrained indices. + * Since \f$LI = L\f$ it is clear that the system + * + * \f[ \hat{u} \in \mathbb{R}^n: \qquad \langle \hat{A}, v \rangle = \langle \hat{b}, v \rangle \qquad \forall v \in \mathbb{R}^n \f] + * + * with \f$ \hat{A} = L^T A L + \hat{I} \f$ and \f$\hat{b} = L^T(b-A c) + c\f$ + * is equivalent in the sense that \f$u_0 = L \hat{u}\f$. Hence we can solve + * the constrained problem by solving + * + * \f[ \hat{A}\hat{u} = \hat{b} \f] + * + * and then setting \f$u = \Psi(\hat{u}) = L\hat{u} + c = L u_0 + c\f$. + * + * The present class supports this approach as follows: + * The pair \f$(A,b)\f$ can be transformed to \f$(\hat{A}, \hat{b})\f$ + * using the method `constrainLinearSystem(A,b)`. Since this is an inplace operation + * `extendMatrixPattern(...)` can be used before, to ensure that the pattern of the + * matrix is large enough to also store \f$\hat{A}\f$. Once the system is solved, + * \f$\hat{u}\f$ can be transformed to \f$u=\Psi(\hat{u})\f$ using + * `interpolate(u)` which is again an inplace operation. * * \warning This class is still experimental and the interface may change. */ @@ -132,8 +173,8 @@ public: /** * \brief Const access to linear part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the sparse matrix \f$C\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the sparse matrix \f$L\f$. */ const LinearTerm& linearTerm() const { @@ -143,8 +184,8 @@ public: /** * \brief Mutable access to linear part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the sparse matrix \f$C\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the sparse matrix \f$L\f$. */ LinearTerm& linearTerm() { @@ -154,8 +195,8 @@ public: /** * \brief Const access to i-th row of linear part of affine map * - * Assuming that the affine constraints is written as \f$ x = Cx + b\f$ - * this result represents the i-th row of the sparse matrix \f$C\f$. + * Assuming that the affine constraints is written as \f$ x = L x + c\f$ + * the result represents the i-th row of the sparse matrix \f$L\f$. * * This method should only be used if the i-th DOF is constrained. * For non-constrained DOFs this returns an empty range instead @@ -174,8 +215,8 @@ public: /** * \brief Const access to constant part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the vector \f$b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the vector \f$c\f$. */ const ConstantTerm& constantTerm() const { @@ -185,8 +226,8 @@ public: /** * \brief Mutable access to constant part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the vector \f$b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the vector \f$c\f$. */ ConstantTerm& constantTerm() { @@ -196,8 +237,8 @@ public: /** * \brief Const access to i-th row of constant part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the i-th row of the vector \f$b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the i-th row of the vector \f$c\f$. */ template<class MultiIndexType> const auto& constantTerm(const MultiIndexType& i) const @@ -208,8 +249,8 @@ public: /** * \brief Mutable access to i-th row of constant part of affine map * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this result represents the i-th row of the vector \f$b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * the result represents the i-th row of the vector \f$c\f$. */ template<class MultiIndexType> auto& constantTerm(const MultiIndexType& i) @@ -220,8 +261,8 @@ public: /** * \brief Interpolate vector according to affine constraints * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this overwrites the given vector \f$v\f$ by \f$Cv+b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * this overwrites the given vector \f$v\f$ by \f$L v+c\f$. * * This computed the values of constrained DOFs using the * interpolation weights while non-constrained DOFs remain @@ -237,8 +278,8 @@ public: if (isConstrained(i)) { vector_i = constantTerm(i); - for(const auto& [j, c_ij] : linearTerm(i)) - vector_i += c_ij * vectorBackend[j]; + for(const auto& [j, L_ij] : linearTerm(i)) + vector_i += L_ij * vectorBackend[j]; } }); } @@ -246,12 +287,12 @@ public: /** * \brief Extend matrix pattern for a constrained basis * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ * and the given pattern is able to store a sparse matrix \f$A\f$ - * this enlarges the pattern for storing \f$C^TAC\f$. + * this enlarges the pattern for storing \f$L^T A L\f$. */ template<class MatrixPatternBackend, class Basis> - void constrainMatrixPattern(MatrixPatternBackend& patternBackend, const Basis& basis) const + void extendMatrixPattern(MatrixPatternBackend& patternBackend, const Basis& basis) const { auto localView = basis.localView(); for(const auto& element : elements(basis.gridView())) @@ -266,16 +307,16 @@ public: if (isConstrained(i)) { if (isConstrained(j)) - for(const auto& [k, c_ik] : linearTerm(i)) - for(const auto& [l, c_jl] : linearTerm(j)) + for(const auto& [k, L_ik] : linearTerm(i)) + for(const auto& [l, L_jl] : linearTerm(j)) patternBackend.insertEntry(k, l); else - for(const auto& [k, c_ik] : linearTerm(i)) + for(const auto& [k, L_ik] : linearTerm(i)) patternBackend.insertEntry(k, j); } else if (isConstrained(j)) { - for(const auto& [l, c_jl] : linearTerm(j)) + for(const auto& [l, L_jl] : linearTerm(j)) patternBackend.insertEntry(i, l); } } @@ -286,9 +327,9 @@ public: /** * \brief Constrain matrix to a constrained basis * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this overwrites the given matrix \f$A\f$ by \f$C^TAC+\hat{E}\f$ where - * \f$E\f$ is the identity matrix constrained to the kernel of \f$C\f$ + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * this overwrites the given matrix \f$A\f$ by \f$L^T A L+\hat{I}\f$ where + * \f$\hat{I}\f$ is the identity matrix constrained to the kernel of \f$L\f$ * given by the constrained rows. */ template<class Matrix> @@ -306,18 +347,18 @@ public: if (isConstrained(i)) { if (isConstrained(j)) - for(const auto& [k, c_ik] : linearTerm(i)) - for(const auto& [l, c_jl] : linearTerm(j)) - matrixBackend(k, l) += c_ik*c_jl * matrix_ij; + for(const auto& [k, L_ik] : linearTerm(i)) + for(const auto& [l, L_jl] : linearTerm(j)) + matrixBackend(k, l) += L_ik*L_jl * matrix_ij; else - for(const auto& [k, c_ik] : linearTerm(i)) - matrixBackend(k, j) += c_ik * matrix_ij; + for(const auto& [k, L_ik] : linearTerm(i)) + matrixBackend(k, j) += L_ik * matrix_ij; matrix_ij = equals(i, j) ? 1 : 0; } else if (isConstrained(j)) { - for(const auto& [l, c_jl] : linearTerm(j)) - matrixBackend(i, l) += c_jl * matrix_ij; + for(const auto& [l, L_jl] : linearTerm(j)) + matrixBackend(i, l) += L_jl * matrix_ij; matrix_ij = equals(i, j) ? 1 : 0; } }); @@ -326,19 +367,19 @@ public: /** * \brief Constrain vector to a constrained basis * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this overwrites the given vector \f$r\f$ by \f$C^Tr + b\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * this overwrites the given vector \f$b\f$ by \f$L^T b + c\f$. */ template<class Vector> - void constrainVector(Vector& r) const + void constrainVector(Vector& b) const { - auto vectorBackend = Dune::Functions::istlVectorBackend(r); - Dune::Fufem::recursiveForEachVectorEntry(r, [&](auto&& vector_i, const auto& i) + auto vectorBackend = Dune::Functions::istlVectorBackend(b); + Dune::Fufem::recursiveForEachVectorEntry(b, [&](auto&& vector_i, const auto& i) { if (isConstrained(i)) { - for(const auto& [k, c_ik] : linearTerm(i)) - vectorBackend[k] += c_ik * vector_i; + for(const auto& [k, L_ik] : linearTerm(i)) + vectorBackend[k] += L_ik * vector_i; vector_i = constantTerm(i); } }); @@ -347,16 +388,17 @@ public: /** * \brief Constrain linear system affine subspace satisfying the constraints * - * Assuming that the affine constraints are written as \f$ x = Cx + b\f$ - * this overwrites the given vector \f$r\f$ by \f$C^T(r-Ab) + b\f$ - * and the matrix \f$A\f$ by \f$C^TAC\f$. + * Assuming that the affine constraints are written as \f$ x = L x + c\f$ + * this overwrites the given vector \f$b\f$ by \f$L^T(b-A c) + c\f$ + * and the matrix \f$A\f$ by \f$L^T A L + \hat{I}\f$ where + * \f$\hat{I}\f$ is the identity matrix constrained to the kernel of \f$L\f$. */ template<class Matrix, class Vector> - void constrainLinearSystem(Matrix& A, Vector& r) const + void constrainLinearSystem(Matrix& A, Vector& b) const { - A.mmv(constantTerm_, r); + A.mmv(constantTerm_, b); constrainMatrix(A); - constrainVector(r); + constrainVector(b); } /** @@ -364,11 +406,11 @@ public: */ void report() const { - for(const auto& [i, c_i] : interpolation_) + for(const auto& [i, L_i] : interpolation_) { std::cout << "x_" << i << " = "; - for(const auto& [j, c_ij] : c_i) - std::cout << c_ij << " * x_" << j << " + " ; + for(const auto& [j, L_ij] : L_i) + std::cout << L_ij << " * x_" << j << " + " ; std::cout << constantTerm(i) << std::endl; } std::cout << "interpolated dofs : " << interpolation_.size() << std::endl; @@ -382,9 +424,9 @@ public: bool check() const { bool pass = true; - for(const auto& [i, c_i] : interpolation_) + for(const auto& [i, L_i] : interpolation_) { - for(const auto& [j, c_ij] : c_i) + for(const auto& [j, L_ij] : L_i) { if (isConstrained(j)) { @@ -412,39 +454,39 @@ public: auto dependenciesResolved = isConstrained_; auto dependenciesResolvedBackend = Dune::Functions::istlVectorBackend(dependenciesResolved); dependenciesResolvedBackend = false; - for(auto& [i, c_i] : interpolation_) + for(auto& [i, L_i] : interpolation_) if (not(dependenciesResolvedBackend[i])) - resolveDOF(dependenciesResolved, i, c_i); + resolveDOF(dependenciesResolved, i, L_i); } private: - void resolveDOF(BitVector& dependenciesResolved, const MultiIndex& i, LinearTermRow& c_i) + void resolveDOF(BitVector& dependenciesResolved, const MultiIndex& i, LinearTermRow& L_i) { auto dependenciesResolvedBackend = Dune::Functions::istlVectorBackend(dependenciesResolved); - auto b = Dune::Functions::istlVectorBackend(constantTerm_); - auto it = c_i.begin(); - while (it != c_i.end()) + auto c = Dune::Functions::istlVectorBackend(constantTerm_); + auto it = L_i.begin(); + while (it != L_i.end()) { - auto& [j, c_ij] = *it; + auto& [j, L_ij] = *it; // If interpolation weight is itself constraint, // first resolve it recursively (if needed) // and then insert all indirect dependencies directly if (isConstrained(j)) { - b[i] += c_ij * b[j]; - auto c_j_it = interpolation_.find(j); - if (c_j_it != interpolation_.end()) + c[i] += L_ij * c[j]; + auto L_j_it = interpolation_.find(j); + if (L_j_it != interpolation_.end()) { - auto& c_j = c_j_it->second; + auto& L_j = L_j_it->second; if (not(dependenciesResolvedBackend[j])) - resolveDOF(dependenciesResolved, j, c_j); - for (const auto& [k, c_jk] : c_j) - c_i[k] += c_ij * c_jk; + resolveDOF(dependenciesResolved, j, L_j); + for (const auto& [k, L_jk] : L_j) + L_i[k] += L_ij * L_jk; } else dependenciesResolvedBackend[j] = true; - it = c_i.erase(it); + it = L_i.erase(it); } else ++it; diff --git a/examples/utilities.hh b/examples/utilities.hh index e4340143..6f94b854 100644 --- a/examples/utilities.hh +++ b/examples/utilities.hh @@ -75,7 +75,7 @@ public: operatorAssembler.assembleBulkPattern(patternBackend, Dune::resolveRef(args)...); - constraints.constrainMatrixPattern(patternBackend, testBasis_); + constraints.extendMatrixPattern(patternBackend, testBasis_); patternBackend.setupMatrix(); @@ -126,7 +126,7 @@ public: operatorAssembler.assembleBulkPattern(patternBackend, Dune::resolveRef(args)...); - constraints.constrainMatrixPattern(patternBackend, testBasis_); + constraints.extendMatrixPattern(patternBackend, testBasis_); patternBackend.setupMatrix(); -- GitLab