diff --git a/examples/algebraicSolver.m b/examples/algebraicSolver.m
index f353744f765d7017efa7c02823f5d3f8c96a3b8a..9e6211ebd724ca7f80ce5d711e49ab5a679322c5 100644
--- a/examples/algebraicSolver.m
+++ b/examples/algebraicSolver.m
@@ -30,7 +30,9 @@ for p = 1:pmax
     % choose the iterative solver of choice: multigrid (with the variants
     % lowOrderVcycle and highOrderVcycle), pcg (with jacobi and additive
     % Schwarz preconditioner), and the cg solver
-    [solver, P] = IterativeSolver.choose(fes, blf, 'multigrid', 'lowOrderVcycle');
+    %[solver, P] = IterativeSolver.choose(fes, blf, "pcg", "iChol");
+    %[solver, P] = IterativeSolver.choose(fes, blf, "pcg", "additiveSchwarzHighOrder");
+    [solver, P] = IterativeSolver.choose(fes, blf, "multigrid", "lowOrderVcycle");
     solver.tol = 1e-6;
     solver.maxIter = 100;
 
diff --git a/experiments/p_laplace.m b/experiments/p_laplace.m
deleted file mode 100644
index 16e81435101ff679cd305a62701dcc28128c05f7..0000000000000000000000000000000000000000
--- a/experiments/p_laplace.m
+++ /dev/null
@@ -1,170 +0,0 @@
-%% paramters
-nElemMax = 1e5;
-theta = 0.5;
-lambda = 0.1;
-deltaZ = 0.1;
-linearization = 'kacanov';
-geometry = 'Lshape';
-p = 3;
-
-%% initialisation
-[nElem, eta, E] = deal(zeros(1,1000));
-
-%% setup geometry & spaces
-mesh = Mesh.loadFromGeometry(geometry);
-mesh.refineUniform(2);
-fes = FeSpace(mesh, LowestOrderH1Fe(), 'dirichlet', ':');
-u = FeFunction(fes);
-
-%% set problem data given in [Heid, Praetorius, Wihler; 2021]
-Du = Gradient(u);
-g = Constant(mesh, 1);
-[blf, lf] = setProblemData(mesh, fes, Du, g, linearization, p);
-eDensity = CompositeFunction(@(t) muIntegral(vectorProduct(t, t),p), Du);
-u.setFreeData(rand(1,length(getFreeDofs(u.fes))));
-
-%% nested iteration
-P = LoMeshProlongation(fes);
-
-%% adaptive algorithm
-i = 1;
-Eold = 0;
-qr = QuadratureRule.ofOrder(1); % tweak?
-while 1
-    % the volume residual for lowest order is independent of the approximate
-    % solution, so it can be computed once
-    vol = estimateVolume(g);
-        
-    %% inner iteration to solve the nonlinear problem
-    nIterations = 1;
-    while true
-        %% solve linearized problem
-        A = assemble(blf, fes);
-        F = assemble(lf, fes);
-        updateDataU(u, deltaZ, A, F, linearization);
-        
-        %% compute remainder of error estimator and energy
-        edge = estimateEdge(u, mesh, p);
-        eta2 = combineEstimators(vol, edge, mesh);
-        eta(i) = sqrt(sum(eta2));
-        nElem(i) = mesh.nElements;
-            
-        if i > 1, Eold = E(i-1); end
-        E(i) = sum(integrateElement(eDensity, qr)) + u.data * F;
-            
-        %% stopping criterion of the iterative solver
-        if sqrt(E(i) - Eold) <= lambda * eta(i) % norm(e) <= fehler
-            break
-        end
-        nIterations = nIterations + 1;
-        i = i+1;
-    end
-
-    %% stoping criterion for the AFEM algorithm
-    printLogMessage('number of elements: %d, iterations: %d, estimator: %.2e', ...
-        nElem(i), nIterations, eta(i));
-    if nElem(i) > nElemMax
-        break
-    end
-
-    %% refine mesh
-    marked = markDoerflerSorting(eta2, theta);
-    mesh.refineLocally(marked, 'NVB');
-    u.setData(prolongate(P, u));
-    i = i+1;
-end
-
-%% plot convergence rates
-plotData(nElem, 'number of elements', eta, 'error estimator', linearization,p)
-
-%% helper function for plotting
-function plotData(xdata, xlab, ydata, ylab, name,p)
-    figure()
-    for k = 1:size(xdata,1)
-        idx = find(xdata(k,:) > 0);
-        loglog(xdata(k,idx), ydata(k,idx), '-o', 'LineWidth', 2, 'DisplayName', strcat(name,', p=', num2str(p)));
-        hold on
-    end
-    x = xdata(1,idx) / xdata(1,1);
-    loglog(xdata(1,1)*x, ydata(1,1)*x.^(-1/2), '--', 'LineWidth', 2, 'Color', 'k', 'DisplayName', '\alpha = 1/2')
-    legend
-    xlabel(xlab)
-    ylabel(ylab)
-    title([ylab, ' over ', xlab])
-end
-
-%% local function for problem data
-function [blf, lf] = setProblemData(mesh, fes, Du, g, linearization, p)
-    blf = BilinearForm();
-    lf = LinearForm();
-
-    switch linearization
-        case "zarantonello"
-            blf.a = Constant(mesh, 1);
-            lf.f = g;
-            lf.fvec = CompositeFunction(@(t) -mu(vectorProduct(t, t),p) .* t, Du);
-        case "kacanov"
-            blf.a = CompositeFunction(@(t) mu(vectorProduct(t, t),p), Du);
-            lf.f = g;
-            lf.fvec = [];
-        case "newton"
-            blf.a = CompositeFunction(@(t) mu(vectorProduct(t, t),p) .* [1;0;0;1] ...
-                + 2*muDerivative(vectorProduct(t, t),p).*vectorProduct(t, t, [2,1], [2,1]'), Du);
-            lf.f = g;
-            lf.fvec = CompositeFunction(@(t) -mu(vectorProduct(t, t),p) .* t, Du);
-    end
-end
-
-function updateDataU(u, deltaZ, A, F, linearization)
-    freeDofs = getFreeDofs(u.fes);
-    v = FeFunction(u.fes);
-    switch linearization
-        case "zarantonello"
-            v.setFreeData(A(freeDofs,freeDofs) \ F(freeDofs));
-            u.setData(u.data + deltaZ*v.data);
-        case "kacanov"
-            %u.setData(A \ F);
-            u.setFreeData(A(freeDofs,freeDofs) \ F(freeDofs));
-        case "newton"
-            v.setFreeData(A(freeDofs,freeDofs) \ F(freeDofs));
-            u.setData(u.data + v.data);
-    end
-end
-
-%% local function for residual a posteriori error estimation
-% \eta(T)^2 = h_T^2 * || g ||_{L^2(T)}^2
-%               + h_T * || [[ mu(|Du|^2)*Du * n]] ||_{L^2(E) \cap \Omega}^2
-function vol = estimateVolume(g)
-    qr = QuadratureRule.ofOrder(1);
-    vol = integrateElement(g, qr);
-end
-   
-function edge = estimateEdge(u, mesh, p)
-    qr = QuadratureRule.ofOrder(1, '1D');
-    dirichlet = vertcat(mesh.boundaries{:});
-    
-    f = CompositeFunction(@(t) mu(vectorProduct(t, t),p).*t, Gradient(u));
-    edge = integrateNormalJump(f, qr, ...
-        @(j) zeros(size(j)), {}, dirichlet, ...     % no jump on dirichlet edges
-        @(j) j.^2, {}, ':');                        % normal jump on inner edges
-    
-end
-
-function indicators = combineEstimators(vol, edge, mesh)
-    trafo = getAffineTransformation(mesh);
-    hT = sqrt(trafo.area);
-    indicators = hT.^2.*vol + hT.*sum(edge(mesh.element2edges), Dim.Vector);
-end
-
-%% nonlinearities
-function y = mu(t,p)
-    y = t.^(p/2 - 1);
-end
-
-function y = muIntegral(t,p)
-    y = p/4 * t.^(p/2);
-end
-
-function y = muDerivative(t,p)
-    y = (p/2 - 1) * t.^(p/2 - 2);
-end
diff --git a/runAdditiveSchwarzPreconditioner.m b/runAdditiveSchwarzPreconditioner.m
deleted file mode 100644
index a0dbc376bbeba500ba22ff0476058fae1069bdce..0000000000000000000000000000000000000000
--- a/runAdditiveSchwarzPreconditioner.m
+++ /dev/null
@@ -1,123 +0,0 @@
-function leveldata = runAdditiveSchwarzPreconditioner(pMax)
-
-    % Proceed input
-    if nargin < 1
-        pMax = 1e1;
-    end
-
-    % Number of refinement levels
-    nLevels = 2;
- 
-    % Initialize LevelData
-    leveldata = LevelData('results');
-    leveldata.problem = 'Poisson';
-    leveldata.domain = 'UnitSquare';
-    leveldata.method = 'Sp_PCG_AdditiveSchwarz';
-
-    % Run experiment for various p
-    for p = 1:pMax
-        % Load mesh
-        mesh = Mesh.loadFromGeometry('unitsquare');
-        %mesh.refineUniform();
-    
-        % Create FE space
-        fes = FeSpace(mesh, HigherOrderH1Fe(p), 'dirichlet', ':');
-        % u = FeFunction(fes);
-    
-        % Setup variational problem
-        blf = BilinearForm();
-        lf = LinearForm();
-        blf.a = Constant(mesh, 1);
-        lf.f = Constant(mesh, 1);
-    
-        % Choose iterative solver
-        solver = IterativeSolver.choose(fes, blf, "pcg", "additiveSchwarzLowOrder");
-        % solver = IterativeSolver.choose(fes, blf, "pcg", "additiveSchwarzHighOrder");
-        % solver = IterativeSolver.choose(fes, blf, "pcg", "iChol");
-        % solver = IterativeSolver.choose(fes, blf, "multigrid", "lowOrderVcycle");
-    
-        for k = 1:nLevels-1
-            % Assemble matrices on intermediate meshes
-            A = assemble(blf, fes);
-            F = assemble(lf, fes);
-            freeDofs = getFreeDofs(fes);
-            solver.setupSystemMatrix(A(freeDofs,freeDofs));
-            solver.setupRhs(F(freeDofs));  % initializes x0 as well
-            % Mesh refinement
-            mesh.refineUniform();
-        end
-    
-        % Assemble matrices on fine mesh
-        A = assemble(blf, fes);
-        if ~issymmetric(A)
-            %warning(['symmetrizing, norm(asym(A)) = ', num2str(norm(A - A', 1))])
-            A = (A + A') / 2;
-        end
-        F = assemble(lf, fes);
-        freeDofs = getFreeDofs(fes);
-        solver.setupSystemMatrix(A(freeDofs,freeDofs));
-        solver.setupRhs(F(freeDofs));  % initializes x0 as well
-
-        % A = A(freeDofs,freeDofs);
-
-        % figure(2);
-        % spy(A);
-        % title('Matrix A');
-
-        % Compute C
-        % C = zeros(length(freeDofs));
-        % for j = 1:length(freeDofs)
-        %     ej = zeros(length(freeDofs), 1);
-        %     ej(j) = 1;
-        %     C(:,j) = solver.preconditionAction(ej);  
-        % end
-        % if ~issymmetric(C)
-        %     % warning(['symmetrizing, norm(asym(C)) = ', num2str(norm(C - C', 1))])
-        %     C = (C + C') / 2;
-        % end
-
-        % figure(3);
-        % CC = C;
-        % CC(abs(C) < 1e-10) = 0; 
-        % spy(CC);
-        % title('Matrix C');
-        % 
-        % asymC = (C - C');
-
-        % figure(4);
-        % largeAsymC = asymC;
-        % largeAsymC(abs(largeAsymC) < max(abs(largeAsymC(:))) / 10) = 0;
-        % spy(largeAsymC);
-        % title('Large entries of asym(C)');
-
-        % figure(5);
-        % asymC(abs(asymC) < 1e-10) = 0;
-        % spy(asymC);
-        % title('Nonzero entries of asym(C)');
-
-        % Compute conditional number
-        % CA = C*A;
-        % lambda = eig(full(CA));
-        % condition = max(abs(lambda)) / min(abs(lambda));
-        % lambdaMin = eigs(@(x)applyCA(x, A, solver), length(freeDofs), 1, 'smallestreal');
-        % lambdaMax = eigs(@(x)applyCA(x, A, solver), length(freeDofs), 1, 'largestreal');
-        % lambdaMin = eigs(CA, 1, 'smallestabs');
-        % lambdaMax = eigs(CA, 1, 'largestabs');
-        % condition = lambdaMax / lambdaMin;
-
-        % Print result to commandline
-        % leveldata.append('p', uint32(p), 'condition', condition);
-        % leveldata.printLevel();
-    end
-
-    % Plot condition
-    figure();
-    leveldata.plot('p');
-end
-
-
-function CAx = applyCA(x, A, solver)
-    % CAx = solver.preconditionAction(A * x);
-    % CAx = (A * x);
-    CAx = solver.preconditionAction(x);
-end
diff --git a/runIterativeSolver.m b/runIterativeSolver.m
deleted file mode 100644
index a14ceae87e6bf7a7b550c59b7b2b0b33b338c0ec..0000000000000000000000000000000000000000
--- a/runIterativeSolver.m
+++ /dev/null
@@ -1,144 +0,0 @@
-function leveldata = runIterativeSolver(maxNiter)
-
-    % Proceed input
-    if nargin < 1
-        maxNiter = 1e2;
-    end
-
-    % Polynomial degree
-    p = 4;
-
-    % Number of refinement levels
-    nLevels = 6;
-
-    % Load mesh
-    mesh = Mesh.loadFromGeometry('unitsquare');
-    mesh.refineUniform();
-
-    % Create FE space
-    fes = FeSpace(mesh, HigherOrderH1Fe(p), 'dirichlet', ':');
-    u = FeFunction(fes);
-    ustar = FeFunction(fes);
-
-    % Setup variational problem
-    blf = BilinearForm();
-    lf = LinearForm();
-    blf.a = Constant(mesh, 1);
-    lf.f = Constant(mesh, 1);
-
-    % Choose iterative solver
-    solver = IterativeSolver.choose(fes, blf, "pcg", "additiveSchwarzLowOrder");
-    % solver = IterativeSolver.choose(fes, blf, "pcg", "additiveSchwarzHighOrder");
-    % solver = IterativeSolver.choose(fes, blf, "pcg", "iChol");
-    % solver = IterativeSolver.choose(fes, blf, "multigrid", "lowOrderVcycle");
-
-    % Initialize LevelData
-    leveldata = LevelData('results');
-    leveldata.problem = 'Poisson';
-    leveldata.domain = 'UnitSquare';
-    leveldata.method = 'Sp_PCG_AdditiveSchwarz';
-
-    for k = 1:nLevels-1
-        % Output
-        fprintf('Assemble data for level = %d\n', k);
-        % Assemble matrices on intermediate meshes
-        A = assemble(blf, fes);
-        F = assemble(lf, fes);
-        freeDofs = getFreeDofs(fes);
-        solver.setupSystemMatrix(A(freeDofs,freeDofs));
-        solver.setupRhs(F(freeDofs));  % initializes x0 as well
-        % Mesh refinement
-        mesh.refineUniform();
-    end
-
-    fprintf('Assemble data for level = %d\n', nLevels);
-
-    % Assemble matrices on fine mesh
-    A = assemble(blf, fes);
-    F = assemble(lf, fes);
-    freeDofs = getFreeDofs(fes);
-    solver.setupSystemMatrix(A(freeDofs,freeDofs));
-    solver.setupRhs(F(freeDofs));  % initializes x0 as well
-
-    % Compute exact solution
-    fprintf('Do exact solve\n');
-    xstar = A(freeDofs,freeDofs) \ F(freeDofs);
-
-    % Refinement loop
-    nIter = 1;
-    while true
-        ndof = getDofs(fes).nDofs;
-
-        % Compute energy norm
-        errorU = sqrt((xstar - solver.x)' * A(freeDofs,freeDofs) * (xstar - solver.x));
-
-        % Compute contraction factor for energy norm
-        if nIter == 1
-            contraction = Inf;
-        else
-            contraction = errorU / errorUPrevious;
-        end
-
-        % Store variables
-        leveldata.append('ndof', uint32(ndof), ...
-                         'errorU', errorU);
-        leveldata.setAbsolute(nIter, 'nIter', uint32(nIter), ...
-                                     'contraction', contraction);
-
-        % Print level information
-        leveldata.printLevel();
-
-        % Plot solution
-        % if p == 2
-        %     x = zeros(mesh.nCoordinates + mesh.nEdges, 1);
-        %     x(freeDofs) = solver.x;
-        %     figure(1);
-        %     plotS2(mesh.clone(), x);
-        % 
-        %     figure(2);
-        %     x(freeDofs) = xstar;
-        %     plotS2(mesh.clone(), x);
-        %     title("Exact algebraic solution")
-        % else
-        %     figure(1);
-        %     u.setFreeData(solver.x);
-        %     plot(u);
-        % 
-        %     figure(2);
-        %     ustar.setFreeData(xstar);
-        %     plot(ustar);
-        % end
-        % pause(2)
-
-        % Break condition
-        % if ndof > maxNdof
-        %     break;
-        % end
-
-        % Mesh refinement
-        % mesh.refineUniform();
-
-        if nIter > maxNiter
-            break;
-        end
-
-        % One iterative solver step
-        solver.step();
-
-        % Update iteration counter
-        nIter = nIter + 1;
-        errorUPrevious = errorU;
-    end
-
-    % Plot solution
-    u.setFreeData(solver.x);
-    plot(u);
-
-    % Plot error
-    figure();
-    leveldata.plot('nIter');
-
-    % Plot contraction
-    figure();
-    leveldata.plotAbsolute('nIter');
-end
diff --git a/test.m b/test.m
deleted file mode 100644
index 505e85f64341a4cb0f5b3508fa0e7e2d6eb4d257..0000000000000000000000000000000000000000
--- a/test.m
+++ /dev/null
@@ -1,61 +0,0 @@
-mesh = Mesh.loadFromGeometry('unitsquare');
-%mesh.refineUniform();
-
-fes1 = FeSpace(mesh, HigherOrderH1Fe(1), 'dirichlet', ':');
-fes2 = FeSpace(mesh, HigherOrderH1Fe(15), 'dirichlet', ':');
-
-testMatrix = ones(getDofs(fes2).nDofs, 1);
-inclusionMatrix = MeshProlongation(fes1, fes2);
-inclusionMatrix = full(inclusionMatrix);
-Vertices = 1:getDofs(fes1).nDofs;
-inclusionMatrix2 = interpolateData(eye(length(Vertices)), fes1, fes2)';
-diff = inclusionMatrix - inclusionMatrix2;
-
-function inclusionMatrix = MeshProlongation(fromFes, toFes)
-    %Create the prolongation matrix on the unit triangle
-    unittriangle = Mesh.loadFromGeometry('unittriangle');
-    fromFesUnitTriangle = FeSpace(unittriangle, ...
-                             HigherOrderH1Fe(fromFes.finiteElement.order));
-    toFesUnitTriangle = FeSpace(unittriangle, ...
-                               HigherOrderH1Fe(toFes.finiteElement.order));
-    Vertices = 1:getDofs(fromFesUnitTriangle).nDofs;
-    unitTriangleInclusionMatrix = ...
-        permute(interpolateData(eye(length(Vertices)),...
-                           fromFesUnitTriangle, toFesUnitTriangle), [2 1]);
-    % Get local to global map in unit triangle
-
-    unitTriangleInclusionMatrix = unitTriangleInclusionMatrix( ...
-                             getDofs(fromFesUnitTriangle).element2Dofs, ...
-                             getDofs(toFesUnitTriangle).element2Dofs);
-    % Get dofs of the finite element spaces on the meshes
-    fromFesDofs = getDofs(fromFes);
-    toFesDofs = getDofs(toFes);
-
-    % Copy the matrix from the unit triangle for each element
-    mat = repmat(unitTriangleInclusionMatrix, ...
-                 [1, 1, fromFes.mesh.nElements]);
-
-    % Create index sets for the accumarray
-    temp = fromFesDofs.element2Dofs(:, :);
-    temp2 = toFesDofs.element2Dofs(:, :);
-    I = repmat(permute(temp, [1 3 2]), [1, size(temp2, 1), 1]);
-    J = repmat(permute(temp2, [3 1 2]), [size(temp, 1), 1, 1]);
-    ind = [I(:), J(:)];
-    
-    inclusionMatrix = ...
-      accumarray(ind, mat(:), [], @mean, [], true);
-end
-
-function interpolatedData = interpolateData(data, fromFes, toFes)
-    Dofs = 1:getDofs(toFes).nDofs;
-    nComponents = size(data, 2);
-    interpolatedData = zeros(numel(Dofs), nComponents);
-    feFunctionWrapper = FeFunction(fromFes);
-    for k = 1:nComponents
-        feFunctionWrapper.setData(data(:,k));
-        wholeData = nodalInterpolation(feFunctionWrapper, toFes);
-        interpolatedData(:,k) = wholeData(Dofs);
-    end
-end
-
-
diff --git a/tmp/plotS2.m b/tmp/plotS2.m
deleted file mode 100644
index 92ee44046520096223c78bb8688872588c419297..0000000000000000000000000000000000000000
--- a/tmp/plotS2.m
+++ /dev/null
@@ -1,49 +0,0 @@
-function ax = plotS2(mesh, x)
-%%PLOTS2 plots a piecewise P2 and globally continuous function (Lagrange
-%finite element function) given by the coefficient vector x on the
-%triangulation meshdata
-%   ax = PLOTS2(meshdata, x)
-
-% Copyright 2023 Philipp Bringmann
-%
-% This program is free software: you can redistribute it and/or modify
-% it under the terms of the GNU General Public License as published by
-% the Free Software Foundation, either version 3 of the License, or
-% (at your option) any later version.
-%
-% This program is distributed in the hope that it will be useful,
-% but WITHOUT ANY WARRANTY; without even the implied warranty of
-% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-% GNU General Public License for more details.
-%
-% You should have received a copy of the GNU General Public License
-% along with this program.  If not, see <http://www.gnu.org/licenses/>.
-%
-
-
-    % Create axes object
-    ax = gca;
-
-    % Uniform red-refinement of triangulation
-    mesh.refineUniform();
-
-    % S1 plot of nodal interpolation on red-refinement
-    if size(x, 2) == 1
-        % Plot of scalar function
-        pat = trisurf(mesh.elements', mesh.coordinates(1,:)',...
-                      mesh.coordinates(2,:)', x);
-        if(mesh.nElements > 2000)
-            set(pat, 'EdgeColor', 'none');
-        end
-    elseif size(x, 2) == 2
-        % Plot of vector field
-        set(ax, 'XLim', 2.1*[min(mesh.coordinates(1,:)), max(mesh.coordinates(1,:))],...
-                'YLim', 1.1*[min(mesh.coordinates(2,:)), max(mesh.coordinates(2,:))]);
-        quiver(mesh.coordinates(1,:), mesh.coordinates(2,:), x(:,1), x(:,2));
-    else
-        error('Invalid size of coefficient vector');
-    end
-
-    % Draw title
-    title(ax, {'S2 function plot'; [num2str(mesh.nEdges + mesh.nCoordinates), ' dofs']});
-end