From a4f622140a92865f6a4e847d2b039eca57ae96e5 Mon Sep 17 00:00:00 2001
From: Michael Innerberger <michael.innerberger@asc.tuwien.ac.at>
Date: Wed, 19 Jul 2023 09:33:02 -0400
Subject: [PATCH] Switch addtive Schwarz implementation and delete old code

This somehow also fixes the problems with the higher order version of
the multilevel additive Schwarz preconditioner (this is tested now!)
---
 lib/solvers/chooseIterativeSolver.m           |   8 +-
 .../AdditiveSchwarzHighOrderCascade.m         | 141 -----------------
 .../AdditiveSchwarzLowOrderCascade.m          | 146 ------------------
 .../OptimalMLAdditiveSchwarz.m                |  76 +++++++++
 .../preconditioners/P1AdditiveSchwarz.m       | 120 --------------
 tests/TestIterativeSolver.m                   |   1 +
 6 files changed, 81 insertions(+), 411 deletions(-)
 delete mode 100644 lib/solvers/preconditioners/AdditiveSchwarzHighOrderCascade.m
 delete mode 100644 lib/solvers/preconditioners/AdditiveSchwarzLowOrderCascade.m
 create mode 100644 lib/solvers/preconditioners/OptimalMLAdditiveSchwarz.m
 delete mode 100644 lib/solvers/preconditioners/P1AdditiveSchwarz.m

diff --git a/lib/solvers/chooseIterativeSolver.m b/lib/solvers/chooseIterativeSolver.m
index 5058d49..55dbc42 100644
--- a/lib/solvers/chooseIterativeSolver.m
+++ b/lib/solvers/chooseIterativeSolver.m
@@ -33,15 +33,15 @@ switch class
                 end
             case {"", "additiveSchwarzLowOrder"}
                 if order == 1
-                    preconditioner = P1AdditiveSchwarz(fes, blf, P);
+                    preconditioner = OptimalMLAdditiveSchwarz(P1JacobiSmoother(fes, blf, P));
                 else
-                    preconditioner = AdditiveSchwarzLowOrderCascade(fes, blf);
+                    preconditioner = OptimalMLAdditiveSchwarz(JacobiLowOrderCascadeSmoother(fes, blf));
                 end
             case "additiveSchwarzHighOrder"
                 if order == 1
-                    preconditioner = P1AdditiveSchwarz(fes, blf, P);
+                    preconditioner = OptimalMLAdditiveSchwarz(P1JacobiSmoother(fes, blf, P));
                 else
-                    preconditioner = AdditiveSchwarzHighOrderCascade(fes, blf, P);
+                    preconditioner = OptimalMLAdditiveSchwarz(JacobiHighOrderCascadeSmoother(fes, blf, P));
                 end
             otherwise
                 error('No PCG variant %s!', variant)
