From 4bbc4feb727dcb59fe2ed6d9dccb392ad8ff3fc4 Mon Sep 17 00:00:00 2001
From: "Dennis.Glaeser" <dennis.glaeser@iws.uni-stuttgart.de>
Date: Wed, 26 Jun 2019 14:20:22 +0200
Subject: [PATCH] [projector] reduce system size for solve where possible

---
 dumux/discretization/projection/projector.hh | 215 +++++++++++++++++--
 1 file changed, 198 insertions(+), 17 deletions(-)

diff --git a/dumux/discretization/projection/projector.hh b/dumux/discretization/projection/projector.hh
index 9b74c78099..0222a16b5d 100644
--- a/dumux/discretization/projection/projector.hh
+++ b/dumux/discretization/projection/projector.hh
@@ -87,7 +87,40 @@ public:
     Projector(Matrix&& massMatrix, Matrix&& projectionMatrix)
     : massMat_(std::move(massMatrix))
     , projMat_(std::move(projectionMatrix))
-    {}
+    , numDofsTarget_(massMat_.N())
+    {
+        if (massMat_.N() != projMat_.N())
+            DUNE_THROW(Dune::InvalidStateException, "Matrix row size mismatch: " << massMat_.N() << " vs " << projMat_.N());
+    }
+
+    /*!
+     * \brief Constructor for projection into a target space that occupies
+     *        a larger geometric region than the domain space. In this case,
+     *        the mass matrix can be chosen such that is is solved for only
+     *        those dofs which will be populated with values from the projection.
+     *        This requires an additional index map that maps the entries of the
+     *        projected solution into the solution vector for the target space.
+     *        Furthermore, the number of degrees of freedom must be specified to
+     *        set up the coefficient vector with correct size for the target space.
+     */
+    Projector(Matrix&& massMatrix,
+              Matrix&& projectionMatrix,
+              std::vector<std::size_t>&& indexMap,
+              std::size_t numDofsTarget)
+    : massMat_(std::move(massMatrix))
+    , projMat_(std::move(projectionMatrix))
+    , indexMapTarget_(std::move(indexMap))
+    , numDofsTarget_(numDofsTarget)
+    {
+        if (indexMapTarget_.size() != massMat_.N())
+            DUNE_THROW(Dune::InvalidStateException, "Target index map size mismatch: " << indexMapTarget_.size() << " vs " << massMat_.N());
+
+        if (massMat_.N() != projMat_.N())
+            DUNE_THROW(Dune::InvalidStateException, "Matrix row size mismatch: " << massMat_.N() << " vs " << projMat_.N());
+
+        if (*std::max_element(indexMapTarget_.begin(), indexMapTarget_.end()) > numDofsTarget_)
+            DUNE_THROW(Dune::InvalidStateException, "Index map exceeds provided number of dofs in target domain!");
+    }
 
     /*!
      * \brief Project a solution u into up
@@ -99,7 +132,7 @@ public:
     {
         // be picky about size of u
         if ( u.size() != projMat_.M())
-            DUNE_THROW(Dune::InvalidStateException, "Vector size mismatch" );
+            DUNE_THROW(Dune::InvalidStateException, "Vector size mismatch");
 
         Dune::BlockVector<BlockType> up(massMat_.N());
 
@@ -111,6 +144,18 @@ public:
         solver.setMaxIter(params.maxIterations);
         solver.solve(massMat_, up, rhs);
 
+        // if target space occupies a larger region, fill missing entries with zero
+        if (!indexMapTarget_.empty())
+        {
+            Dune::BlockVector<BlockType> result(numDofsTarget_);
+
+            result = 0.0;
+            for (std::size_t i = 0; i < indexMapTarget_.size(); ++i)
+                result[indexMapTarget_[i]] = up[i];
+
+            return result;
+        }
+
         return up;
     }
 
@@ -122,7 +167,7 @@ public:
     template< class BlockType, std::enable_if_t<!std::is_convertible<BlockType, ScalarType>::value, int> = 0 >
     Dune::BlockVector<BlockType> project(const Dune::BlockVector<BlockType>& u, const Params& params = Params{}) const
     {
-        Dune::BlockVector<BlockType> result(massMat_.N());
+        Dune::BlockVector<BlockType> result(numDofsTarget_);
 
         for (int pvIdx = 0; pvIdx < BlockType::size(); ++pvIdx)
         {
@@ -146,6 +191,9 @@ public:
 private:
     Matrix massMat_;
     Matrix projMat_;
+
+    std::vector<std::size_t> indexMapTarget_;
+    std::size_t numDofsTarget_;
 };
 
 /*!
@@ -167,6 +215,75 @@ public:
 // Projector construction details
 namespace Impl {
 
+/*!
+ * \brief Reduces a mass matrix and projection matrix such that they are composed
+ *        of only those dofs that actually take part in the projection. Simultaneously,
+ *        a container with the index map into the complete target space is filled so that
+ *        the entries after projection can be assigned to the corresponding dof in the
+ *        overall target space.
+ */
+template<class Matrix>
+void setupReducedMatrices(const Matrix& massMatrix, const Matrix& projMatrix, const std::vector<bool>& dofIsVoid,
+                          Matrix& reducedM, Matrix& reducedP, std::vector<std::size_t>& expansionMap)
+{
+    const std::size_t numNonVoidDofs = std::count_if(dofIsVoid.begin(), dofIsVoid.end(), [] (bool v) { return !v; });
+
+    // reduce matrices to only dofs that take part and create index map
+    std::vector<std::size_t> reductionMap(massMatrix.N());
+    expansionMap.resize(numNonVoidDofs);
+
+    std::size_t idxInReducedSpace = 0;
+    for (std::size_t dofIdx = 0; dofIdx < dofIsVoid.size(); ++dofIdx)
+        if (!dofIsVoid[dofIdx])
+        {
+            reductionMap[dofIdx] = idxInReducedSpace;
+            expansionMap[idxInReducedSpace] = dofIdx;
+            idxInReducedSpace++;
+        }
+
+    // create reduced M/P matrix
+    Dune::MatrixIndexSet patternMReduced, patternPReduced;
+    patternMReduced.resize(numNonVoidDofs, numNonVoidDofs);
+    patternPReduced.resize(numNonVoidDofs, projMatrix.M());
+    for (auto rowIt = massMatrix.begin(); rowIt != massMatrix.end(); ++rowIt)
+        if (!dofIsVoid[rowIt.index()])
+        {
+            const auto reducedRowIdx = reductionMap[rowIt.index()];
+            for (auto colIt = (*rowIt).begin(); colIt != (*rowIt).end(); ++colIt)
+                if (!dofIsVoid[colIt.index()])
+                    patternMReduced.add(reducedRowIdx, reductionMap[colIt.index()]);
+        }
+
+    for (auto rowIt = projMatrix.begin(); rowIt != projMatrix.end(); ++rowIt)
+        if (!dofIsVoid[rowIt.index()])
+        {
+            const auto reducedRowIdx = reductionMap[rowIt.index()];
+            for (auto colIt = (*rowIt).begin(); colIt != (*rowIt).end(); ++colIt)
+                patternPReduced.add(reducedRowIdx, colIt.index());
+        }
+
+    patternMReduced.exportIdx(reducedM);
+    patternPReduced.exportIdx(reducedP);
+
+    // fill matrix entries
+    for (auto rowIt = massMatrix.begin(); rowIt != massMatrix.end(); ++rowIt)
+        if (!dofIsVoid[rowIt.index()])
+        {
+            const auto reducedRowIdx = reductionMap[rowIt.index()];
+            for (auto colIt = (*rowIt).begin(); colIt != (*rowIt).end(); ++colIt)
+                if (!dofIsVoid[colIt.index()])
+                    reducedM[reducedRowIdx][reductionMap[colIt.index()]] = *colIt;
+        }
+
+    for (auto rowIt = projMatrix.begin(); rowIt != projMatrix.end(); ++rowIt)
+        if (!dofIsVoid[rowIt.index()])
+        {
+            const auto reducedRowIdx = reductionMap[rowIt.index()];
+            for (auto colIt = (*rowIt).begin(); colIt != (*rowIt).end(); ++colIt)
+                reducedP[reducedRowIdx][colIt.index()] = *colIt;
+        }
+}
+
 /*!
  * \brief Creates a projector class between two function space bases
  * \tparam doBidirectional If false, the backward projection matrix is not assembled
@@ -325,26 +442,90 @@ makeProjectorPair(const FEBasisDomain& feBasisDomain,
         }
     }
 
-    // On those dofs that to not take part in any intersection we will have zeroes
-    // in the mass matrices. The right hand side will be zero because the projection
-    // matrix has no entries for those dofs as there was no intersection. Thus, we set
-    // 1.0 on the diagonal for those dofs, such that after projection, the projected
-    // solution is 0 on them.
-    for (std::size_t dofIdx = 0; dofIdx < forwardM.N(); ++dofIdx)
-        if (forwardM[dofIdx][dofIdx] == 0.0)
-            forwardM[dofIdx][dofIdx] = 1.0;
+    // determine the dofs that do not take part in intersections
+    std::vector<bool> isVoidTarget(forwardM.N(), false);
+    for (std::size_t dofIdxTarget = 0; dofIdxTarget < forwardM.N(); ++dofIdxTarget)
+        if (forwardM[dofIdxTarget][dofIdxTarget] == 0.0)
+            isVoidTarget[dofIdxTarget] = true;
 
+    std::vector<bool> isVoidDomain;
     if (doBidirectional)
     {
-        for (std::size_t dofIdx = 0; dofIdx < backwardM.N(); ++dofIdx)
-            if (backwardM[dofIdx][dofIdx] == 0.0)
-                backwardM[dofIdx][dofIdx] = 1.0;
+        isVoidDomain.resize(backwardM.N(), false);
+        for (std::size_t dofIdxDomain = 0; dofIdxDomain < backwardM.N(); ++dofIdxDomain)
+            if (backwardM[dofIdxDomain][dofIdxDomain] == 0.0)
+                isVoidDomain[dofIdxDomain] = true;
     }
 
-    ForwardProjector fw(std::move(forwardM), std::move(forwardP));
-    BackwardProjector bw(std::move(backwardM), std::move(backwardP));
+    const bool hasVoidTarget = std::any_of(isVoidTarget.begin(), isVoidTarget.end(), [] (bool v) { return v; });
+    const bool hasVoidDomain = std::any_of(isVoidDomain.begin(), isVoidDomain.end(), [] (bool v) { return v; });
+    if (!hasVoidDomain && !hasVoidTarget)
+    {
+        return std::make_pair(ForwardProjector(std::move(forwardM), std::move(forwardP)),
+                              BackwardProjector(std::move(backwardM), std::move(backwardP)));
+    }
+    else if (!hasVoidDomain && hasVoidTarget)
+    {
+        std::vector<std::size_t> expansionMapTarget;
+        ForwardProjectionMatrix forwardMReduced, forwardPReduced;
+        setupReducedMatrices(forwardM, forwardP, isVoidTarget,
+                             forwardMReduced, forwardPReduced, expansionMapTarget);
+
+        return std::make_pair( ForwardProjector(std::move(forwardMReduced),
+                                                std::move(forwardPReduced),
+                                                std::move(expansionMapTarget),
+                                                forwardM.N()),
+                               BackwardProjector(std::move(backwardM), std::move(backwardP)) );
+    }
+    else if (hasVoidDomain && !hasVoidTarget)
+    {
+        if (doBidirectional)
+        {
+            std::vector<std::size_t> expansionMapDomain;
+            BackwardProjectionMatrix backwardMReduced, backwardPReduced;
+            setupReducedMatrices(backwardM, backwardP, isVoidDomain,
+                                 backwardMReduced, backwardPReduced, expansionMapDomain);
+
+            return std::make_pair( ForwardProjector(std::move(forwardM), std::move(forwardP)),
+                                   BackwardProjector(std::move(backwardMReduced),
+                                                     std::move(backwardPReduced),
+                                                     std::move(expansionMapDomain),
+                                                     backwardM.N()) );
+        }
+        else
+            return std::make_pair( ForwardProjector(std::move(forwardM), std::move(forwardP)),
+                                   BackwardProjector(std::move(backwardM), std::move(backwardP)) );
+    }
+    else
+    {
+        std::vector<std::size_t> expansionMapTarget;
+        ForwardProjectionMatrix forwardMReduced, forwardPReduced;
+        setupReducedMatrices(forwardM, forwardP, isVoidTarget,
+                             forwardMReduced, forwardPReduced, expansionMapTarget);
 
-    return std::make_pair(std::move(fw), std::move(bw));
+        if (doBidirectional)
+        {
+            std::vector<std::size_t> expansionMapDomain;
+            BackwardProjectionMatrix backwardMReduced, backwardPReduced;
+            setupReducedMatrices(backwardM, backwardP, isVoidDomain,
+                                 backwardMReduced, backwardPReduced, expansionMapDomain);
+
+            return std::make_pair( ForwardProjector(std::move(forwardMReduced),
+                                                    std::move(forwardPReduced),
+                                                    std::move(expansionMapTarget),
+                                                    forwardM.N()),
+                                   BackwardProjector(std::move(backwardMReduced),
+                                                     std::move(backwardPReduced),
+                                                     std::move(expansionMapDomain),
+                                                     backwardM.N()) );
+        }
+        else
+            return std::make_pair( ForwardProjector(std::move(forwardMReduced),
+                                                    std::move(forwardPReduced),
+                                                    std::move(expansionMapTarget),
+                                                    forwardM.N()),
+                                   BackwardProjector(std::move(backwardM), std::move(backwardP)) );
+    }
 }
 
 } // end namespace Implementation
-- 
GitLab