diff --git a/lib/solvers/preconditioners/AdditiveSchwarzHighOrderCascade.m b/lib/solvers/preconditioners/AdditiveSchwarzHighOrderCascade.m
deleted file mode 100644
index c0c4e1c..0000000
--- a/lib/solvers/preconditioners/AdditiveSchwarzHighOrderCascade.m
+++ /dev/null
@@ -1,141 +0,0 @@
-% AdditiveSchwarzHighOrderCascade (subclass of Preconditioner) optimal
-%   multilevel additive Schwarz preconditioner for arbitrary order finite
-%   elements: smooth with patchwise higher order matrix on changed patches on
-%   all levels (local higher order smoothing) and globally with patchwise higher
-%   order matrix on finest level (global higher order smoothing).
-%
-% See also: Preconditioner, PcgSolver
-
-% TODO: this does not work as intended! Test and fix it!
-classdef AdditiveSchwarzHighOrderCascade < Preconditioner
-    %% properties
-    properties (GetAccess=public,SetAccess=protected)
-        nLevels
-    end
-    
-    properties (Access=protected)
-        blf
-        hoFes
-        loFes
-        p1Acoarse
-        hoAcoarse
-        P
-        inclusionMatrix
-        intergridMatrix
-        freeVertices
-        freeDofs
-        freeDofsOld
-        changedPatches
-        patchwiseA
-    end
-
-    properties (Access=private)
-        listenerHandle
-    end
-    
-    %% methods
-    methods (Access=public)
-        function obj = AdditiveSchwarzHighOrderCascade(fes, blf, P)
-            arguments
-                fes FeSpace
-                blf BilinearForm
-                P Prolongation
-            end
-            
-            assert(fes.finiteElement.order > 1, ...
-                'Higher order additive Schwarz preconditioner only works for higher order finite elements.')
-            assert(isempty(blf.b), ...
-                'Additive Schwarz preconditioner only tested for symmetric problems.')
-
-            obj.nLevels = 0;
-
-            mesh = fes.mesh;
-            obj.loFes = FeSpace(mesh, LowestOrderH1Fe, 'dirichlet', fes.bnd.dirichlet);
-            obj.hoFes = fes;
-            obj.P = P;
-            obj.blf = blf;
-
-            obj.listenerHandle = mesh.listener('IsAboutToRefine', @obj.getChangedPatches);
-        end
-        
-        function setup(obj, A)
-            obj.nLevels = obj.nLevels + 1;
-            
-            L = obj.nLevels;
-            obj.freeVertices = getFreeDofs(obj.loFes);
-
-            obj.freeDofsOld = obj.freeDofs;
-            obj.freeDofs = getFreeDofs(obj.hoFes);
-
-            p1Matrix = assemble(obj.blf, obj.loFes);
-            p1Matrix = p1Matrix(obj.freeVertices, obj.freeVertices);
-            
-            if L == 1
-                obj.p1Acoarse = p1Matrix;
-                obj.hoAcoarse = A;
-                obj.inclusionMatrix = SpaceProlongation(obj.loFes, obj.hoFes) ...
-                    .matrix(getFreeDofs(obj.loFes), getFreeDofs(obj.hoFes));
-            else
-                obj.intergridMatrix{L} = obj.P.matrix(obj.freeDofs, obj.freeDofsOld);
-                obj.changedPatches = obj.changedPatches(obj.freeVertices);
-                if L == 2
-                    % since order changes to 2nd mesh, we have to do global smoothing
-                    obj.patchwiseA{L} = assemblePatchwise(obj.blf, obj.hoFes, ':');
-                else
-                    obj.patchwiseA{L} = assemblePatchwise(obj.blf, obj.hoFes, obj.changedPatches);
-                end
-            end
-        end
-        
-        % preconditioner: patchwise higher order solve on changed patches
-        function Cx = apply(obj, x)
-            assert(~isempty(obj.nLevels), 'Data for multilevel iteration not given!')
-
-            L = obj.nLevels;
-            rho = cell(L, 1);
-
-            if L == 1
-                Cx = obj.hoAcoarse \ x;
-                return
-            end
-
-            % descending cascade
-            for k = L:-1:2
-                rho{k} = localSmoothing(obj, k, x);
-                x = obj.intergridMatrix{k}' * x;
-            end
-
-            % exact solve on coarsest level
-            x = obj.inclusionMatrix * x;
-            Cx = obj.p1Acoarse \ x;
-            Cx = obj.inclusionMatrix' * Cx;
-            
-            % ascending cascade
-            for k = 2:L
-                Cx = obj.intergridMatrix{k} * Cx;
-                Cx = Cx + rho{k};
-            end
-        end
-    end
-
-    methods (Access=protected)
-        % callback function to be executed before mesh refinement:
-        % -> patches change for all vertices adjacent to refined edges and
-        %    all new vertices
-        function getChangedPatches(obj, mesh, bisecData)
-            % Selects vertices of bisected edges and all newly created
-            % vertices
-            nCOld = mesh.nCoordinates;
-            nCNew = mesh.nCoordinates + nnz(bisecData.bisectedEdges) + ...
-                bisecData.nRefinedElements' * bisecData.nInnerNodes;
-            bisectedEdgeNodes = unique(mesh.edges(:,bisecData.bisectedEdges));
-            obj.changedPatches = false(nCNew, 1);
-            idx = [bisectedEdgeNodes; ((nCOld+1):nCNew)'];
-            obj.changedPatches(idx) = true;
-        end
-        
-        function rho = localSmoothing(obj, k, res)
-            rho = obj.patchwiseA{k} \ res;
-        end
-    end
-end
\ No newline at end of file
diff --git a/lib/solvers/preconditioners/AdditiveSchwarzLowOrderCascade.m b/lib/solvers/preconditioners/AdditiveSchwarzLowOrderCascade.m
deleted file mode 100644
index f65917c..0000000
--- a/lib/solvers/preconditioners/AdditiveSchwarzLowOrderCascade.m
+++ /dev/null
@@ -1,146 +0,0 @@
-% AdditiveSchwarzLowOrderCascade (subclass of Preconditioner) optimal multilevel
-%   additive Schwarz preconditioner for arbitrary order finite elements: smooth
-%   with diagonal of P1 matrix on changed patches on every level (local P1
-%   smoothing) and with patchwise higher order matrix on finest level (global
-%   higher order smoothing).
-%
-% See also: Preconditioner, PcgSolver
-
-classdef AdditiveSchwarzLowOrderCascade < Preconditioner
-    %% properties
-    properties (GetAccess=public,SetAccess=protected)
-    end
-    
-    properties (Access=protected)
-        blf
-        hoFes
-        loFes
-        P
-        p1Acoarse
-        hoAcoarse
-        p1Smoother
-        nLevels
-        inclusionMatrix
-        intergridMatrix
-        freeVertices
-        freeVerticesOld
-        changedPatches
-        patchwiseA
-        patchwiseP1Matrix
-    end
-
-    properties (Access=private)
-        listenerHandle
-    end
-    
-    %% methods
-    methods (Access=public)
-        function obj = AdditiveSchwarzLowOrderCascade(fes, blf)
-            arguments
-                fes FeSpace
-                blf BilinearForm
-            end
-            
-            assert(fes.finiteElement.order > 1, ...
-                'Higher order additive Schwarz preconditioner only works for higher order finite elements.')
-            assert(isempty(blf.b), ...
-                'Additive Schwarz preconditioner only tested for symmetric problems.')
-
-            obj.nLevels = 0;
-
-            mesh = fes.mesh;
-            obj.loFes = FeSpace(mesh, LowestOrderH1Fe, 'dirichlet', fes.bnd.dirichlet);
-            obj.hoFes = fes;
-            obj.P = Prolongation.chooseFor(obj.loFes);
-            obj.blf = blf;
-
-            obj.listenerHandle = mesh.listener('IsAboutToRefine', @obj.getChangedPatches);
-        end
-        
-        function setup(obj, A)
-            obj.nLevels = obj.nLevels + 1;
-            
-            L = obj.nLevels;
-            obj.freeVerticesOld = obj.freeVertices;
-            obj.freeVertices = getFreeDofs(obj.loFes);
-
-            p1Matrix = assemble(obj.blf, obj.loFes);
-            p1Matrix = p1Matrix(obj.freeVertices, obj.freeVertices);
-            
-            if L == 1
-                obj.p1Acoarse = p1Matrix;
-                obj.hoAcoarse = A;
-            else
-                obj.p1Smoother{L} = full(diag(p1Matrix)).^(-1);
-                obj.intergridMatrix{L} = obj.P.matrix(obj.freeVertices, obj.freeVerticesOld);
-                obj.changedPatches{L} = obj.changedPatches{L}(obj.freeVertices);
-                obj.patchwiseA = assemblePatchwise(obj.blf, obj.hoFes);
-                obj.inclusionMatrix = SpaceProlongation(obj.loFes, obj.hoFes) ...
-                    .matrix(getFreeDofs(obj.loFes), getFreeDofs(obj.hoFes));
-            end
-        end
-        
-        % preconditioner: inverse of diagonal on each level
-        function Cx = apply(obj, x)
-            assert(~isempty(obj.nLevels), 'Data for multilevel iteration not given!')
-
-            L = obj.nLevels;
-            rho = cell(L, 1);
-
-            if L == 1
-                Cx = obj.hoAcoarse \ x;
-                return
-            end
-
-            % descending cascade
-            rho{L} = hoGlobalSmoothing(obj, x);
-            x = obj.inclusionMatrix * x;
-
-            for k = L:-1:3
-                x = obj.intergridMatrix{k}' * x;
-                rho{k-1} = p1LocalSmoothing(obj, k-1, x);
-            end
-
-            % exact solve on coarsest level
-            x = obj.intergridMatrix{2}' * x;
-            Cx = obj.p1Acoarse \ x;
-            Cx = obj.intergridMatrix{2}*Cx;
-
-            % ascending cascade
-            for k = 3:L
-                Cx = Cx + rho{k-1};
-                Cx = obj.intergridMatrix{k} * Cx;
-            end
-
-            Cx = obj.inclusionMatrix' * Cx;
-            Cx = Cx + rho{L};
-        end
-    end
-
-    methods (Access=protected)
-        % callback function to be executed before mesh refinement:
-        % -> patches change for all vertices adjacent to refined edges and
-        %    all new vertices
-        function getChangedPatches(obj, mesh, bisecData)
-            % Selects vertices of bisected edges and all newly created
-            % vertices
-            nCOld = mesh.nCoordinates;
-            nCNew = mesh.nCoordinates + nnz(bisecData.bisectedEdges) + ...
-                bisecData.nRefinedElements' * bisecData.nInnerNodes;
-            bisectedEdgeNodes = unique(mesh.edges(:,bisecData.bisectedEdges));
-            obj.changedPatches{obj.nLevels+1} = false(nCNew, 1);
-            idx = [bisectedEdgeNodes; ((nCOld+1):nCNew)'];
-            obj.changedPatches{obj.nLevels+1}(idx) = true;
-        end
-
-        function rho = p1LocalSmoothing(obj, k, res)
-            idx = obj.changedPatches{k};
-            rho = zeros(size(res));
-            rho(idx,:) = obj.p1Smoother{k}(idx).*res(idx,:);
-        end
-        
-        function rho = hoGlobalSmoothing(obj, res)
-            rho = obj.patchwiseA \ res;
-        end
-    end
-end
\ No newline at end of file
diff --git a/lib/solvers/preconditioners/OptimalMLAdditiveSchwarz.m b/lib/solvers/preconditioners/OptimalMLAdditiveSchwarz.m
new file mode 100644
index 0000000..c7499bd
--- /dev/null
+++ b/lib/solvers/preconditioners/OptimalMLAdditiveSchwarz.m
@@ -0,0 +1,76 @@
+% OptimalMLAdditiveSchwarz (subclass of Preconditioner) optimal multilevel
+%   additive Schwarz preconditioner with given smoother.
+%
+% See also: Preconditioner, PcgSolver, MultilevelSmoother
+
+classdef OptimalMLAdditiveSchwarz < Preconditioner
+    %% properties
+    properties (Access=protected)
+        smoother
+        Acoarse
+        projectedAcoarse
+    end
+
+    properties (Dependent)
+        nLevels
+    end
+
+    properties (Access=private)
+        listenerHandle
+    end
+    
+    %% methods
+    methods (Access=public)
+        function obj = OptimalMLAdditiveSchwarz(smoother)
+            arguments
+                smoother MultilevelSmoother
+            end
+            
+            obj = obj@Preconditioner();
+            obj.smoother = smoother;
+        end
+        
+        function setup(obj, A)
+            if obj.nLevels == 0
+                obj.Acoarse = A;
+                obj.projectedAcoarse = obj.smoother.setup(A);
+            else
+                obj.smoother.setup(A);
+            end
+        end
+           
+        function Cx = apply(obj, x)
+            assert(~isempty(obj.nLevels), 'Data for multilevel iteration not given!')
+
+            L = obj.nLevels;
+            rho = cell(L, 1);
+
+            if L == 1
+                Cx = obj.Acoarse \ x;
+                return
+            end
+
+            % descending cascade
+            residual = x;
+            for k = L:-1:2
+                rho{k} = obj.smoother.smooth(residual, k);
+                residual = obj.smoother.restrict(residual, k);
+            end
+            
+            % exact solve on coarsest level
+            Cx = obj.projectedAcoarse \ residual;
+            
+            % ascending cascade
+            for k = 2:L
+                Cx = obj.smoother.prolongate(Cx, k);
+                Cx = Cx + rho{k};
+            end
+        end
+    end
+
+    methods
+        function n = get.nLevels(obj)
+            n = obj.smoother.nLevels;
+        end
+    end
+end
\ No newline at end of file
diff --git a/lib/solvers/preconditioners/P1AdditiveSchwarz.m b/lib/solvers/preconditioners/P1AdditiveSchwarz.m
deleted file mode 100644
index a001f79..0000000
--- a/lib/solvers/preconditioners/P1AdditiveSchwarz.m
+++ /dev/null
@@ -1,120 +0,0 @@
-% P1AdditiveSchwarz (subclass of Preconditioner) optimal multilevel
-%   additive Schwarz preconditioner for lowest order finite elements:
-%   smooth with inverse diagonal on changed patches on every level.
-%
-% See also: Preconditioner, PcgSolver
-
-classdef P1AdditiveSchwarz < Preconditioner
-    %% properties
-    properties (Access=protected)
-        fes
-        blf
-        P
-        nLevels
-        Acoarse
-        smoother
-        intergridMatrix
-        freeDofs
-        freeDofsOld
-        changedPatches
-        highestOrderIsOne
-    end
-
-    properties (Access=private)
-        listenerHandle
-    end
-    
-    %% methods
-    methods (Access=public)
-        function obj = P1AdditiveSchwarz(fes, blf, P)
-            arguments
-                fes FeSpace
-                blf BilinearForm
-                P Prolongation
-            end
-            
-            assert(fes.finiteElement.order == 1, ...
-                'P1 Additive Schwarz preconditioner only works for lowest order finite elements.')
-            assert(isempty(blf.b), ...
-                'Additive Schwarz preconditioner only tested for symmetric problems.')
-            
-            obj.nLevels = 0;
-            
-            mesh = fes.mesh;
-            obj.fes = fes;
-            obj.blf = blf;
-            obj.P = P;
-            
-            obj.listenerHandle = mesh.listener('IsAboutToRefine', @obj.getChangedPatches);
-        end
-        
-        function setup(obj, A)
-            obj.nLevels = obj.nLevels + 1;
-            
-            L = obj.nLevels;
-            obj.freeDofsOld = obj.freeDofs;
-            obj.freeDofs = getFreeDofs(obj.fes);
-            
-            if L == 1
-                obj.Acoarse = A;
-            else
-                obj.smoother{L} = full(diag(A)).^(-1);
-                obj.intergridMatrix{L} = obj.P.matrix(obj.freeDofs, obj.freeDofsOld);
-                obj.changedPatches{L} = find(obj.changedPatches{L}(obj.freeDofs));
-            end
-        end
-           
-        % action: inverse of diagonal on each level
-        function Cx = apply(obj, x)
-            assert(~isempty(obj.nLevels), 'Data for multilevel iteration not given!')
-
-            L = obj.nLevels;
-            rho = cell(L, 1);
-
-            if L == 1
-                Cx = obj.Acoarse \ x;
-                return
-            end
-
-            % descending cascade
-            rho{L} = localSmoothing(obj, L, x);
-            residual = x;
-            for k = L-1:-1:2
-                residual = obj.intergridMatrix{k+1}' * residual;
-                rho{k} = localSmoothing(obj, k, residual);
-            end
-            
-            % exact solve on coarsest level
-            residual = obj.intergridMatrix{2}' * residual;
-            Cx = obj.Acoarse \ residual;
-            
-            % ascending cascade
-            for k = 2:L-1
-                Cx = obj.intergridMatrix{k} * Cx;
-                Cx = Cx + rho{k};
-            end
-            Cx = obj.intergridMatrix{L} * Cx + rho{L};
-        end
-    end
-
-    methods (Access=protected)
-        % callback function to be executed before mesh refinement:
-        % -> patches change for all vertices adjacent to refined edges and
-        %    all new vertices
-        function getChangedPatches(obj, mesh, bisecData)
-            nCOld = mesh.nCoordinates;
-            nCNew = mesh.nCoordinates + nnz(bisecData.bisectedEdges) + ...
-                bisecData.nRefinedElements'*bisecData.nInnerNodes;
-            bisectedEdgeNodes = unique(mesh.edges(:,bisecData.bisectedEdges));
-            obj.changedPatches{obj.nLevels+1} = false(nCNew, 1);
-            idx = [unique(bisectedEdgeNodes); ((nCOld+1):nCNew)'];
-            obj.changedPatches{obj.nLevels+1}(idx) = true;
-        end
-        
-        function rho = localSmoothing(obj, k, res)
-            idx = obj.changedPatches{k};
-            rho = zeros(size(res));
-            rho(idx,:) = obj.smoother{k}(idx).*res(idx,:);
-        end
-    end
-end
\ No newline at end of file
diff --git a/tests/TestIterativeSolver.m b/tests/TestIterativeSolver.m
index a4f516c..cbde884 100644
--- a/tests/TestIterativeSolver.m
+++ b/tests/TestIterativeSolver.m
@@ -7,6 +7,7 @@ properties (TestParameter)
         'jacobiPCG', ["pcg", "jacobi"], ...
         'iChol', ["pcg", "iChol"], ...
         'loAdditiveSchwarzPCG', ["pcg", "additiveSchwarzLowOrder"], ...
+        'hoAdditiveSchwarzPCG', ["pcg", "additiveSchwarzHighOrder"], ...
         'lowMG', ["multigrid", "lowOrderVcycle"], ...
         'highMG', ["multigrid", "highOrderVcycle"]);
 end
-- 
GitLab