changeset 1331:60c875c18de3 feature/D2_boundary_opt

Merge with feature/poroelastic for Elastic schemes
author Vidar Stiernström <vidar.stiernstrom@it.uu.se>
date Thu, 10 Mar 2022 16:54:26 +0100
parents 855871e0b852 (diff) 412b8ceafbc6 (current diff)
children 8e9df030a0a5
files +multiblock/DefCurvilinear.m +multiblock/DiffOp.m +parametrization/Curve.m +parametrization/dataSpline.m +sbp/D2VariableCompatible.m +scheme/Elastic2dCurvilinear.m +scheme/Elastic2dVariable.m diracDiscr.m diracDiscrTest.m
diffstat 65 files changed, 3480 insertions(+), 2020 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/boundaryOptimized.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,54 @@
+% Creates a Cartesian grid of dimension length(m)
+% over the domain xlim, ylim, ...
+% The grid is non-equidistant in the boundary regions,
+% with node placement based on boundary-optimized SBP operators.
+% Examples:
+%   g = grid.boundaryOptimized([mx, my], xlim, ylim, order, opt)
+%   g = grid.boundaryOptimized([10, 15], {0,1}, {0,2}, 4) - defaults to 'accurate' stencils
+%   g = grid.boundaryOptimized([10, 15], {0,1}, {0,2}, 4, 'minimal')
+function g = boundaryOptimized(m, varargin)
+    n = length(m);
+
+    % Check that parameters matches dimensions
+    matchingParams = false;
+    if length(varargin) == n+1 % Minimal number of arguments
+            matchingParams = iscell([varargin{1:n}]) && ...
+                             isfloat([varargin{n+1}]);
+    elseif length(varargin) == n+2 % Stencil options supplied
+            matchingParams = iscell([varargin{1:n}]) && ...
+                             isfloat([varargin{n+1}]) && ...
+                             ischar([varargin{n+2}]);
+    end
+    assert(matchingParams,'grid:boundaryOptimized:NonMatchingParameters','The number of parameters per dimensions do not match.');
+
+    % Check that stencil options are passed correctly (if supplied)
+    if length(varargin) == n+2 % Stencil options supplied
+        availabe_opts = ["Accurate","accurate","A","acc","Minimal","minimal","M","min"];
+        assert(any(varargin{n+2} == availabe_opts), ...
+            'grid:boundaryOptimized:InvalidOption',"The operator option must be 'accurate' or 'minimal.'");
+    else %If not passed, populate varargin with default option 'accurate'
+        varargin(n+2) = {'accurate'};
+    end
+
+    % Specify generating function
+    switch varargin{n+2}
+        case {'Accurate','accurate','A','acc'}
+            gridgenerator = @sbp.grid.accurateBoundaryOptimizedGrid;
+        case {'Minimal','minimal','M','min'}
+            gridgenerator = @sbp.grid.minimalBoundaryOptimizedGrid;
+    end
+
+    X = {};
+    h = [];
+    for i = 1:n
+        try
+            [X{i},h(i)] = gridgenerator(varargin{i},m(i),varargin{n+1});
+        catch exception % Propagate any errors in the grid generation functions.
+            msgText = getReport(exception);
+            error('grid:boundaryOptimized:InvalidParameter',msgText)
+        end
+    end
+
+    g = grid.Cartesian(X{:});
+    g.h = h;
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/boundaryOptimizedCurvilinear.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,57 @@
+% Creates a curvilinear grid of dimension length(m).
+% over the logical domain xi_lim, eta_lim, ...
+% The grid point distribution is based on the boundary
+% optimized SBP operators, passed as arguments, order
+% and opt.
+% Examples:
+%   g = grid.boundaryOptimizedCurvilinear(mapping, [mx, my], xlim, ylim, order, opt)
+%   g = grid.boundaryOptimizedCurvilinear(mapping, [10, 15], {0,1}, {0,2}, 4) - defaults to 'accurate' stencils
+%   g = grid.boundaryOptimizedCurvilinear(mapping, [10, 15], {0,1}, {0,2}, 4, 'minimal')
+function g = boundaryOptimizedCurvilinear(mapping, m, varargin)
+    n = length(m);
+    % TODO: The input parameter check is also present in boundaryOptimizedGrid.m,
+    % Consider refactoring it.
+        
+    % Check that parameters matches dimensions
+    matchingParams = false;
+    if length(varargin) == n+1 % Minimal number of arguments
+            matchingParams = iscell([varargin{1:n}]) && ...
+                             isfloat([varargin{n+1}]);
+    elseif length(varargin) == n+2 % Stencil options supplied
+            matchingParams = iscell([varargin{1:n}]) && ...
+                             isfloat([varargin{n+1}]) && ...
+                             ischar([varargin{n+2}]);
+    end
+    assert(matchingParams,'grid:boundaryOptimizedCurvilinear:NonMatchingParameters','The number of parameters per dimensions do not match.');
+
+    % Check that stencil options are passed correctly (if supplied)
+    if length(varargin) == n+2 % Stencil options supplied
+        availabe_opts = ["Accurate","accurate","A","acc","Minimal","minimal","M","min"];
+        assert(any(varargin{n+2} == availabe_opts), ...
+            'grid:boundaryOptimizedCurvilinear:InvalidOption',"The operator option must be 'accurate' or 'minimal.'");
+    else %If not passed, populate varargin with default option 'accurate'
+        varargin(n+2) = {'accurate'};
+    end
+
+    % Specify generating function
+    switch varargin{n+2}
+        case {'Accurate','accurate','A','acc'}
+            gridgenerator = @sbp.grid.accurateBoundaryOptimizedGrid;
+        case {'Minimal','minimal','M','min'}
+            gridgenerator = @sbp.grid.minimalBoundaryOptimizedGrid;
+    end
+
+    X = {};
+    h = [];
+    for i = 1:n
+        try
+            [X{i},h(i)] = gridgenerator(varargin{i},m(i),varargin{n+1});
+        catch exception % Propagate any errors in the grid generation functions.
+            msgText = getReport(exception);
+            error('grid:boundaryOptimizedCurvilinear:InvalidParameter',msgText)
+        end
+    end
+
+    g = grid.Curvilinear(mapping, X{:});
+    g.logic.h = h;
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/boundaryOptimizedTest.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,111 @@
+function tests = boundaryOptimizedTest()
+    tests = functiontests(localfunctions);
+end
+
+function testErrorInvalidParam(testCase)
+    in  = {
+        %Invalid order
+        {[10 10],{0,1},{0,2},3},
+        %Invalid grid size
+        {5, {0,1}, 4},
+        {[10 5],{0,1},{0,2},4},
+        {[10 5],{0,1},{0,2},6,'M'},
+        %Invalid limits
+        {10,{1},4},
+        {[10,10],{0,1},{1},4},
+        {[10,10],{1},{1,0},4},
+        {10,{1,0},4},
+        {[10, 5],{1,0},{0,-1},4},
+    };
+
+    for i = 1:length(in)
+        testCase.verifyError(@()grid.boundaryOptimized(in{i}{:}),'grid:boundaryOptimized:InvalidParameter',sprintf('in(%d) = %s',i,toString(in{i})));
+    end
+end
+
+function testErrorInvalidOption(testCase)
+    in  = {
+        {[8 8],{0,1},{0,2},4,'acrurate'},
+    };
+
+    for i = 1:length(in)
+        testCase.verifyError(@()grid.boundaryOptimized(in{i}{:}),'grid:boundaryOptimized:InvalidOption',sprintf('in(%d) = %s',i,toString(in{i})));
+    end
+end
+
+function testErrorNonMatchingParam(testCase)
+    in  = {
+        {[],{1},4},
+        {[],{0,1},{0,1},4},
+        {[5,5],{0,1},{0,1},{0,1},4},
+        {[5,5,4],{0,1},{0,1},4,'accurate'}
+        {[5,5,4],{0,1},{0,1},{0,1},4,4},
+    };
+
+    for i = 1:length(in)
+        testCase.verifyError(@()grid.boundaryOptimized(in{i}{:}),'grid:boundaryOptimized:NonMatchingParameters',sprintf('in(%d) = %s',i,toString(in{i})));
+    end
+end
+
+% Tests that the expected grid points are obtained for a boundary optimized grid with a 4th order
+% accurate stencil and 8th order minimal stencil.
+% The boundary grid point distance weights are taken from the D1Nonequidistant operators and
+% grid spacing is calculated according to Mattsson et al 2018.
+function testCompiles(testCase)
+    
+    %% 1D 4th order accurate stencil
+    % Boundary weights, number of non-equidistantly spaced points for 4th order accurate stencil
+    bw = [0.0000000000000e+00 6.8764546205559e-01 1.8022115125776e+00];
+    n = length(bw)-1;
+    xi_n = bw(end);
+
+    % Grid points in x-direction.
+    Lx = 1;
+    mx = 8;
+    hx_4 = Lx/(2*xi_n+(mx-2*n-1)); 
+    
+    bp_l = hx_4*bw;
+    bp_r = Lx-flip(hx_4*bw);
+    interior = [hx_4*(xi_n+1) hx_4*(xi_n+2)];
+    x_4 = [bp_l interior bp_r];
+
+    % Boundary weights, number of non-equidistantly spaced points for 8th order minimal stencil    
+    bw = [0.0000000000000e+00, 4.9439570885261e-01, 1.4051531374839e+00];
+    n = length(bw)-1;
+    xi_n = bw(end);
+
+    %% 2D 8th order minimal stencil
+    % Grid points in x-direction.
+    hx_8 = Lx/(2*xi_n+(mx-2*n-1)); 
+    
+    bp_l = hx_8*bw;
+    bp_r = Lx-flip(hx_8*bw);
+    interior = [hx_8*(xi_n+1) hx_8*(xi_n+2)];
+    x_8 = [bp_l interior bp_r];
+
+    % Grid points in y-direction.
+    Ly = 2;
+    my = 9;
+    hy = Ly/(2*xi_n+(my-2*n-1));
+    
+    bp_l = hy*bw;
+    bp_r = Ly-flip(hy*bw);
+    interior = [hy*(xi_n+1) hy*(xi_n+2) hy*(xi_n+3)];
+    y = [bp_l interior bp_r];
+
+    in  = {
+        {mx, {0,Lx},4},
+        {[mx, my],{0,Lx},{0,Ly},8,'M'},
+    };
+    
+    out = {
+        {[x_4'],hx_4}
+        {[kr(x_8',ones(size(y'))),kr(ones(size(x_8')),y')],[hx_8, hy]}
+    };
+
+    for i = 1:length(in)
+        g = grid.boundaryOptimized(in{i}{:});
+        testCase.verifyEqual(g.points(),out{i}{1},'AbsTol', 1e-14, 'RelTol', 1e-14);
+        testCase.verifyEqual(g.scaling(),out{i}{2},'AbsTol', 1e-14, 'RelTol', 1e-14);
+    end
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/+domain/Annulus.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,74 @@
+classdef Annulus < multiblock.DefCurvilinear
+    properties
+        r_inner % Radii of inner disk
+        c_inner % Center of inner disk
+        r_outer % Radii of outer disk
+        c_outer % Radii of outer disk
+    end
+
+    methods
+        function obj = Annulus(r_inner, c_inner, r_outer, c_outer)
+            default_arg('r_inner', 0.3);
+            default_arg('c_inner', [0; 0]);
+            default_arg('r_outer', 1)
+            default_arg('c_outer', [0; 0]);
+            % Assert that the problem is well-defined
+            d = norm(c_outer-c_inner,2);
+            assert(r_inner > 0, 'Inner radius must be greater than zero');
+            assert(r_outer > d+r_inner, 'Inner disk not contained in outer disk');
+            
+            cir_out_A = parametrization.Curve.circle(c_outer,r_outer,[-pi/2 pi/2]);
+            cir_in_A = parametrization.Curve.circle(c_inner,r_inner,[pi/2 -pi/2]);
+            
+            cir_out_B = parametrization.Curve.circle(c_outer,r_outer,[pi/2 3*pi/2]);
+            cir_in_B = parametrization.Curve.circle(c_inner,r_inner,[3*pi/2 pi/2]);
+
+            c0_out = cir_out_A(0);
+            c1_out = cir_out_A(1);
+            
+            c0_in_A = cir_in_A(1);
+            c1_in_A = cir_in_A(0);
+            
+            c0_out_B = cir_out_B(0);
+            c1_out_B = cir_out_B(1);
+            
+            c0_in_B = cir_in_B(1);
+            c1_in_B = cir_in_B(0);
+
+
+            sp2_A = parametrization.Curve.line(c0_in_A,c0_out);
+            sp3_A = parametrization.Curve.line(c1_in_A,c1_out);
+            
+            sp2_B = parametrization.Curve.line(c0_in_B,c0_out_B);
+            sp3_B = parametrization.Curve.line(c1_in_B,c1_out_B);
+
+
+            A = parametrization.Ti(sp2_A, cir_out_A, sp3_A.reverse, cir_in_A); 
+            B = parametrization.Ti(sp2_B , cir_out_B,sp3_B.reverse, cir_in_B );
+            
+            blocks = {A,B};
+            blocksNames = {'A','B'};
+
+            conn = cell(2,2);
+
+            conn{1,2} = {'n','s'};
+            conn{2,1} = {'n','s'};
+
+            boundaryGroups = struct();
+            boundaryGroups.out = multiblock.BoundaryGroup({{1,'e'},{2,'e'}});
+            boundaryGroups.in = multiblock.BoundaryGroup({{1,'w'},{2,'w'}});
+            boundaryGroups.all = multiblock.BoundaryGroup({{1,'e'},{2,'w'},{1,'w'},{2,'e'}});
+
+            obj = obj@multiblock.DefCurvilinear(blocks, conn, boundaryGroups, blocksNames);
+
+            obj.r_inner = r_inner;
+            obj.r_outer = r_outer;
+            obj.c_inner = c_inner;
+            obj.c_outer = c_outer;
+        end
+
+        function ms = getGridSizes(obj, m)
+            ms = {[m m], [m m]};
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/+domain/Line.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,153 @@
+classdef Line < multiblock.Definition
+    properties
+
+    xlims
+    blockNames % Cell array of block labels
+    nBlocks
+    connections % Cell array specifying connections between blocks
+    boundaryGroups % Structure of boundaryGroups
+
+    end
+
+
+    methods
+        % Creates a divided line
+        % x is a vector of boundary and interface positions.
+        % blockNames: cell array of labels. The id is default.
+        function obj = Line(x,blockNames)
+            default_arg('blockNames',[]);
+
+            N = length(x)-1; % number of blocks in the x direction.
+
+            if ~issorted(x)
+                error('The elements of x seem to be in the wrong order');
+            end
+
+            % Dimensions of blocks and number of points
+            blockTi = cell(N,1);
+            xlims = cell(N,1);
+            for i = 1:N
+                xlims{i} = {x(i), x(i+1)};
+            end
+
+            % Interface couplings
+            conn = cell(N,N);
+            for i = 1:N
+                conn{i,i+1} = {'r','l'};
+            end
+
+            % Block names (id number as default)
+            if isempty(blockNames)
+                obj.blockNames = cell(1, N);
+                for i = 1:N
+                    obj.blockNames{i} = sprintf('%d', i);
+                end
+            else
+                assert(length(blockNames) == N);
+                obj.blockNames = blockNames;
+            end
+            nBlocks = N;
+
+            % Boundary groups
+            boundaryGroups = struct();
+            L = { {1, 'l'} };
+            R = { {N, 'r'} };
+            boundaryGroups.L = multiblock.BoundaryGroup(L);
+            boundaryGroups.R = multiblock.BoundaryGroup(R);
+            boundaryGroups.all = multiblock.BoundaryGroup([L,R]);
+
+            obj.connections = conn;
+            obj.nBlocks = nBlocks;
+            obj.boundaryGroups = boundaryGroups;
+            obj.xlims = xlims;
+
+        end
+
+
+        % Returns a multiblock.Grid given some parameters
+        % ms: cell array of m values
+        % For same m in every block, just input one scalar.
+        function g = getGrid(obj, ms, varargin)
+
+            default_arg('ms',21)
+
+            % Extend ms if input is a single scalar
+            if (numel(ms) == 1) && ~iscell(ms)
+                m = ms;
+                ms = cell(1,obj.nBlocks);
+                for i = 1:obj.nBlocks
+                    ms{i} = m;
+                end
+            end
+
+            grids = cell(1, obj.nBlocks);
+            for i = 1:obj.nBlocks
+                grids{i} = grid.equidistant(ms{i}, obj.xlims{i});
+            end
+
+            g = multiblock.Grid(grids, obj.connections, obj.boundaryGroups);
+        end
+
+        % Returns a multiblock.Grid given some parameters
+        % ms: cell array of m values
+        % For same m in every block, just input one scalar.
+        function g = getStaggeredGrid(obj, ms, varargin)
+
+            default_arg('ms',21)
+
+            % Extend ms if input is a single scalar
+            if (numel(ms) == 1) && ~iscell(ms)
+                m = ms;
+                ms = cell(1,obj.nBlocks);
+                for i = 1:obj.nBlocks
+                    ms{i} = m;
+                end
+            end
+
+            grids = cell(1, obj.nBlocks);
+            for i = 1:obj.nBlocks
+                [g_primal, g_dual] = grid.primalDual1D(ms{i}, obj.xlims{i});
+                grids{i} = grid.Staggered1d(g_primal, g_dual);
+            end
+
+            g = multiblock.Grid(grids, obj.connections, obj.boundaryGroups);
+        end
+
+        % label is the type of label used for plotting,
+        % default is block name, 'id' show the index for each block.
+        function show(obj, label)
+            default_arg('label', 'name')
+
+            m = 10;
+            figure
+            for i = 1:obj.nBlocks
+               x = linspace(obj.xlims{i}{1}, obj.xlims{i}{2}, m);
+               y = 0*x + 0.05* ( (-1)^i + 1 ) ;
+               plot(x,y,'+');
+               hold on
+            end
+            hold off
+
+            switch label
+                case 'name'
+                    labels = obj.blockNames;
+                case 'id'
+                    labels = {};
+                    for i = 1:obj.nBlocks
+                        labels{i} = num2str(i);
+                    end
+                otherwise
+                    axis equal
+                    return
+            end
+
+            legend(labels)
+            axis equal
+        end
+
+        % Returns the grid size of each block in a cell array
+        % The input parameters are determined by the subclass
+        function ms = getGridSizes(obj, varargin)
+        end
+    end
+end
--- a/+multiblock/+domain/Rectangle.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+multiblock/+domain/Rectangle.m	Thu Mar 10 16:54:26 2022 +0100
@@ -93,7 +93,7 @@
                 N_id = flat_index(m,j,1);
                 S{j} = {S_id,'s'};
                 N{j} = {N_id,'n'};
-            end  
+            end
             boundaryGroups.E = multiblock.BoundaryGroup(E);
             boundaryGroups.W = multiblock.BoundaryGroup(W);
             boundaryGroups.S = multiblock.BoundaryGroup(S);
@@ -117,6 +117,20 @@
         % Returns a multiblock.Grid given some parameters
         % ms: cell array of [mx, my] vectors
         % For same [mx, my] in every block, just input one vector.
+        % Currently defaults to equidistant grid if varargin is empty.
+        % If varargin is non-empty, the first argument should supply the grid type, followed by
+        % additional arguments required to construct the grid.
+        % Grid types:
+        %          'equidist' - equidistant grid
+        %                       Additional argumets: none
+        %          'boundaryopt' - boundary optimized grid based on boundary
+        %                          optimized SBP operators
+        %                          Additional arguments: order, stencil option
+        % Example: g = getGrid() - the local blocks are 21x21 equidistant grids.
+        %          g = getGrid(ms,) - block i is an equidistant grid with size given by ms{i}.
+        %          g = getGrid(ms,'equidist') - block i is an equidistant grid with size given by ms{i}.
+        %          g = getGrid(ms,'boundaryopt',4,'minimal') - block i is a Cartesian grid with size given by ms{i}
+        %              and nodes placed according to the boundary optimized minimal 4th order SBP operator.
         function g = getGrid(obj, ms, varargin)
 
             default_arg('ms',[21,21])
@@ -129,10 +143,19 @@
                     ms{i} = m;
                 end
             end
-
+            if isempty(varargin) || strcmp(varargin{1},'equidist')
+               gridgenerator = @(m,xlim,ylim)grid.equidistant(m,xlim,ylim);
+            elseif strcmp(varargin{1},'boundaryopt')
+                order = varargin{2};
+                stenciloption = varargin{3};
+                gridgenerator = @(m,xlim,ylim)grid.boundaryOptimized(m,xlim,ylim,...
+                    order,stenciloption);
+            else
+                error('No grid type supplied!');
+            end
             grids = cell(1, obj.nBlocks);
             for i = 1:obj.nBlocks
-                grids{i} = grid.equidistant(ms{i}, obj.xlims{i}, obj.ylims{i});
+                grids{i} = gridgenerator(ms{i}, obj.xlims{i}, obj.ylims{i});
             end
 
             g = multiblock.Grid(grids, obj.connections, obj.boundaryGroups);
--- a/+multiblock/DefCurvilinear.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+multiblock/DefCurvilinear.m	Thu Mar 10 16:54:26 2022 +0100
@@ -37,13 +37,39 @@
             obj.boundaryGroups = boundaryGroups;
         end
 
-        function g = getGrid(obj, varargin)
-            ms = obj.getGridSizes(varargin{:});
+        % Returns a multiblock.Grid given some parameters
+        % ms: cell array of [mx, my] vectors
+        % Currently defaults to an equidistant curvilinear grid if varargin is empty.
+        % If varargin is non-empty, the first argument should supply the grid type, followed by
+        % additional arguments required to construct the grid.
+        % Grid types:
+        %          'equidist' - equidistant curvilinear grid
+        %                       Additional argumets: none
+        %          'boundaryopt' - boundary optimized grid based on boundary
+        %                          optimized SBP operators
+        %                          Additional arguments: order, stencil option
+        function g = getGrid(obj, ms, varargin)
+            % If a scalar is passed, defer to getGridSizes implemented by subclass
+            % TODO: This forces the interface of subclasses.
+            % Should ms be included in varargin? Figure out bow to do it properly
+            if length(ms) == 1
+                ms = obj.getGridSizes(ms);
+            end
 
-            grids = cell(1, obj.nBlocks);
-            for i = 1:obj.nBlocks
-                grids{i} = grid.equidistantCurvilinear(obj.blockMaps{i}.S, ms{i});
-            end
+            if isempty(varargin) || strcmp(varargin{1},'equidist')
+                gridgenerator = @(blockMap,m) grid.equidistantCurvilinear(blockMap, m);
+            elseif strcmp(varargin{1},'boundaryopt')
+                 order = varargin{2};
+                 stenciloption = varargin{3};
+                 gridgenerator = @(blockMap,m) grid.boundaryOptimizedCurvilinear(blockMap,m,{0,1},{0,1},...
+                     order,stenciloption);
+             else
+                 error('No grid type supplied!');
+             end
+             grids = cell(1, obj.nBlocks);
+             for i = 1:obj.nBlocks
+                grids{i} = gridgenerator(obj.blockMaps{i}.S, ms{i});
+             end
 
             g = multiblock.Grid(grids, obj.connections, obj.boundaryGroups);
         end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/LaplaceSquared.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,105 @@
+classdef LaplaceSquared < scheme.Scheme
+    properties
+        grid
+        order
+        laplaceDiffOp
+
+        D
+        H
+        Hi
+
+        a,b
+    end
+
+    methods
+        % Discretisation of a*nabla*b*nabla
+        function obj = LaplaceSquared(g, order, a, b, opGen)
+            default_arg('order', 4);
+            default_arg('a', 1);
+            default_arg('b', 1);
+            default_arg('opGen', @sbp.D4Variable);
+
+            if isscalar(a)
+                a = grid.evalOn(g, a);
+            end
+
+            if isscalar(b)
+                b = grid.evalOn(g, b);
+            end
+
+            obj.grid = g;
+            obj.order = order;
+            obj.a = a;
+            obj.b = b;
+
+            obj.laplaceDiffOp = multiblock.Laplace(g, order, 1, 1, opGen);
+
+            obj.H = obj.laplaceDiffOp.H;
+            obj.Hi = spdiag(1./diag(obj.H));
+
+            A = spdiag(a);
+            B = spdiag(b);
+
+            D_laplace = obj.laplaceDiffOp.D;
+            obj.D = A*D_laplace*B*D_laplace;
+        end
+
+        function s = size(obj)
+            s = size(obj.laplaceDiffOp);
+        end
+
+        function op = getBoundaryOperator(obj, opName, boundary)
+            switch opName
+                case 'e'
+                    op = getBoundaryOperator(obj.laplaceDiffOp, 'e', boundary);
+                case 'd1'
+                    op = getBoundaryOperator(obj.laplaceDiffOp, 'd', boundary);
+                case 'd2'
+                    e = getBoundaryOperator(obj.laplaceDiffOp, 'e', boundary);
+                    op = (e'*obj.laplaceDiffOp.D)';
+                case 'd3'
+                    d1 = getBoundaryOperator(obj.laplaceDiffOp, 'd', boundary);
+                    op = (d1'*spdiag(obj.b)*obj.laplaceDiffOp.D)';
+            end
+        end
+
+        function op = getBoundaryQuadrature(obj, boundary)
+            op = getBoundaryQuadrature(obj.laplaceDiffOp, boundary);
+        end
+
+        function [closure, penalty] = boundary_condition(obj,boundary,type) % TODO: Change name to boundaryCondition
+            switch type
+                case 'e'
+                    error('Bc of type ''e'' not implemented')
+                case 'd1'
+                    error('Bc of type ''d1'' not implemented')
+                case 'd2'
+                    e = obj.getBoundaryOperator('e', boundary);
+                    d1 = obj.getBoundaryOperator('d1', boundary);
+                    d2 = obj.getBoundaryOperator('d2', boundary);
+                    H_b = obj.getBoundaryQuadrature(boundary);
+
+                    A = spdiag(obj.a);
+                    B_b = spdiag(e'*obj.b);
+
+                    tau = obj.Hi*A*d1*B_b*H_b;
+                    closure =  tau*d2';
+                    penalty = -tau;
+                case 'd3'
+                    e = obj.getBoundaryOperator('e', boundary);
+                    d3 = obj.getBoundaryOperator('d3', boundary);
+                    H_b = obj.getBoundaryQuadrature(boundary);
+
+                    A = spdiag(obj.a);
+
+                    tau = -obj.Hi*A*e*H_b;
+                    closure =  tau*d3';
+                    penalty = -tau;
+            end
+        end
+
+        function [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary)
+            error('Not implemented')
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/joinGrids.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,90 @@
+% Connects several multiblock grids into one grid
+% gs 		   -- 	Cell array of multiblock grids
+% gConnections -- 	Upper-triangular cell matrix
+% 					gConnections{i,j} specifies all connections between grid i and grid j
+%					Example:
+%					gConnections{i,j} = { {{1,'e'},{2,'w'}}, {{5,'s'},{2,'n'}},... };
+% names 	   --	(Optional) cell array of strings, used for boundary groups in new grid
+%					default: names = {'g1', 'g2', ..., 'gN'};
+% 					Boundary groups from grid i are contained in g.boundaryGroups.(names{i}), etc.
+function g = joinGrids(gs, gConnections, names)
+
+	nGrids = numel(gs);
+
+	% Default names are {'g1', 'g2', ... 'gN'}.
+	defaultNames = cell(nGrids, 1);
+	for i = 1:nGrids
+		defaultNames{i} = sprintf('g%d',i);
+	end
+	default_arg('names', defaultNames);
+
+	nBlocks = 0;
+	for i = 1:nGrids
+		nBlocks = nBlocks + gs{i}.nBlocks();
+	end
+
+	% Create vector of cumulative sum of number of blocks per grid
+	startIndex = zeros(1, nGrids);
+	for i = 2:nGrids
+		startIndex(i) = startIndex(i-1) + gs{i-1}.nBlocks();
+	end
+
+	% Create cell array of all grids
+	grids = cell(nBlocks, 1);
+	for i = 1:nGrids
+		for j = 1:gs{i}.nBlocks();
+			grids{startIndex(i)+j} = gs{i}.grids{j};
+		end
+	end
+
+	% Create cell matrix of connections
+	connections = cell(nBlocks, nBlocks);
+
+	% Connections within grids
+	for i = 1:nGrids
+		for j = 1:gs{i}.nBlocks()
+			for k = 1:gs{i}.nBlocks()
+				connections{startIndex(i)+j,startIndex(i)+k} = gs{i}.connections{j,k};
+			end
+		end
+	end
+
+	% Connections between grids
+	for i = 1:nGrids
+		for j = 1:nGrids
+			for k = 1:numel(gConnections{i,j})
+				b1 = gConnections{i,j}{k}{1};
+				id1 = b1{1};
+				str1 = b1{2};
+
+				b2 = gConnections{i,j}{k}{2};
+				id2 = b2{1};
+				str2 = b2{2};
+
+				connections{startIndex(i)+id1, startIndex(j)+id2} = {str1, str2};
+			end
+		end
+	end
+
+	% Boundary groups
+	boundaryGroups = struct;
+	for i = 1:nGrids
+		bgs = gs{i}.boundaryGroups;
+		bgNames = fieldnames(bgs);
+		for j = 1:numel(bgNames)
+			bg = bgs.(bgNames{j});
+
+			% Shift block id:s in boundary groups
+			for k = 1:length(bg)
+				bg{k}{1} = bg{k}{1} + startIndex(i);
+			end
+
+			bgs.(bgNames{j}) = bg;
+		end
+		boundaryGroups.(names{i}) = bgs;
+	end
+
+	% Create grid object
+	g = multiblock.Grid(grids, connections, boundaryGroups);
+
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+grid/accurateBoundaryOptimizedGrid.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,67 @@
+% Computes the grid points x and grid spacing h used by the boundary optimized SBP operators 
+% with improved boundary accuracy, presented in 
+% 'Boundary optimized diagonal-norm SBP operators - Mattsson, Almquist, van der Weide 2018'.
+%
+% lim - cell array with domain limits
+% N - Number of grid points
+% order - order of accuracy of sbp operator.
+function [x,h] = accurateBoundaryOptimizedGrid(lim,N,order)
+    assert(iscell(lim) && numel(lim) == 2,'The limit should be cell array with 2 elements.');
+    L = lim{2} - lim{1};
+    assert(L>0,'Limits must be given in increasing order.');
+    %%%% Non-equidistant grid points %%%%%
+    xb = boundaryPoints(order);
+    m = length(xb)-1; % Number of non-equidistant points
+    assert(N-2*(m+1)>=0,'Not enough grid points to contain the boundary region. Requires at least %d points.',2*(m+1));
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Compute h %%%%%%%%%%
+    h = L/(2*xb(end) + N-1-2*m);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Define grid %%%%%%%%
+    x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
+    x = x + lim{1};
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+end
+function xb = boundaryPoints(order)
+    switch order
+        case 4
+            x0 =  0.0000000000000e+00;
+            x1 =  6.8764546205559e-01;
+            x2 =  1.8022115125776e+00;
+            xb = [x0 x1 x2]';
+        case 6
+            x0 =  0.0000000000000e+00;
+            x1 =  4.4090263368623e-01;
+            x2 =  1.2855984345073e+00;
+            x3 =  2.2638953951239e+00;
+            xb = [x0 x1 x2 x3]';
+        case 8
+            x0 =  0.0000000000000e+00;
+            x1 =  3.8118550247622e-01;
+            x2 =  1.1899550868338e+00;
+            x3 =  2.2476300175641e+00;
+            x4 =  3.3192851303204e+00;
+            xb = [x0 x1 x2 x3 x4]';
+        case 10
+            x0 =  0.0000000000000e+00;
+            x1 =  3.5902433622052e-01;
+            x2 =  1.1436659188355e+00;
+            x3 =  2.2144895894456e+00;
+            x4 =  3.3682742337736e+00;
+            x5 =  4.4309689056870e+00;
+            xb = [x0 x1 x2 x3 x4 x5]';
+        case 12
+            x0 =  0.0000000000000e+00;
+            x1 =  3.6098032343909e-01;
+            x2 =  1.1634317168086e+00;
+            x3 =  2.2975905356987e+00;
+            x4 =  3.6057529790929e+00;
+            x5 =  4.8918275675510e+00;
+            x6 =  6.0000000000000e+00;
+            xb = [x0 x1 x2 x3 x4 x5 x6]';
+        otherwise
+            error('Invalid operator order %d.',order);
+    end
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+grid/minimalBoundaryOptimizedGrid.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,60 @@
+% Computes the grid points x and grid spacing h used by the boundary optimized SBP operators 
+% with minimal number of non-equidistant boundary points, presented in 
+% 'Boundary optimized diagonal-norm SBP operators - Mattsson, Almquist, van der Weide 2018'.
+%
+% lim - cell array with domain limits
+% N - Number of grid points
+% order - order of accuracy of sbp operator.
+function [x,h] = minimalBoundaryOptimizedGrid(lim,N,order)
+	assert(iscell(lim) && numel(lim) == 2,'The limit should be a cell array with 2 elements.');
+	L = lim{2} - lim{1};
+    assert(L>0,'Limits must be given in increasing order.');
+    %%%% Non-equidistant grid points %%%%%
+    xb = boundaryPoints(order);
+    m = length(xb)-1; % Number of non-equidistant points
+    assert(N-2*(m+1)>=0,'Not enough grid points to contain the boundary region. Requires at least %d points.',2*(m+1));
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Compute h %%%%%%%%%%
+    h = L/(2*xb(end) + N-1-2*m);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Define grid %%%%%%%%
+    x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
+    x = x + lim{1};
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+end
+
+function xb = boundaryPoints(order)
+	switch order
+		case 4
+			x0 =  0.0000000000000e+00;
+			x1 =  7.7122987842562e-01;
+			xb = [x0 x1]';
+		case 6
+			x0 =  0.0000000000000e+00;
+			x1 =  4.0842950991998e-01;
+			x2 =  1.1968523189207e+00;
+			xb = [x0 x1 x2]';
+		case 8
+			x0 =  0.0000000000000e+00;
+			x1 =  4.9439570885261e-01;
+			x2 =  1.4051531374839e+00;
+			xb = [x0 x1 x2]';
+		case 10
+			x0 =  0.0000000000000e+00;
+			x1 =  5.8556160757529e-01;
+			x2 =  1.7473267488572e+00;
+			x3 =  3.0000000000000e+00;
+			xb = [x0 x1 x2 x3]';
+		case 12
+			x0 =  0.0000000000000e+00;
+			x1 =  4.6552112904489e-01;
+			x2 =  1.4647984306493e+00;
+			x3 =  2.7620429464763e+00;
+			x4 =  4.0000000000000e+00;
+			xb = [x0 x1 x2 x3 x4]';
+		otherwise
+			error('Invalid operator order %d.',order);
+	end
+end
\ No newline at end of file
--- a/+sbp/+implementations/d1_noneq_10.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_10.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,48 +1,12 @@
-function [D1,H,x,h] = d1_noneq_10(N,L)
+function [D1,H] = d1_noneq_10(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<20)
     error('Operator requires at least 20 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 10;
-m = 5;
-order = 10;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  3.5902433622052e-01;
-x2 =  1.1436659188355e+00;
-x3 =  2.2144895894456e+00;
-x4 =  3.3682742337736e+00;
-x5 =  4.4309689056870e+00;
-x6 =  5.4309689056870e+00;
-x7 =  6.4309689056870e+00;
-x8 =  7.4309689056870e+00;
-x9 =  8.4309689056870e+00;
-x10 =  9.4309689056870e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -69,22 +33,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 10;
+d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_12.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_12.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,50 +1,12 @@
-function [D1,H,x,h] = d1_noneq_12(N,L)
+function [D1,H] = d1_noneq_12(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<24)
     error('Operator requires at least 24 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 12;
-m = 6;
-order = 12;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  3.6098032343909e-01;
-x2 =  1.1634317168086e+00;
-x3 =  2.2975905356987e+00;
-x4 =  3.6057529790929e+00;
-x5 =  4.8918275675510e+00;
-x6 =  6.0000000000000e+00;
-x7 =  7.0000000000000e+00;
-x8 =  8.0000000000000e+00;
-x9 =  9.0000000000000e+00;
-x10 =  1.0000000000000e+01;
-x11 =  1.1000000000000e+01;
-x12 =  1.2000000000000e+01;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -73,22 +35,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 12;
+d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_4.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_4.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,42 +1,12 @@
-function [D1,H,x,h] = d1_noneq_4(N,L)
+function [D1,H] = d1_noneq_4(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<8)
     error('Operator requires at least 8 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 4;
-m = 2;
-order = 4;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  6.8764546205559e-01;
-x2 =  1.8022115125776e+00;
-x3 =  2.8022115125776e+00;
-x4 =  3.8022115125776e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -57,22 +27,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 4;
+d = [1/12,-2/3,0,2/3,-1/12];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_6.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_6.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,44 +1,12 @@
-function [D1,H,x,h] = d1_noneq_6(N,L)
+function [D1,H] = d1_noneq_6(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<12)
     error('Operator requires at least 12 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 6;
-m = 3;
-order = 6;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  4.4090263368623e-01;
-x2 =  1.2855984345073e+00;
-x3 =  2.2638953951239e+00;
-x4 =  3.2638953951239e+00;
-x5 =  4.2638953951239e+00;
-x6 =  5.2638953951239e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -61,22 +29,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 6;
+d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_8.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_8.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,46 +1,12 @@
-function [D1,H,x,h] = d1_noneq_8(N,L)
+function [D1,H] = d1_noneq_8(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<16)
     error('Operator requires at least 16 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 8;
-m = 4;
-order = 8;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  3.8118550247622e-01;
-x2 =  1.1899550868338e+00;
-x3 =  2.2476300175641e+00;
-x4 =  3.3192851303204e+00;
-x5 =  4.3192851303204e+00;
-x6 =  5.3192851303204e+00;
-x7 =  6.3192851303204e+00;
-x8 =  7.3192851303204e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -65,22 +31,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 8;
+d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_minimal_10.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_minimal_10.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,46 +1,12 @@
-function [D1,H,x,h] = d1_noneq_minimal_10(N,L)
+function [D1,H] = d1_noneq_minimal_10(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<16)
     error('Operator requires at least 16 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 8;
-m = 3;
-order = 10;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  5.8556160757529e-01;
-x2 =  1.7473267488572e+00;
-x3 =  3.0000000000000e+00;
-x4 =  4.0000000000000e+00;
-x5 =  5.0000000000000e+00;
-x6 =  6.0000000000000e+00;
-x7 =  7.0000000000000e+00;
-x8 =  8.0000000000000e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -65,22 +31,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 10;
+d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_minimal_12.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_minimal_12.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,48 +1,12 @@
-function [D1,H,x,h] = d1_noneq_minimal_12(N,L)
+function [D1,H] = d1_noneq_minimal_12(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<20)
     error('Operator requires at least 20 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 10;
-m = 4;
-order = 12;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  4.6552112904489e-01;
-x2 =  1.4647984306493e+00;
-x3 =  2.7620429464763e+00;
-x4 =  4.0000000000000e+00;
-x5 =  5.0000000000000e+00;
-x6 =  6.0000000000000e+00;
-x7 =  7.0000000000000e+00;
-x8 =  8.0000000000000e+00;
-x9 =  9.0000000000000e+00;
-x10 =  1.0000000000000e+01;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -69,22 +33,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 12;
+d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_minimal_4.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_minimal_4.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,41 +1,12 @@
-function [D1,H,x,h] = d1_noneq_minimal_4(N,L)
+function [D1,H] = d1_noneq_minimal_4(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<6)
     error('Operator requires at least 6 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 3;
-m = 1;
-order = 4;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  7.7122987842562e-01;
-x2 =  1.7712298784256e+00;
-x3 =  2.7712298784256e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -55,22 +26,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 4;
+d = [1/12,-2/3,0,2/3,-1/12];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_minimal_6.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_minimal_6.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,43 +1,12 @@
-function [D1,H,x,h] = d1_noneq_minimal_6(N,L)
+function [D1,H] = d1_noneq_minimal_6(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<10)
     error('Operator requires at least 10 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 5;
-m = 2;
-order = 6;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  4.0842950991998e-01;
-x2 =  1.1968523189207e+00;
-x3 =  2.1968523189207e+00;
-x4 =  3.1968523189207e+00;
-x5 =  4.1968523189207e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -59,22 +28,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 6;
+d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- a/+sbp/+implementations/d1_noneq_minimal_8.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/+implementations/d1_noneq_minimal_8.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,44 +1,12 @@
-function [D1,H,x,h] = d1_noneq_minimal_8(N,L)
+function [D1,H] = d1_noneq_minimal_8(N,h)
 
-% L: Domain length
 % N: Number of grid points
-if(nargin < 2)
-    L = 1;
-end
-
 if(N<12)
     error('Operator requires at least 12 grid points');
 end
 
 % BP: Number of boundary points
-% m:  Number of nonequidistant spacings
-% order: Accuracy of interior stencil
 BP = 6;
-m = 2;
-order = 8;
-
-%%%% Non-equidistant grid points %%%%%
-x0 =  0.0000000000000e+00;
-x1 =  4.9439570885261e-01;
-x2 =  1.4051531374839e+00;
-x3 =  2.4051531374839e+00;
-x4 =  3.4051531374839e+00;
-x5 =  4.4051531374839e+00;
-x6 =  5.4051531374839e+00;
-
-xb = sparse(m+1,1);
-for i = 0:m
-    xb(i+1) = eval(['x' num2str(i)]);
-end
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Compute h %%%%%%%%%%
-h = L/(2*xb(end) + N-1-2*m);
-%%%%%%%%%%%%%%%%%%%%%%%%%
-
-%%%% Define grid %%%%%%%%
-x = h*[xb; linspace(xb(end)+1,L/h-xb(end)-1,N-2*(m+1))'; L/h-flip(xb) ];
-%%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Norm matrix %%%%%%%%
 P = sparse(BP,1);
@@ -61,22 +29,9 @@
 %%%%%%%%%%%%%%%%%%%%%%%%%
 
 %%%% Q matrix %%%%%%%%%%%
-
 % interior stencil
-switch order
-    case 2
-        d = [-1/2,0,1/2];
-    case 4
-        d = [1/12,-2/3,0,2/3,-1/12];
-    case 6
-        d = [-1/60,3/20,-3/4,0,3/4,-3/20,1/60];
-    case 8
-        d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
-    case 10
-        d = [-1/1260,5/504,-5/84,5/21,-5/6,0,5/6,-5/21,5/84,-5/504,1/1260];
-    case 12
-        d = [1/5544,-1/385,1/56,-5/63,15/56,-6/7,0,6/7,-15/56,5/63,-1/56,1/385,-1/5544];
-end
+order = 8;
+d = [1/280,-4/105,1/5,-4/5,0,4/5,-1/5,4/105,-1/280];
 d = repmat(d,N,1);
 Q = spdiags(d,-order/2:order/2,N,N);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+implementations/d2_noneq_variable_10.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,323 @@
+function [H, HI, D1, D2, DI] = d2_noneq_variable_10(N, h, options)
+    % N: Number of grid points
+    % h: grid spacing
+    % options: struct containing options for constructing the operator
+    %          current options are: 
+    %               options.stencil_type ('minimal','nonminimal','wide')
+    %               options.AD ('upwind', 'op')
+
+    % BP: Number of boundary points
+    % order: Accuracy of interior stencil
+    BP = 10;
+    order = 10;
+    if(N<2*BP)
+        error(['Operator requires at least ' num2str(2*BP) ' grid points']);
+    end
+
+    %%%% Norm matrix %%%%%%%%
+    P = zeros(BP, 1);
+    P0 = 1.0000000000000e-01;
+    P1 = 5.8980851260667e-01;
+    P2 = 9.5666820955973e-01;
+    P3 = 1.1500297411596e+00;
+    P4 = 1.1232986993248e+00;
+    P5 = 1.0123020150951e+00;
+    P6 = 9.9877122702527e-01;
+    P7 = 1.0000873322761e+00;
+    P8 = 1.0000045540888e+00;
+    P9 = 9.9999861455083e-01;
+
+    for i = 0:BP - 1
+        P(i + 1) = eval(['P' num2str(i)]);
+    end
+
+    Hv = ones(N, 1);
+    Hv(1:BP) = P;
+    Hv(end - BP + 1:end) = flip(P);
+    Hv = h * Hv;
+    H = spdiags(Hv, 0, N, N);
+    HI = spdiags(1 ./ Hv, 0, N, N);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Q matrix %%%%%%%%%%%
+
+    % interior stencil
+    d = [-1/1260, 5/504, -5/84, 5/21, -5/6, 0, 5/6, -5/21, 5/84, -5/504, 1/1260];
+    d = repmat(d, N, 1);
+    Q = spdiags(d, -order / 2:order / 2, N, N);
+
+    % Boundaries
+    Q0_0 = -5.0000000000000e-01;
+    Q0_1 = 6.7548747038002e-01;
+    Q0_2 = -2.6691978151546e-01;
+    Q0_3 = 1.4438714982130e-01;
+    Q0_4 = -7.7273673750760e-02;
+    Q0_5 = 2.5570078343005e-02;
+    Q0_6 = 4.2808774693299e-03;
+    Q0_7 = -8.2902108933389e-03;
+    Q0_8 = 3.2031176427908e-03;
+    Q0_9 = -4.4502749689556e-04;
+    Q0_10 = 0.0000000000000e+00;
+    Q0_11 = 0.0000000000000e+00;
+    Q0_12 = 0.0000000000000e+00;
+    Q0_13 = 0.0000000000000e+00;
+    Q0_14 = 0.0000000000000e+00;
+    Q1_0 = -6.7548747038002e-01;
+    Q1_1 = 0.0000000000000e+00;
+    Q1_2 = 9.5146052715180e-01;
+    Q1_3 = -4.2442349882626e-01;
+    Q1_4 = 2.1538865145190e-01;
+    Q1_5 = -7.1939778160350e-02;
+    Q1_6 = -8.2539187832840e-03;
+    Q1_7 = 1.9930661669090e-02;
+    Q1_8 = -7.7433256989613e-03;
+    Q1_9 = 1.0681515760869e-03;
+    Q1_10 = 0.0000000000000e+00;
+    Q1_11 = 0.0000000000000e+00;
+    Q1_12 = 0.0000000000000e+00;
+    Q1_13 = 0.0000000000000e+00;
+    Q1_14 = 0.0000000000000e+00;
+    Q2_0 = 2.6691978151546e-01;
+    Q2_1 = -9.5146052715180e-01;
+    Q2_2 = 0.0000000000000e+00;
+    Q2_3 = 9.6073770842387e-01;
+    Q2_4 = -3.9378595264609e-01;
+    Q2_5 = 1.3302097358959e-01;
+    Q2_6 = 8.1200458151489e-05;
+    Q2_7 = -2.3849770528789e-02;
+    Q2_8 = 9.6600442856829e-03;
+    Q2_9 = -1.3234579460680e-03;
+    Q2_10 = 0.0000000000000e+00;
+    Q2_11 = 0.0000000000000e+00;
+    Q2_12 = 0.0000000000000e+00;
+    Q2_13 = 0.0000000000000e+00;
+    Q2_14 = 0.0000000000000e+00;
+    Q3_0 = -1.4438714982130e-01;
+    Q3_1 = 4.2442349882626e-01;
+    Q3_2 = -9.6073770842387e-01;
+    Q3_3 = 0.0000000000000e+00;
+    Q3_4 = 9.1551097634196e-01;
+    Q3_5 = -2.8541713079648e-01;
+    Q3_6 = 4.1398809121293e-02;
+    Q3_7 = 1.7256059167927e-02;
+    Q3_8 = -9.4349194803610e-03;
+    Q3_9 = 1.3875650645663e-03;
+    Q3_10 = 0.0000000000000e+00;
+    Q3_11 = 0.0000000000000e+00;
+    Q3_12 = 0.0000000000000e+00;
+    Q3_13 = 0.0000000000000e+00;
+    Q3_14 = 0.0000000000000e+00;
+    Q4_0 = 7.7273673750760e-02;
+    Q4_1 = -2.1538865145190e-01;
+    Q4_2 = 3.9378595264609e-01;
+    Q4_3 = -9.1551097634196e-01;
+    Q4_4 = 0.0000000000000e+00;
+    Q4_5 = 8.3519401865051e-01;
+    Q4_6 = -2.0586492924974e-01;
+    Q4_7 = 3.1230261235901e-02;
+    Q4_8 = -2.0969453466651e-04;
+    Q4_9 = -5.0965470499782e-04;
+    Q4_10 = 0.0000000000000e+00;
+    Q4_11 = 0.0000000000000e+00;
+    Q4_12 = 0.0000000000000e+00;
+    Q4_13 = 0.0000000000000e+00;
+    Q4_14 = 0.0000000000000e+00;
+    Q5_0 = -2.5570078343005e-02;
+    Q5_1 = 7.1939778160350e-02;
+    Q5_2 = -1.3302097358959e-01;
+    Q5_3 = 2.8541713079648e-01;
+    Q5_4 = -8.3519401865051e-01;
+    Q5_5 = 0.0000000000000e+00;
+    Q5_6 = 8.1046389580138e-01;
+    Q5_7 = -2.1879194972141e-01;
+    Q5_8 = 5.2977237804899e-02;
+    Q5_9 = -9.0146730522360e-03;
+    Q5_10 = 7.9365079365079e-04;
+    Q5_11 = 0.0000000000000e+00;
+    Q5_12 = 0.0000000000000e+00;
+    Q5_13 = 0.0000000000000e+00;
+    Q5_14 = 0.0000000000000e+00;
+    Q6_0 = -4.2808774693299e-03;
+    Q6_1 = 8.2539187832840e-03;
+    Q6_2 = -8.1200458151489e-05;
+    Q6_3 = -4.1398809121293e-02;
+    Q6_4 = 2.0586492924974e-01;
+    Q6_5 = -8.1046389580138e-01;
+    Q6_6 = 0.0000000000000e+00;
+    Q6_7 = 8.2787884456005e-01;
+    Q6_8 = -2.3582460382545e-01;
+    Q6_9 = 5.9178678209520e-02;
+    Q6_10 = -9.9206349206349e-03;
+    Q6_11 = 7.9365079365079e-04;
+    Q6_12 = 0.0000000000000e+00;
+    Q6_13 = 0.0000000000000e+00;
+    Q6_14 = 0.0000000000000e+00;
+    Q7_0 = 8.2902108933389e-03;
+    Q7_1 = -1.9930661669090e-02;
+    Q7_2 = 2.3849770528789e-02;
+    Q7_3 = -1.7256059167927e-02;
+    Q7_4 = -3.1230261235901e-02;
+    Q7_5 = 2.1879194972141e-01;
+    Q7_6 = -8.2787884456005e-01;
+    Q7_7 = 0.0000000000000e+00;
+    Q7_8 = 8.3301028859275e-01;
+    Q7_9 = -2.3804321850015e-01;
+    Q7_10 = 5.9523809523809e-02;
+    Q7_11 = -9.9206349206349e-03;
+    Q7_12 = 7.9365079365079e-04;
+    Q7_13 = 0.0000000000000e+00;
+    Q7_14 = 0.0000000000000e+00;
+    Q8_0 = -3.2031176427908e-03;
+    Q8_1 = 7.7433256989613e-03;
+    Q8_2 = -9.6600442856829e-03;
+    Q8_3 = 9.4349194803610e-03;
+    Q8_4 = 2.0969453466651e-04;
+    Q8_5 = -5.2977237804899e-02;
+    Q8_6 = 2.3582460382545e-01;
+    Q8_7 = -8.3301028859275e-01;
+    Q8_8 = 0.0000000000000e+00;
+    Q8_9 = 8.3333655748509e-01;
+    Q8_10 = -2.3809523809524e-01;
+    Q8_11 = 5.9523809523809e-02;
+    Q8_12 = -9.9206349206349e-03;
+    Q8_13 = 7.9365079365079e-04;
+    Q8_14 = 0.0000000000000e+00;
+    Q9_0 = 4.4502749689556e-04;
+    Q9_1 = -1.0681515760869e-03;
+    Q9_2 = 1.3234579460680e-03;
+    Q9_3 = -1.3875650645663e-03;
+    Q9_4 = 5.0965470499782e-04;
+    Q9_5 = 9.0146730522360e-03;
+    Q9_6 = -5.9178678209520e-02;
+    Q9_7 = 2.3804321850015e-01;
+    Q9_8 = -8.3333655748509e-01;
+    Q9_9 = 0.0000000000000e+00;
+    Q9_10 = 8.3333333333333e-01;
+    Q9_11 = -2.3809523809524e-01;
+    Q9_12 = 5.9523809523809e-02;
+    Q9_13 = -9.9206349206349e-03;
+    Q9_14 = 7.9365079365079e-04;
+
+    for i = 1:BP
+
+        for j = 1:BP
+            Q(i, j) = eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+            Q(N + 1 - i, N + 1 - j) = -eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+        end
+
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%% Undivided difference operators %%%%
+    % Closed with zeros at the first boundary nodes.
+    m = N;
+
+    DD_5 = (-diag(ones(m - 3, 1), -3) + 5 * diag(ones(m - 2, 1), -2) - 10 * diag(ones(m - 1, 1), -1) + 10 * diag(ones(m, 1), 0) - 5 * diag(ones(m - 1, 1), 1) + diag(ones(m - 2, 1), 2));
+    DD_5(1:8, 1:10) = [0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; -0.88425676349344827581e1 0.18735837038089672208e2 -0.17076524806228533350e2 0.10664693462778093925e2 -0.43403898038449865885e1 0.85895174414023656383e0 0 0 0 0; 0 -0.13262411219221127021e1 0.45552739083119183335e1 -0.73424543813164879330e1 0.70876331528332015948e1 -0.38059884697709954324e1 0.83177691186447613913e0 0 0 0; 0 0 -0.67600766166626540709e0 0.32310531977344450754e1 -0.69639522132755183916e1 0.77488870404680024829e1 -0.42187263911386203939e1 0.87874602787795663432e0 0 0; 0 0 0 -0.66326118352702166971e0 0.38132489024062224761e1 -0.84909798376351149047e1 0.90434791287189283383e1 -0.46461964978830115989e1 0.94370948791999735890e0 0; 0 0 0 0 -0.86903681018048694197e0 0.47050202961852482685e1 -0.96960545214617434798e1 0.97952957162584740145e1 -0.49228410242754295559e1 0.98761634347393769455e0; ];
+    DD_5(m - 6:m, m - 9:m) = [-0.98761634347393769455e0 0.49228410242754295559e1 -0.97952957162584740145e1 0.96960545214617434798e1 -0.47050202961852482685e1 0.86903681018048694197e0 0 0 0 0; 0 -0.94370948791999735890e0 0.46461964978830115989e1 -0.90434791287189283383e1 0.84909798376351149047e1 -0.38132489024062224761e1 0.66326118352702166971e0 0 0 0; 0 0 -0.87874602787795663432e0 0.42187263911386203939e1 -0.77488870404680024829e1 0.69639522132755183916e1 -0.32310531977344450754e1 0.67600766166626540709e0 0 0; 0 0 0 -0.83177691186447613913e0 0.38059884697709954324e1 -0.70876331528332015948e1 0.73424543813164879330e1 -0.45552739083119183335e1 0.13262411219221127021e1 0; 0 0 0 0 -0.85895174414023656383e0 0.43403898038449865885e1 -0.10664693462778093925e2 0.17076524806228533350e2 -0.18735837038089672208e2 0.88425676349344827581e1; 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; ];
+    DD_5 = sparse(DD_5);
+
+    DD_6 = (diag(ones(m - 3, 1), 3) - 6 * diag(ones(m - 2, 1), 2) + 15 * diag(ones(m - 1, 1), 1) - 20 * diag(ones(m, 1), 0) + 15 * diag(ones(m - 1, 1), -1) - 6 * diag(ones(m - 2, 1), -2) + diag(ones(m - 3, 1), -3));
+    DD_6(1:8, 1:11) = [0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0.97690498198305661286e1 -0.22164087301995739032e2 0.23898275711233304083e2 -0.19893851160044639280e2 0.12625396854743289626e2 -0.51537104648414193830e1 0.91892654107463785790e0 0 0 0 0; 0 0.13105269062480722931e1 -0.51692977530964314903e1 0.10448225399376470222e2 -0.13885092532071720368e2 0.11417965409312986297e2 -0.49906614711868568348e1 0.86833404141747988109e0 0 0 0; 0 0 0.64511698871199831925e0 -0.37163607887886698482e1 0.10284728893981919286e2 -0.15497774080936004966e2 0.12656179173415861182e2 -0.52724761672677398059e1 0.90058598088263583338e0 0 0; 0 0 0 0.64016413450696574250e0 -0.45192323253005276829e1 0.12736469756452672357e2 -0.18086958257437856677e2 0.13938589493649034797e2 -0.56622569275199841534e1 0.95322412564969561677e0 0; 0 0 0 0 0.86005005088560527649e0 -0.56460243554222979222e1 0.14544081782192615220e2 -0.19590591432516948029e2 0.14768523072826288668e2 -0.59256980608436261673e1 0.98965894287836295484e0; ];
+    DD_6(m - 7:m, m - 10:m) = [0.98965894287836295484e0 -0.59256980608436261673e1 0.14768523072826288668e2 -0.19590591432516948029e2 0.14544081782192615220e2 -0.56460243554222979222e1 0.86005005088560527649e0 0 0 0 0; 0 0.95322412564969561677e0 -0.56622569275199841534e1 0.13938589493649034797e2 -0.18086958257437856677e2 0.12736469756452672357e2 -0.45192323253005276829e1 0.64016413450696574250e0 0 0 0; 0 0 0.90058598088263583338e0 -0.52724761672677398059e1 0.12656179173415861182e2 -0.15497774080936004966e2 0.10284728893981919286e2 -0.37163607887886698482e1 0.64511698871199831925e0 0 0; 0 0 0 0.86833404141747988109e0 -0.49906614711868568348e1 0.11417965409312986297e2 -0.13885092532071720368e2 0.10448225399376470222e2 -0.51692977530964314903e1 0.13105269062480722931e1 0; 0 0 0 0 0.91892654107463785790e0 -0.51537104648414193830e1 0.12625396854743289626e2 -0.19893851160044639280e2 0.23898275711233304083e2 -0.22164087301995739032e2 0.97690498198305661286e1; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_6 = sparse(DD_6);
+
+    DD_7 = (-diag(ones(m - 4, 1), -4) + 7 * diag(ones(m - 3, 1), -3) - 21 * diag(ones(m - 2, 1), -2) + 35 * diag(ones(m - 1, 1), -1) - 35 * diag(ones(m, 1), 0) + 21 * diag(ones(m - 1, 1), 1) - 7 * diag(ones(m - 2, 1), 2) + diag(ones(m - 3, 1), 3));
+    DD_7(1:9, 1:12) = [0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; -0.10633444157744515342e2 0.25551717302255079868e2 -0.31639558087487298930e2 0.33026832975062979647e2 -0.28856215669710369771e2 0.18037986626944967840e2 -0.64324857875224650053e1 0.94516679820162169311e0 0 0 0 0; 0 -0.12971946051930944772e1 0.57552633215463132945e1 -0.14020486493241325087e2 0.23923936099960072962e2 -0.26641919288396968027e2 0.17467315149153998922e2 -0.60783382899223591677e1 0.89142410609336157976e0 0 0 0; 0 0 -0.61968315701047317846e0 0.41847682905586435755e1 -0.14220312881453249947e2 0.27121104641638008690e2 -0.29531084737970342758e2 0.18453666585437089321e2 -0.63041018661784508337e1 0.91564312497877513070e0 0 0; 0 0 0 -0.62096054671195295664e0 0.52179151332917540372e1 -0.17831057659033741300e2 0.31652176950516249184e2 -0.32523375485181081192e2 0.19817899246319944537e2 -0.66725688795478693174e1 0.95997124034669700791e0 0; 0 0 0 0 -0.85241549236735026150e0 0.65870284146593475759e1 -0.20361714495069661308e2 0.34283535006904659051e2 -0.34459887169928006891e2 0.20739943212952691585e2 -0.69276126001485406839e1 0.99112312299686093167e0; ];
+    DD_7(m - 7:m, m - 11:m) = [-0.99112312299686093167e0 0.69276126001485406839e1 -0.20739943212952691585e2 0.34459887169928006891e2 -0.34283535006904659051e2 0.20361714495069661308e2 -0.65870284146593475759e1 0.85241549236735026150e0 0 0 0 0; 0 -0.95997124034669700791e0 0.66725688795478693174e1 -0.19817899246319944537e2 0.32523375485181081192e2 -0.31652176950516249184e2 0.17831057659033741300e2 -0.52179151332917540372e1 0.62096054671195295664e0 0 0 0; 0 0 -0.91564312497877513070e0 0.63041018661784508337e1 -0.18453666585437089321e2 0.29531084737970342758e2 -0.27121104641638008690e2 0.14220312881453249947e2 -0.41847682905586435755e1 0.61968315701047317846e0 0 0; 0 0 0 -0.89142410609336157976e0 0.60783382899223591677e1 -0.17467315149153998922e2 0.26641919288396968027e2 -0.23923936099960072962e2 0.14020486493241325087e2 -0.57552633215463132945e1 0.12971946051930944772e1 0; 0 0 0 0 -0.94516679820162169311e0 0.64324857875224650053e1 -0.18037986626944967840e2 0.28856215669710369771e2 -0.33026832975062979647e2 0.31639558087487298930e2 -0.25551717302255079868e2 0.10633444157744515342e2; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_7 = sparse(DD_7);
+
+    DD_8 = (diag(ones(m - 4, 1), 4) - 8 * diag(ones(m - 3, 1), 3) + 28 * diag(ones(m - 2, 1), 2) - 56 * diag(ones(m - 1, 1), 1) + 70 * diag(ones(m, 1), 0) - 56 * diag(ones(m - 1, 1), -1) + 28 * diag(ones(m - 2, 1), -2) - 8 * diag(ones(m - 3, 1), -3) + diag(ones(m - 4, 1), -4));
+    DD_8(1:9, 1:13) = [0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0.11447706798618566234e2 -0.28904884139025747734e2 0.40258353260409964223e2 -0.50649997399180126990e2 0.56821824921675878869e2 -0.48101297671853247575e2 0.25729943150089860021e2 -0.75613343856129735449e1 0.95968546487782649665e0 0 0 0 0; 0 0.12856328177474934636e1 -0.63181271116961104382e1 0.18042992864608611563e2 -0.37804272468074727719e2 0.53283838576793936053e2 -0.46579507064410663791e2 0.24313353159689436671e2 -0.71313928487468926381e1 0.90748207408891683663e0 0 0 0; 0 0 0.59820007352804876260e0 -0.46391245450012267365e1 0.18764346418211454409e2 -0.43393767426620813904e2 0.59062169475940685515e2 -0.49209777561165571522e2 0.25216407464713803335e2 -0.73251449998302010456e1 0.92669110022382118702e0 0 0; 0 0 0 0.60460011916248255841e0 -0.59103958199322344450e1 0.23774743545378321733e2 -0.50643483120825998695e2 0.65046750970362162385e2 -0.52847731323519852099e2 0.26690275518191477270e2 -0.76797699227735760633e1 0.96501003395721735560e0 0; 0 0 0 0 0.84578719850252712660e0 -0.75280324738963972296e1 0.27148952660092881743e2 -0.54853656011047454481e2 0.68919774339856013782e2 -0.55306515234540510895e2 0.27710450400594162736e2 -0.79289849839748874534e1 0.99222410441366467138e0; ];
+    DD_8(m - 8:m, m - 12:m) = [0.99222410441366467138e0 -0.79289849839748874534e1 0.27710450400594162736e2 -0.55306515234540510895e2 0.68919774339856013782e2 -0.54853656011047454481e2 0.27148952660092881743e2 -0.75280324738963972296e1 0.84578719850252712660e0 0 0 0 0; 0 0.96501003395721735560e0 -0.76797699227735760633e1 0.26690275518191477270e2 -0.52847731323519852099e2 0.65046750970362162385e2 -0.50643483120825998695e2 0.23774743545378321733e2 -0.59103958199322344450e1 0.60460011916248255841e0 0 0 0; 0 0 0.92669110022382118702e0 -0.73251449998302010456e1 0.25216407464713803335e2 -0.49209777561165571522e2 0.59062169475940685515e2 -0.43393767426620813904e2 0.18764346418211454409e2 -0.46391245450012267365e1 0.59820007352804876260e0 0 0; 0 0 0 0.90748207408891683663e0 -0.71313928487468926381e1 0.24313353159689436671e2 -0.46579507064410663791e2 0.53283838576793936053e2 -0.37804272468074727719e2 0.18042992864608611563e2 -0.63181271116961104382e1 0.12856328177474934636e1 0; 0 0 0 0 0.95968546487782649665e0 -0.75613343856129735449e1 0.25729943150089860021e2 -0.48101297671853247575e2 0.56821824921675878869e2 -0.50649997399180126990e2 0.40258353260409964223e2 -0.28904884139025747734e2 0.11447706798618566234e2; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_8 = sparse(DD_8);
+
+    DD_9 = (-diag(ones(m - 5, 1), -5) + 9 * diag(ones(m - 4, 1), -4) - 36 * diag(ones(m - 3, 1), -3) + 84 * diag(ones(m - 2, 1), -2) - 126 * diag(ones(m - 1, 1), -1) + 126 * diag(ones(m, 1), 0) - 84 * diag(ones(m - 1, 1), 1) + 36 * diag(ones(m - 2, 1), 2) - 9 * diag(ones(m - 3, 1), 3) + diag(ones(m - 4, 1), 4));
+    DD_9(1:10, 1:14) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; -0.12220346479758703402e2 0.32228164479135716142e2 -0.49720065159556828467e2 0.73329283892516285142e2 -0.10101269332559123572e3 0.10822791976166980704e3 -0.77189829450269580063e2 0.34026004735258380952e2 -0.86371691839004384699e1 0.96873073049659684460e0 0 0 0 0; 0 -0.12754371756934054072e1 0.68614776237194576426e1 -0.22502238094968228382e2 0.56120004490560654424e2 -0.95910909438229084895e2 0.10480389089492399353e3 -0.72940059479068310012e2 0.32091267819361016871e2 -0.81673386668002515297e1 0.91934202619415775756e0 0 0 0; 0 0 -0.57969473693003825890e0 0.50815098898235167347e1 -0.23911428372444897689e2 0.65090651139931220857e2 -0.10631190505669323393e3 0.11072199951262253592e3 -0.75649222394141410004e2 0.32963152499235904705e2 -0.83402199020143906832e1 0.93515742061079234147e0 0 0; 0 0 0 -0.59039909771982400264e0 0.65974918490578446834e1 -0.30567527415486413657e2 0.75965224681238998042e2 -0.11708415174665189229e3 0.11890739547791966722e3 -0.80070826554574431809e2 0.34558964652481092285e2 -0.86850903056149562004e1 0.96891845934991572940e0 0; 0 0 0 0 -0.83993614063969059233e0 0.84690365331334468834e1 -0.34905796277262276527e2 0.82280484016571181722e2 -0.12405559381174082481e3 0.12443965927771614951e3 -0.83131351201782488207e2 0.35680432427886993540e2 -0.89300169397229820424e1 0.99308211584049051802e0; ];
+    DD_9(m - 8:m, m - 13:m) = [-0.99308211584049051802e0 0.89300169397229820424e1 -0.35680432427886993540e2 0.83131351201782488207e2 -0.12443965927771614951e3 0.12405559381174082481e3 -0.82280484016571181722e2 0.34905796277262276527e2 -0.84690365331334468834e1 0.83993614063969059233e0 0 0 0 0; 0 -0.96891845934991572940e0 0.86850903056149562004e1 -0.34558964652481092285e2 0.80070826554574431809e2 -0.11890739547791966722e3 0.11708415174665189229e3 -0.75965224681238998042e2 0.30567527415486413657e2 -0.65974918490578446834e1 0.59039909771982400264e0 0 0 0; 0 0 -0.93515742061079234147e0 0.83402199020143906832e1 -0.32963152499235904705e2 0.75649222394141410004e2 -0.11072199951262253592e3 0.10631190505669323393e3 -0.65090651139931220857e2 0.23911428372444897689e2 -0.50815098898235167347e1 0.57969473693003825890e0 0 0; 0 0 0 -0.91934202619415775756e0 0.81673386668002515297e1 -0.32091267819361016871e2 0.72940059479068310012e2 -0.10480389089492399353e3 0.95910909438229084895e2 -0.56120004490560654424e2 0.22502238094968228382e2 -0.68614776237194576426e1 0.12754371756934054072e1 0; 0 0 0 0 -0.96873073049659684460e0 0.86371691839004384699e1 -0.34026004735258380952e2 0.77189829450269580063e2 -0.10822791976166980704e3 0.10101269332559123572e3 -0.73329283892516285142e2 0.49720065159556828467e2 -0.32228164479135716142e2 0.12220346479758703402e2; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_9 = sparse(DD_9);
+
+    DD_10 = (diag(ones(m - 5, 1), 5) - 10 * diag(ones(m - 4, 1), 4) + 45 * diag(ones(m - 3, 1), 3) - 120 * diag(ones(m - 2, 1), 2) + 210 * diag(ones(m - 1, 1), 1) - 252 * diag(ones(m, 1), 0) + 210 * diag(ones(m - 1, 1), -1) - 120 * diag(ones(m - 2, 1), -2) + 45 * diag(ones(m - 3, 1), -3) - 10 * diag(ones(m - 4, 1), -4) + diag(ones(m - 5, 1), -5));
+    DD_10(1:10, 1:15) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0.12957678688124699986e2 -0.35525089722887367966e2 0.59995471673283665865e2 -0.10161365491269611714e3 0.16661352548983481943e3 -0.21645583952333961409e3 0.19297457362567395016e3 -0.11342001578419460317e3 0.43185845919502192349e2 -0.96873073049659684460e1 0.97481185166434302666e0 0 0 0 0; 0 0.12663266431786644877e1 -0.73880195719183439765e1 0.27386715439651102593e2 -0.79459763018974764721e2 0.15985151573038180816e3 -0.20960778178984798706e3 0.18235014869767077503e3 -0.10697089273120338957e3 0.40836693334001257648e2 -0.91934202619415775756e1 0.92847752900245498778e0 0 0 0; 0 0 0.56350506801536116666e0 -0.55135043604652958562e1 0.29656869502625787574e2 -0.92986644485616029795e2 0.17718650842782205655e3 -0.22144399902524507185e3 0.18912305598535352501e3 -0.10987717499745301568e3 0.41701099510071953416e2 -0.93515742061079234147e1 0.94185858099865288757e0 0 0; 0 0 0 0.57788899624281661128e0 -0.72798346274475049971e1 0.38209409269358017071e2 -0.10852174954462714006e3 0.19514025291108648715e3 -0.23781479095583933444e3 0.20017706638643607952e3 -0.11519654884160364095e3 0.43425451528074781002e2 -0.96891845934991572940e1 0.97203947181859638306e0 0; 0 0 0 0 0.83470299758184640199e0 -0.94100405923704965371e1 0.43632245346577845659e2 -0.11754354859510168817e3 0.20675932301956804135e3 -0.24887931855543229903e3 0.20782837800445622052e3 -0.11893477475962331180e3 0.44650084698614910212e2 -0.99308211584049051802e1 0.99376959413383658153e0; ];
+    DD_10(m - 9:m, m - 14:m) = [0.99376959413383658153e0 -0.99308211584049051802e1 0.44650084698614910212e2 -0.11893477475962331180e3 0.20782837800445622052e3 -0.24887931855543229903e3 0.20675932301956804135e3 -0.11754354859510168817e3 0.43632245346577845659e2 -0.94100405923704965371e1 0.83470299758184640199e0 0 0 0 0; 0 0.97203947181859638306e0 -0.96891845934991572940e1 0.43425451528074781002e2 -0.11519654884160364095e3 0.20017706638643607952e3 -0.23781479095583933444e3 0.19514025291108648715e3 -0.10852174954462714006e3 0.38209409269358017071e2 -0.72798346274475049971e1 0.57788899624281661128e0 0 0 0; 0 0 0.94185858099865288757e0 -0.93515742061079234147e1 0.41701099510071953416e2 -0.10987717499745301568e3 0.18912305598535352501e3 -0.22144399902524507185e3 0.17718650842782205655e3 -0.92986644485616029795e2 0.29656869502625787574e2 -0.55135043604652958562e1 0.56350506801536116666e0 0 0; 0 0 0 0.92847752900245498778e0 -0.91934202619415775756e1 0.40836693334001257648e2 -0.10697089273120338957e3 0.18235014869767077503e3 -0.20960778178984798706e3 0.15985151573038180816e3 -0.79459763018974764721e2 0.27386715439651102593e2 -0.73880195719183439765e1 0.12663266431786644877e1 0; 0 0 0 0 0.97481185166434302666e0 -0.96873073049659684460e1 0.43185845919502192349e2 -0.11342001578419460317e3 0.19297457362567395016e3 -0.21645583952333961409e3 0.16661352548983481943e3 -0.10161365491269611714e3 0.59995471673283665865e2 -0.35525089722887367966e2 0.12957678688124699986e2; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_10 = sparse(DD_10);
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Difference operator %%
+    D1 = H \ Q;
+
+    % Helper functions for constructing D2(c)
+    % TODO: Consider changing sparse(diag(...)) to spdiags(....)
+
+    % Minimal 11 point stencil width
+    function D2 = D2_fun_minimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 2/7 * diag(ones(m - 1, 1), -1) + 2/7 * diag(ones(m - 1, 1), 1) + 3/7 * diag(ones(m, 1), 0); C3(1, 3) = 2/7; C3(m, m - 2) = 2/7;
+        C4 = 1/5 * diag(ones(m - 2, 1), -2) + 3/10 * diag(ones(m - 1, 1), -1) + 1/5 * diag(ones(m - 1, 1), 1) + 3/10 * diag(ones(m, 1), 0); C4(2, 4) = 1/5; C4(1, 3) = 1/5; C4(1, 4) = 3/10; C4(m, m - 2) = 1/5; C4(m, m - 3) = 1/5;
+        C5 = 1/5 * diag(ones(m - 2, 1), -2) + 1/5 * diag(ones(m - 2, 1), 2) + 1/5 * diag(ones(m - 1, 1), -1) + 1/5 * diag(ones(m - 1, 1), 1) + 1/5 * diag(ones(m, 1), 0); C5(2, 4) = 1/5; C5(1, 4) = 1/5; C5(1, 5) = 1/5; C5(2, 5) = 1/5; C5(m - 1, m - 4) = 1/5; C5(m, m - 4) = 1/5; C5(m, m - 3) = 1/5;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+        C4 = sparse(diag(C4 * c));
+        C5 = sparse(diag(C5 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/1587600 / h) * transpose(DD_10) * C1 * DD_10 + (1/317520 / h) * transpose(DD_9) * C2 * DD_9 + (1/60480 / h) * transpose(DD_8) * C3 * DD_8 + (1/10584 / h) * transpose(DD_7) * C4 * DD_7 + (1/1512 / h) * transpose(DD_6) * C5 * DD_6;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Few additional grid point in interior stencil cmp. to minimal
+    function D2 = D2_fun_nonminimal(c)
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 2/7 * diag(ones(m - 1, 1), -1) + 2/7 * diag(ones(m - 1, 1), 1) + 3/7 * diag(ones(m, 1), 0); C3(1, 3) = 2/7; C3(m, m - 2) = 2/7;
+        C4 = 1/5 * diag(ones(m - 2, 1), -2) + 3/10 * diag(ones(m - 1, 1), -1) + 1/5 * diag(ones(m - 1, 1), 1) + 3/10 * diag(ones(m, 1), 0); C4(2, 4) = 1/5; C4(1, 3) = 1/5; C4(1, 4) = 3/10; C4(m, m - 2) = 1/5; C4(m, m - 3) = 1/5;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+        C4 = sparse(diag(C4 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/1587600 / h) * transpose(DD_10) * C1 * DD_10 + (1/317520 / h) * transpose(DD_9) * C2 * DD_9 + (1/60480 / h) * transpose(DD_8) * C3 * DD_8 + (1/10584 / h) * transpose(DD_7) * C4 * DD_7;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Wide stencil
+    function D2 = D2_fun_wide(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        D2 = D1 * C1 * D1;
+    end
+
+    switch options.stencil_width
+        case 'minimal'
+            D2 = @D2_fun_minimal;
+        case 'nonminimal'
+            D2 = @D2_fun_nonminimal;
+        case 'wide'
+            D2 = @D2_fun_wide;
+        otherwise
+            error('No option %s for stencil width', options.stencil_width)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Artificial dissipation operator %%%
+    switch options.AD
+        case 'upwind'
+            % This is the choice that yield 9th order Upwind
+            DI = H \ (transpose(DD_5) * DD_5) * (-1/1260);
+        case 'op'
+            % This choice will preserve the order of the underlying
+            % Non-dissipative D1 SBP operator
+            DI = H \ (transpose(DD_6) * DD_6) * (-1 / (5 * 1260));
+            % Notice that you can use any negative number instead of (-1/(5*1260))
+        otherwise
+            error("Artificial dissipation options '%s' not implemented.", option.AD)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+implementations/d2_noneq_variable_12.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,404 @@
+function [H, HI, D1, D2, DI] = d2_noneq_variable_12(N, h, options)
+    % N: Number of grid points
+    % h: grid spacing
+    % options: struct containing options for constructing the operator
+    %          current options are: 
+    %               options.stencil_type ('minimal','nonminimal','wide')
+    %               options.AD ('upwind', 'op')
+
+    % BP: Number of boundary points
+    % order: Accuracy of interior stencil
+    BP = 12;
+    order = 12;
+    if(N<2*BP)
+        error(['Operator requires at least ' num2str(2*BP) ' grid points']);
+    end
+
+    %%%% Norm matrix %%%%%%%%
+    P = zeros(BP, 1);
+    P0 = 1.0000000000011e-01;
+    P1 = 5.9616216757547e-01;
+    P2 = 9.9065699844442e-01;
+    P3 = 1.2512548713913e+00;
+    P4 = 1.3316678989403e+00;
+    P5 = 1.2093375037721e+00;
+    P6 = 1.0236491595704e+00;
+    P7 = 9.9685258909811e-01;
+    P8 = 1.0004766563923e+00;
+    P9 = 9.9993617879146e-01;
+    P10 = 1.0000063122914e+00;
+    P11 = 9.9999966373260e-01;
+
+    for i = 0:BP - 1
+        P(i + 1) = eval(['P' num2str(i)]);
+    end
+
+    Hv = ones(N, 1);
+    Hv(1:BP) = P;
+    Hv(end - BP + 1:end) = flip(P);
+    Hv = h * Hv;
+    H = spdiags(Hv, 0, N, N);
+    HI = spdiags(1 ./ Hv, 0, N, N);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Q matrix %%%%%%%%%%%
+
+    % interior stencil
+    d = [1/5544, -1/385, 1/56, -5/63, 15/56, -6/7, 0, 6/7, -15/56, 5/63, -1/56, 1/385, -1/5544];
+    d = repmat(d, N, 1);
+    Q = spdiags(d, -order / 2:order / 2, N, N);
+
+    % Boundaries
+    Q0_0 = -5.0000000000000e-01;
+    Q0_1 = 6.7597560728423e-01;
+    Q0_2 = -2.6859785384416e-01;
+    Q0_3 = 1.4850302678903e-01;
+    Q0_4 = -8.7976689586154e-02;
+    Q0_5 = 4.1833336322613e-02;
+    Q0_6 = -2.2216684976993e-03;
+    Q0_7 = -1.5910034062022e-02;
+    Q0_8 = 1.1296706376589e-02;
+    Q0_9 = -3.1823678285130e-03;
+    Q0_10 = 2.4843594063649e-04;
+    Q0_11 = 3.1501105449828e-05;
+    Q0_12 = 0.0000000000000e+00;
+    Q0_13 = 0.0000000000000e+00;
+    Q0_14 = 0.0000000000000e+00;
+    Q0_15 = 0.0000000000000e+00;
+    Q0_16 = 0.0000000000000e+00;
+    Q0_17 = 0.0000000000000e+00;
+    Q1_0 = -6.7597560728423e-01;
+    Q1_1 = 0.0000000000000e+00;
+    Q1_2 = 9.5424013647146e-01;
+    Q1_3 = -4.3389334603464e-01;
+    Q1_4 = 2.4285669347653e-01;
+    Q1_5 = -1.1443465137214e-01;
+    Q1_6 = 8.5942765682435e-03;
+    Q1_7 = 4.0290424215772e-02;
+    Q1_8 = -2.9396383714543e-02;
+    Q1_9 = 8.5601827834256e-03;
+    Q1_10 = -7.8128092862319e-04;
+    Q1_11 = -6.0444181254875e-05;
+    Q1_12 = 0.0000000000000e+00;
+    Q1_13 = 0.0000000000000e+00;
+    Q1_14 = 0.0000000000000e+00;
+    Q1_15 = 0.0000000000000e+00;
+    Q1_16 = 0.0000000000000e+00;
+    Q1_17 = 0.0000000000000e+00;
+    Q2_0 = 2.6859785384416e-01;
+    Q2_1 = -9.5424013647146e-01;
+    Q2_2 = 0.0000000000000e+00;
+    Q2_3 = 9.7065114311923e-01;
+    Q2_4 = -4.3205328628292e-01;
+    Q2_5 = 1.9549970932735e-01;
+    Q2_6 = -2.4406885385172e-02;
+    Q2_7 = -5.5737279079895e-02;
+    Q2_8 = 4.3772338637753e-02;
+    Q2_9 = -1.3727655130726e-02;
+    Q2_10 = 1.6271304373071e-03;
+    Q2_11 = 1.7066984372933e-05;
+    Q2_12 = 0.0000000000000e+00;
+    Q2_13 = 0.0000000000000e+00;
+    Q2_14 = 0.0000000000000e+00;
+    Q2_15 = 0.0000000000000e+00;
+    Q2_16 = 0.0000000000000e+00;
+    Q2_17 = 0.0000000000000e+00;
+    Q3_0 = -1.4850302678903e-01;
+    Q3_1 = 4.3389334603464e-01;
+    Q3_2 = -9.7065114311923e-01;
+    Q3_3 = 0.0000000000000e+00;
+    Q3_4 = 9.5375878629204e-01;
+    Q3_5 = -3.6113954384951e-01;
+    Q3_6 = 6.9749289223875e-02;
+    Q3_7 = 6.5161366516465e-02;
+    Q3_8 = -6.0325702283960e-02;
+    Q3_9 = 2.1188913621662e-02;
+    Q3_10 = -3.2632650250470e-03;
+    Q3_11 = 1.3097937809499e-04;
+    Q3_12 = 0.0000000000000e+00;
+    Q3_13 = 0.0000000000000e+00;
+    Q3_14 = 0.0000000000000e+00;
+    Q3_15 = 0.0000000000000e+00;
+    Q3_16 = 0.0000000000000e+00;
+    Q3_17 = 0.0000000000000e+00;
+    Q4_0 = 8.7976689586154e-02;
+    Q4_1 = -2.4285669347653e-01;
+    Q4_2 = 4.3205328628292e-01;
+    Q4_3 = -9.5375878629204e-01;
+    Q4_4 = 0.0000000000000e+00;
+    Q4_5 = 8.8676146394834e-01;
+    Q4_6 = -2.1292503103800e-01;
+    Q4_7 = -4.6037018833218e-02;
+    Q4_8 = 7.4338719466734e-02;
+    Q4_9 = -3.1217656663809e-02;
+    Q4_10 = 6.1239492854797e-03;
+    Q4_11 = -4.5892226603067e-04;
+    Q4_12 = 0.0000000000000e+00;
+    Q4_13 = 0.0000000000000e+00;
+    Q4_14 = 0.0000000000000e+00;
+    Q4_15 = 0.0000000000000e+00;
+    Q4_16 = 0.0000000000000e+00;
+    Q4_17 = 0.0000000000000e+00;
+    Q5_0 = -4.1833336322613e-02;
+    Q5_1 = 1.1443465137214e-01;
+    Q5_2 = -1.9549970932735e-01;
+    Q5_3 = 3.6113954384951e-01;
+    Q5_4 = -8.8676146394834e-01;
+    Q5_5 = 0.0000000000000e+00;
+    Q5_6 = 7.7461223007026e-01;
+    Q5_7 = -1.0609547334165e-01;
+    Q5_8 = -4.4853791547749e-02;
+    Q5_9 = 3.2436468405486e-02;
+    Q5_10 = -8.4387621360184e-03;
+    Q5_11 = 8.5964292632428e-04;
+    Q5_12 = 0.0000000000000e+00;
+    Q5_13 = 0.0000000000000e+00;
+    Q5_14 = 0.0000000000000e+00;
+    Q5_15 = 0.0000000000000e+00;
+    Q5_16 = 0.0000000000000e+00;
+    Q5_17 = 0.0000000000000e+00;
+    Q6_0 = 2.2216684976993e-03;
+    Q6_1 = -8.5942765682435e-03;
+    Q6_2 = 2.4406885385172e-02;
+    Q6_3 = -6.9749289223875e-02;
+    Q6_4 = 2.1292503103800e-01;
+    Q6_5 = -7.7461223007026e-01;
+    Q6_6 = 0.0000000000000e+00;
+    Q6_7 = 7.4758103262966e-01;
+    Q6_8 = -1.5730779067906e-01;
+    Q6_9 = 2.6517620342970e-02;
+    Q6_10 = -4.3175367549700e-03;
+    Q6_11 = 1.1092605832824e-03;
+    Q6_12 = -1.8037518037522e-04;
+    Q6_13 = 0.0000000000000e+00;
+    Q6_14 = 0.0000000000000e+00;
+    Q6_15 = 0.0000000000000e+00;
+    Q6_16 = 0.0000000000000e+00;
+    Q6_17 = 0.0000000000000e+00;
+    Q7_0 = 1.5910034062022e-02;
+    Q7_1 = -4.0290424215772e-02;
+    Q7_2 = 5.5737279079895e-02;
+    Q7_3 = -6.5161366516465e-02;
+    Q7_4 = 4.6037018833218e-02;
+    Q7_5 = 1.0609547334165e-01;
+    Q7_6 = -7.4758103262966e-01;
+    Q7_7 = 0.0000000000000e+00;
+    Q7_8 = 8.0975719267918e-01;
+    Q7_9 = -2.3568822398349e-01;
+    Q7_10 = 6.9373143801571e-02;
+    Q7_11 = -1.6606121869177e-02;
+    Q7_12 = 2.5974025974031e-03;
+    Q7_13 = -1.8037518037522e-04;
+    Q7_14 = 0.0000000000000e+00;
+    Q7_15 = 0.0000000000000e+00;
+    Q7_16 = 0.0000000000000e+00;
+    Q7_17 = 0.0000000000000e+00;
+    Q8_0 = -1.1296706376589e-02;
+    Q8_1 = 2.9396383714543e-02;
+    Q8_2 = -4.3772338637753e-02;
+    Q8_3 = 6.0325702283960e-02;
+    Q8_4 = -7.4338719466734e-02;
+    Q8_5 = 4.4853791547749e-02;
+    Q8_6 = 1.5730779067906e-01;
+    Q8_7 = -8.0975719267918e-01;
+    Q8_8 = 0.0000000000000e+00;
+    Q8_9 = 8.4765775072084e-01;
+    Q8_10 = -2.6369594097148e-01;
+    Q8_11 = 7.8759594625702e-02;
+    Q8_12 = -1.7857142857146e-02;
+    Q8_13 = 2.5974025974031e-03;
+    Q8_14 = -1.8037518037522e-04;
+    Q8_15 = 0.0000000000000e+00;
+    Q8_16 = 0.0000000000000e+00;
+    Q8_17 = 0.0000000000000e+00;
+    Q9_0 = 3.1823678285130e-03;
+    Q9_1 = -8.5601827834256e-03;
+    Q9_2 = 1.3727655130726e-02;
+    Q9_3 = -2.1188913621662e-02;
+    Q9_4 = 3.1217656663809e-02;
+    Q9_5 = -3.2436468405486e-02;
+    Q9_6 = -2.6517620342970e-02;
+    Q9_7 = 2.3568822398349e-01;
+    Q9_8 = -8.4765775072084e-01;
+    Q9_9 = 0.0000000000000e+00;
+    Q9_10 = 8.5631774953989e-01;
+    Q9_11 = -2.6769768119702e-01;
+    Q9_12 = 7.9365079365093e-02;
+    Q9_13 = -1.7857142857146e-02;
+    Q9_14 = 2.5974025974031e-03;
+    Q9_15 = -1.8037518037522e-04;
+    Q9_16 = 0.0000000000000e+00;
+    Q9_17 = 0.0000000000000e+00;
+    Q10_0 = -2.4843594063649e-04;
+    Q10_1 = 7.8128092862319e-04;
+    Q10_2 = -1.6271304373071e-03;
+    Q10_3 = 3.2632650250470e-03;
+    Q10_4 = -6.1239492854797e-03;
+    Q10_5 = 8.4387621360184e-03;
+    Q10_6 = 4.3175367549700e-03;
+    Q10_7 = -6.9373143801571e-02;
+    Q10_8 = 2.6369594097148e-01;
+    Q10_9 = -8.5631774953989e-01;
+    Q10_10 = 0.0000000000000e+00;
+    Q10_11 = 8.5712580212095e-01;
+    Q10_12 = -2.6785714285718e-01;
+    Q10_13 = 7.9365079365093e-02;
+    Q10_14 = -1.7857142857146e-02;
+    Q10_15 = 2.5974025974031e-03;
+    Q10_16 = -1.8037518037522e-04;
+    Q10_17 = 0.0000000000000e+00;
+    Q11_0 = -3.1501105449828e-05;
+    Q11_1 = 6.0444181254875e-05;
+    Q11_2 = -1.7066984372933e-05;
+    Q11_3 = -1.3097937809499e-04;
+    Q11_4 = 4.5892226603067e-04;
+    Q11_5 = -8.5964292632428e-04;
+    Q11_6 = -1.1092605832824e-03;
+    Q11_7 = 1.6606121869177e-02;
+    Q11_8 = -7.8759594625702e-02;
+    Q11_9 = 2.6769768119702e-01;
+    Q11_10 = -8.5712580212095e-01;
+    Q11_11 = 0.0000000000000e+00;
+    Q11_12 = 8.5714285714289e-01;
+    Q11_13 = -2.6785714285718e-01;
+    Q11_14 = 7.9365079365093e-02;
+    Q11_15 = -1.7857142857146e-02;
+    Q11_16 = 2.5974025974031e-03;
+    Q11_17 = -1.8037518037522e-04;
+
+    for i = 1:BP
+
+        for j = 1:BP
+            Q(i, j) = eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+            Q(N + 1 - i, N + 1 - j) = -eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+        end
+
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%% Undivided difference operators %%%%
+    % Closed with zeros at the first boundary nodes.
+    m = N;
+
+    DD_6 = (diag(ones(m - 3, 1), 3) - 6 * diag(ones(m - 2, 1), 2) + 15 * diag(ones(m - 1, 1), 1) - 20 * diag(ones(m, 1), 0) + 15 * diag(ones(m - 1, 1), -1) - 6 * diag(ones(m - 2, 1), -2) + diag(ones(m - 3, 1), -3));
+    DD_6(1:9, 1:12) = [0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0.70504538217624485042e1 -0.15481773512478815978e2 0.15439628908176388584e2 -0.11355022328644135767e2 0.62553652361133177256e1 -0.23565505827757565576e1 0.44789845784655348861e0 0 0 0 0 0; 0 0.84178325750049836493e0 -0.30776567832262484696e1 0.55480476621608890820e1 -0.66451562589578036671e1 0.54681670852508283306e1 -0.26873907470793164527e1 0.55220578435115281189e0 0 0 0 0; 0 0 0.36124410255434790612e0 -0.18841869963752696615e1 0.49068751071627186058e1 -0.79710602635488243814e1 0.75771246506939812399e1 -0.36661050678180486359e1 0.67610846733109492706e0 0 0 0; 0 0 0 0.31883568286286899671e0 -0.22216567686182446798e1 0.72341822309820977868e1 -0.12215760254444741754e2 0.10698736280837039597e2 -0.46222617037529123987e1 0.80792453213389245238e0 0 0; 0 0 0 0 0.45451605753051033631e0 -0.36739526096585069959e1 0.11306936594922967126e2 -0.16769946247690595362e2 0.13179014442979029716e2 -0.54150410306154005497e1 0.91847279253199572926e0 0; 0 0 0 0 0 0.77355004999207313207e0 -0.54143198515959566716e1 0.14230335022999000858e2 -0.19303948318184081752e2 0.14605034473743418451e2 -0.58729419174319386168e1 0.98229054047748459948e0; ];
+    DD_6(m - 8:m, m - 11:m) = [0.98229054047748459948e0 -0.58729419174319386168e1 0.14605034473743418451e2 -0.19303948318184081752e2 0.14230335022999000858e2 -0.54143198515959566716e1 0.77355004999207313207e0 0 0 0 0 0; 0 0.91847279253199572926e0 -0.54150410306154005497e1 0.13179014442979029716e2 -0.16769946247690595362e2 0.11306936594922967126e2 -0.36739526096585069959e1 0.45451605753051033631e0 0 0 0 0; 0 0 0.80792453213389245238e0 -0.46222617037529123987e1 0.10698736280837039597e2 -0.12215760254444741754e2 0.72341822309820977868e1 -0.22216567686182446798e1 0.31883568286286899671e0 0 0 0; 0 0 0 0.67610846733109492706e0 -0.36661050678180486359e1 0.75771246506939812399e1 -0.79710602635488243814e1 0.49068751071627186058e1 -0.18841869963752696615e1 0.36124410255434790612e0 0 0; 0 0 0 0 0.55220578435115281189e0 -0.26873907470793164527e1 0.54681670852508283306e1 -0.66451562589578036671e1 0.55480476621608890820e1 -0.30776567832262484696e1 0.84178325750049836493e0 0; 0 0 0 0 0 0.44789845784655348861e0 -0.23565505827757565576e1 0.62553652361133177256e1 -0.11355022328644135767e2 0.15439628908176388584e2 -0.15481773512478815978e2 0.70504538217624485042e1; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_6 = sparse(DD_6);
+
+    DD_7 = (-diag(ones(m - 4, 1), -4) + 7 * diag(ones(m - 3, 1), -3) - 21 * diag(ones(m - 2, 1), -2) + 35 * diag(ones(m - 1, 1), -1) - 35 * diag(ones(m, 1), 0) + 21 * diag(ones(m - 1, 1), 1) - 7 * diag(ones(m - 2, 1), 2) + diag(ones(m - 3, 1), 3));
+    DD_7(1:10, 1:13) = [0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; -0.70504538217624585762e1 0.16323556769979337662e2 -0.18517285691402663507e2 0.16903069990805048996e2 -0.12900521495071139822e2 0.78247176680265960663e1 -0.31352892049258744203e1 0.55220578435115360075e0 0 0 0 0 0; 0 -0.77136636008199086135e0 0.31512297676528003033e1 -0.68105129732006589582e1 0.10585680229488408490e2 -0.12315008394369015993e2 0.94058676147776075845e1 -0.38654404904580696832e1 0.61955060619091911792e0 0 0 0 0; 0 0 -0.32268062071305431283e0 0.19678458985349116625e1 -0.63675477999083230026e1 0.13582054493165302513e2 -0.17679957518285956226e2 0.12831367737363170226e2 -0.47327592713176644894e1 0.72167708116161363042e0 0 0 0; 0 0 0 -0.28975994984220671445e0 0.24321233335964448997e1 -0.99133841479577475282e1 0.21377580445278298070e2 -0.24963717988619759059e2 0.16177915963135193396e2 -0.56554717249372471666e1 0.83471406934702410357e0 0 0; 0 0 0 0 -0.43028213606032104517e0 0.42103703770684731501e1 -0.15829711232892153976e2 0.29347405933458541883e2 -0.30751033700284402671e2 0.18952643607153901924e2 -0.64293095477239701048e1 0.92991669927993083983e0 0; 0 0 0 0 0 -0.76177813656089336989e0 0.63167064935286161169e1 -0.19922469032198601202e2 0.33781909556822143066e2 -0.34078413772067976385e2 0.20555296711011785159e2 -0.68760337833423921963e1 0.98478196280731881078e0; ];
+    DD_7(m - 8:m, m - 12:m) = [-0.98478196280731881078e0 0.68760337833423921963e1 -0.20555296711011785159e2 0.34078413772067976385e2 -0.33781909556822143066e2 0.19922469032198601202e2 -0.63167064935286161169e1 0.76177813656089336989e0 0 0 0 0 0; 0 -0.92991669927993083983e0 0.64293095477239701048e1 -0.18952643607153901924e2 0.30751033700284402671e2 -0.29347405933458541883e2 0.15829711232892153976e2 -0.42103703770684731501e1 0.43028213606032104517e0 0 0 0 0; 0 0 -0.83471406934702410357e0 0.56554717249372471666e1 -0.16177915963135193396e2 0.24963717988619759059e2 -0.21377580445278298070e2 0.99133841479577475282e1 -0.24321233335964448997e1 0.28975994984220671445e0 0 0 0; 0 0 0 -0.72167708116161363042e0 0.47327592713176644894e1 -0.12831367737363170226e2 0.17679957518285956226e2 -0.13582054493165302513e2 0.63675477999083230026e1 -0.19678458985349116625e1 0.32268062071305431283e0 0 0; 0 0 0 0 -0.61955060619091911792e0 0.38654404904580696832e1 -0.94058676147776075845e1 0.12315008394369015993e2 -0.10585680229488408490e2 0.68105129732006589582e1 -0.31512297676528003033e1 0.77136636008199086135e0 0; 0 0 0 0 0 -0.55220578435115360075e0 0.31352892049258744203e1 -0.78247176680265960663e1 0.12900521495071139822e2 -0.16903069990805048996e2 0.18517285691402663507e2 -0.16323556769979337662e2 0.70504538217624585762e1; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_7 = sparse(DD_7);
+
+    DD_8 = (diag(ones(m - 4, 1), 4) - 8 * diag(ones(m - 3, 1), 3) + 28 * diag(ones(m - 2, 1), 2) - 56 * diag(ones(m - 1, 1), 1) + 70 * diag(ones(m, 1), 0) - 56 * diag(ones(m - 1, 1), -1) + 28 * diag(ones(m - 2, 1), -2) - 8 * diag(ones(m - 3, 1), -3) + diag(ones(m - 4, 1), -4));
+    DD_8(1:10, 1:14) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0.70504538217624673892e1 -0.17094923130061349891e2 0.21668515459055490895e2 -0.23713582964005737596e2 0.23486201724559577669e2 -0.20139726062395637234e2 0.12541156819703497681e2 -0.44176462748092288060e1 0.61955060619091989235e0 0 0 0 0 0; 0 0.71430915910501867497e0 -0.32169486987428931518e1 0.81290324137612243914e1 -0.15699214646211457760e2 0.23981482952559874949e2 -0.25082313639406953559e2 0.15461761961832278733e2 -0.49564048495273529434e1 0.66829534663026066516e0 0 0 0 0; 0 0 0.29213206789956748018e0 -0.20438756549159061384e1 0.79665959467484077329e1 -0.21271097908734749058e2 0.35359915036571912453e2 -0.34216980632968453935e2 0.18931037085270657958e2 -0.57734166492929090433e1 0.75569070942147255138e0 0 0 0; 0 0 0 0.26637215914130374043e0 -0.26313682263734604148e1 0.12983764630211125301e2 -0.34204128712445276912e2 0.49927435977239518119e2 -0.43141109235027182388e2 0.22621886899748988667e2 -0.66777125547761928286e1 0.85485906228117671607e0 0 0; 0 0 0 0 0.41007336094698233166e0 -0.47386249189431794318e1 0.21106281643856205301e2 -0.46955849493533667013e2 0.61502067400568805342e2 -0.50540382952410405130e2 0.25717238190895880419e2 -0.74393335942394467187e1 0.93853036285882489957e0 0; 0 0 0 0 0 0.75161513192516571838e0 -0.72190931354612755621e1 0.26563292042931468269e2 -0.54051055290915428906e2 0.68156827544135952769e2 -0.54814124562698093757e2 0.27504135133369568785e2 -0.78782557024585504862e1 0.98665883917119316896e0; ];
+    DD_8(m - 9:m, m - 13:m) = [0.98665883917119316896e0 -0.78782557024585504862e1 0.27504135133369568785e2 -0.54814124562698093757e2 0.68156827544135952769e2 -0.54051055290915428906e2 0.26563292042931468269e2 -0.72190931354612755621e1 0.75161513192516571838e0 0 0 0 0 0; 0 0.93853036285882489957e0 -0.74393335942394467187e1 0.25717238190895880419e2 -0.50540382952410405130e2 0.61502067400568805342e2 -0.46955849493533667013e2 0.21106281643856205301e2 -0.47386249189431794318e1 0.41007336094698233166e0 0 0 0 0; 0 0 0.85485906228117671607e0 -0.66777125547761928286e1 0.22621886899748988667e2 -0.43141109235027182388e2 0.49927435977239518119e2 -0.34204128712445276912e2 0.12983764630211125301e2 -0.26313682263734604148e1 0.26637215914130374043e0 0 0 0; 0 0 0 0.75569070942147255138e0 -0.57734166492929090433e1 0.18931037085270657958e2 -0.34216980632968453935e2 0.35359915036571912453e2 -0.21271097908734749058e2 0.79665959467484077329e1 -0.20438756549159061384e1 0.29213206789956748018e0 0 0; 0 0 0 0 0.66829534663026066516e0 -0.49564048495273529434e1 0.15461761961832278733e2 -0.25082313639406953559e2 0.23981482952559874949e2 -0.15699214646211457760e2 0.81290324137612243914e1 -0.32169486987428931518e1 0.71430915910501867497e0 0; 0 0 0 0 0 0.61955060619091989235e0 -0.44176462748092288060e1 0.12541156819703497681e2 -0.20139726062395637234e2 0.23486201724559577669e2 -0.23713582964005737596e2 0.21668515459055490895e2 -0.17094923130061349891e2 0.70504538217624673892e1; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_8 = sparse(DD_8);
+
+    DD_9 = (-diag(ones(m - 5, 1), -5) + 9 * diag(ones(m - 4, 1), -4) - 36 * diag(ones(m - 3, 1), -3) + 84 * diag(ones(m - 2, 1), -2) - 126 * diag(ones(m - 1, 1), -1) + 126 * diag(ones(m, 1), 0) - 84 * diag(ones(m - 1, 1), 1) + 36 * diag(ones(m - 2, 1), 2) - 9 * diag(ones(m - 3, 1), 3) + diag(ones(m - 4, 1), 4));
+    DD_9(1:11, 1:15) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; -0.70504538217624752230e1 0.17809232289166388354e2 -0.24885464157798411697e2 0.31842615377766997368e2 -0.39185416370771078968e2 0.44121209014955561206e2 -0.37623470459110493043e2 0.19879408236641529627e2 -0.55759554557182790312e1 0.66829534663026140771e0 0 0 0 0 0; 0 -0.66695396914459764542e0 0.32764459415493351213e1 -0.94984942131335544919e1 0.22096883550779531311e2 -0.42252556942280502185e2 0.56435205688665645507e2 -0.46385285885496836199e2 0.22303821822873088245e2 -0.60146581196723459865e1 0.70559212586023632254e0 0 0 0 0; 0 0 -0.26728718140337933088e0 0.21137687176984662199e1 -0.96966416347745772182e1 0.31341597391980823551e2 -0.63647847065829442415e2 0.76988206424179021354e2 -0.56793111255811973873e2 0.25980374921818090695e2 -0.68012163847932529624e1 0.78215606693622397877e0 0 0 0; 0 0 0 -0.24708804973573377055e0 0.28212553166921198174e1 -0.16439370707786854975e2 0.51306193068667915368e2 -0.89869384759031132614e2 0.97067495778811160373e2 -0.67865660699246966000e2 0.30049706496492867728e2 -0.76937315605305904447e1 0.87058511566721451662e0 0 0; 0 0 0 0 -0.39286387086790423439e0 0.52598319320161875843e1 -0.27136647827815121102e2 0.70433774240300500520e2 -0.11070372132102384962e3 0.11371586164292341154e3 -0.77151714572687641258e2 0.33477001174077510234e2 -0.84467732657294240961e1 0.94525186880633042479e0 0; 0 0 0 0 0 -0.74268863896636254047e0 0.81214797773939350074e1 -0.34152804055197602060e2 0.81076582936373143359e2 -0.12268228957944471498e3 0.12333178026607071095e3 -0.82512405400108706356e2 0.35452150661063477188e2 -0.88799295525407385207e1 0.98812358535685795524e0; ];
+    DD_9(m - 9:m, m - 14:m) = [-0.98812358535685795524e0 0.88799295525407385207e1 -0.35452150661063477188e2 0.82512405400108706356e2 -0.12333178026607071095e3 0.12268228957944471498e3 -0.81076582936373143359e2 0.34152804055197602060e2 -0.81214797773939350074e1 0.74268863896636254047e0 0 0 0 0 0; 0 -0.94525186880633042479e0 0.84467732657294240961e1 -0.33477001174077510234e2 0.77151714572687641258e2 -0.11371586164292341154e3 0.11070372132102384962e3 -0.70433774240300500520e2 0.27136647827815121102e2 -0.52598319320161875843e1 0.39286387086790423439e0 0 0 0 0; 0 0 -0.87058511566721451662e0 0.76937315605305904447e1 -0.30049706496492867728e2 0.67865660699246966000e2 -0.97067495778811160373e2 0.89869384759031132614e2 -0.51306193068667915368e2 0.16439370707786854975e2 -0.28212553166921198174e1 0.24708804973573377055e0 0 0 0; 0 0 0 -0.78215606693622397877e0 0.68012163847932529624e1 -0.25980374921818090695e2 0.56793111255811973873e2 -0.76988206424179021354e2 0.63647847065829442415e2 -0.31341597391980823551e2 0.96966416347745772182e1 -0.21137687176984662199e1 0.26728718140337933088e0 0 0; 0 0 0 0 -0.70559212586023632254e0 0.60146581196723459865e1 -0.22303821822873088245e2 0.46385285885496836199e2 -0.56435205688665645507e2 0.42252556942280502185e2 -0.22096883550779531311e2 0.94984942131335544919e1 -0.32764459415493351213e1 0.66695396914459764542e0 0; 0 0 0 0 0 -0.66829534663026140771e0 0.55759554557182790312e1 -0.19879408236641529627e2 0.37623470459110493043e2 -0.44121209014955561206e2 0.39185416370771078968e2 -0.31842615377766997368e2 0.24885464157798411697e2 -0.17809232289166388354e2 0.70504538217624752230e1; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_9 = sparse(DD_9);
+
+    DD_10 = (diag(ones(m - 5, 1), 5) - 10 * diag(ones(m - 4, 1), 4) + 45 * diag(ones(m - 3, 1), 3) - 120 * diag(ones(m - 2, 1), 2) + 210 * diag(ones(m - 1, 1), 1) - 252 * diag(ones(m, 1), 0) + 210 * diag(ones(m - 1, 1), -1) - 120 * diag(ones(m - 2, 1), -2) + 45 * diag(ones(m - 3, 1), -3) - 10 * diag(ones(m - 4, 1), -4) + diag(ones(m - 5, 1), -5));
+    DD_10(1:11, 1:16) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0.70504538217624822734e1 -0.18476186258311004476e2 0.28161910099347774980e2 -0.41341109590900593201e2 0.61282299921550671561e2 -0.86373765957236149764e2 0.94058676147776232608e2 -0.66264694122138432090e2 0.27879777278591395156e2 -0.66829534663026140771e1 0.70559212586023702812e0 0 0 0 0 0; 0 0.62689419647750179604e0 -0.33308831364980034247e1 0.10914786591113557343e2 -0.29883886064802801254e2 0.69173811658980680918e2 -0.11287041137733129101e3 0.11596321471374209050e3 -0.74346072742910294151e2 0.30073290598361729932e2 -0.70559212586023632254e1 0.73517682146919258207e0 0 0 0 0; 0 0 0.24665297575614270036e0 -0.21786018467637256442e1 0.11551532389532584562e2 -0.44092342567416599454e2 0.10607974510971573736e3 -0.15397641284835804271e3 0.14198277813952993468e3 -0.86601249739393635650e2 0.34006081923966264812e2 -0.78215606693622397877e1 0.80337713279357912969e0 0 0 0; 0 0 0 0.23087142251463535911e0 -0.30031734426541638399e1 0.20275063024062368195e2 -0.73294561526668450525e2 0.14978230793171855436e3 -0.19413499155762232075e3 0.16966415174811741500e3 -0.10016568832164289243e3 0.38468657802652952223e2 -0.87058511566721451662e1 0.88321407619404757308e0 0 0; 0 0 0 0 0.37796280007363075810e0 -0.57748488744870271355e1 0.33920809784768901377e2 -0.10061967748614357217e3 0.18450620220170641603e3 -0.22743172328584682309e3 0.19287928643171910314e3 -0.11159000391359170078e3 0.42233866328647120480e2 -0.94525186880633042479e1 0.95064470121725563376e0 0; 0 0 0 0 0 0.73474076934244039806e0 -0.90238664193265944527e1 0.42691005068997002575e2 -0.11582368990910449051e3 0.20447048263240785831e3 -0.24666356053214142190e3 0.20628101350027176589e3 -0.11817383553687825729e3 0.44399647762703692603e2 -0.98812358535685795524e1 0.98929851729658394154e0; ];
+    DD_10(m - 10:m, m - 15:m) = [0.98929851729658394154e0 -0.98812358535685795524e1 0.44399647762703692603e2 -0.11817383553687825729e3 0.20628101350027176589e3 -0.24666356053214142190e3 0.20447048263240785831e3 -0.11582368990910449051e3 0.42691005068997002575e2 -0.90238664193265944527e1 0.73474076934244039806e0 0 0 0 0 0; 0 0.95064470121725563376e0 -0.94525186880633042479e1 0.42233866328647120480e2 -0.11159000391359170078e3 0.19287928643171910314e3 -0.22743172328584682309e3 0.18450620220170641603e3 -0.10061967748614357217e3 0.33920809784768901377e2 -0.57748488744870271355e1 0.37796280007363075810e0 0 0 0 0; 0 0 0.88321407619404757308e0 -0.87058511566721451662e1 0.38468657802652952223e2 -0.10016568832164289243e3 0.16966415174811741500e3 -0.19413499155762232075e3 0.14978230793171855436e3 -0.73294561526668450525e2 0.20275063024062368195e2 -0.30031734426541638399e1 0.23087142251463535911e0 0 0 0; 0 0 0 0.80337713279357912969e0 -0.78215606693622397877e1 0.34006081923966264812e2 -0.86601249739393635650e2 0.14198277813952993468e3 -0.15397641284835804271e3 0.10607974510971573736e3 -0.44092342567416599454e2 0.11551532389532584562e2 -0.21786018467637256442e1 0.24665297575614270036e0 0 0; 0 0 0 0 0.73517682146919258207e0 -0.70559212586023632254e1 0.30073290598361729932e2 -0.74346072742910294151e2 0.11596321471374209050e3 -0.11287041137733129101e3 0.69173811658980680918e2 -0.29883886064802801254e2 0.10914786591113557343e2 -0.33308831364980034247e1 0.62689419647750179604e0 0; 0 0 0 0 0 0.70559212586023702812e0 -0.66829534663026140771e1 0.27879777278591395156e2 -0.66264694122138432090e2 0.94058676147776232608e2 -0.86373765957236149764e2 0.61282299921550671561e2 -0.41341109590900593201e2 0.28161910099347774980e2 -0.18476186258311004476e2 0.70504538217624822734e1; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_10 = sparse(DD_10);
+
+    %[-1, 11, -55, 165, -330, 462, -462, 330, -165, 55, -11, 1, 0]
+    DD_11 = (-diag(ones(m - 6, 1), -6) + 11 * diag(ones(m - 5, 1), -5) - 55 * diag(ones(m - 4, 1), -4) + 165 * diag(ones(m - 3, 1), -3) - 330 * diag(ones(m - 2, 1), -2) + 462 * diag(ones(m - 1, 1), -1) - 462 * diag(ones(m, 1), 0) + 330 * diag(ones(m - 1, 1), 1) - 165 * diag(ones(m - 2, 1), 2) + 55 * diag(ones(m - 3, 1), 3) - 11 * diag(ones(m - 4, 1), 4) + diag(ones(m - 5, 1), 5));
+    DD_11(1:12, 1:17) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; -0.70504538217624886828e1 0.19103080454788523638e2 -0.31492793235845807035e2 0.52255896182014198048e2 -0.91166185986353555693e2 0.15554757761621697209e3 -0.20692908752510771174e3 0.18222790883588068825e3 -0.10222585002150178224e3 0.36756244064664377424e2 -0.77615133844626073094e1 0.73517682146919325041e0 0 0 0 0 0; 0 -0.59247568548574727441e0 0.33811178542850964393e1 -0.12374519230919224800e2 0.39160480492663455349e2 -0.10704747746062038309e3 0.20692908752510736686e3 -0.25511907237023259909e3 0.20445170004300330891e3 -0.11026873219399300975e3 0.38807566922312997740e2 -0.80869450361611184028e1 0.75926914003985711330e0 0 0 0 0; 0 0 -0.22922038452398770506e0 0.22391799149842653766e1 -0.13526028855965613027e2 0.59818136859120542509e2 -0.16669674231526758728e3 0.28229009022198974497e3 -0.31236211190696585630e3 0.23815343678333249804e3 -0.12468896705454297098e3 0.43018583681492318832e2 -0.88371484607293704266e1 0.82079151707601599340e0 0 0 0; 0 0 0 -0.21701391114437616116e0 0.31781915325611402199e1 -0.24486327517266714070e2 0.10078002209916911947e3 -0.23537219817841487113e3 0.35591415118897425470e3 -0.37326113384585831300e3 0.27545564288451795418e3 -0.14105174527639415815e3 0.47882181361696798414e2 -0.97153548381345233039e1 0.89358450029368883150e0 0 0; 0 0 0 0 -0.36488508570871329747e0 0.62843543720560487741e1 -0.41458767514717546128e2 0.13835205654344741174e3 -0.28993831774553865375e3 0.41695815935738584232e3 -0.42433443014978202692e3 0.30687251076237717714e3 -0.15485750987170610843e3 0.51988852784348173364e2 -0.10457091713389811971e2 0.95506826122820715686e0 0; 0 0 0 0 0 -0.72758579432539352184e0 0.99262530612592538979e1 -0.52177895084329669814e2 0.15925757362501867446e3 -0.32131075842235520591e3 0.45221652764225927349e3 -0.45381822970059788496e3 0.32497804772641520756e3 -0.16279870846324687288e3 0.54346797194627187538e2 -0.10882283690262423357e2 0.99026190553785350209e0; ];
+    DD_11(m - 10:m, m - 16:m) = [-0.99026190553785350209e0 0.10882283690262423357e2 -0.54346797194627187538e2 0.16279870846324687288e3 -0.32497804772641520756e3 0.45381822970059788496e3 -0.45221652764225927349e3 0.32131075842235520591e3 -0.15925757362501867446e3 0.52177895084329669814e2 -0.99262530612592538979e1 0.72758579432539352184e0 0 0 0 0 0; 0 -0.95506826122820715686e0 0.10457091713389811971e2 -0.51988852784348173364e2 0.15485750987170610843e3 -0.30687251076237717714e3 0.42433443014978202692e3 -0.41695815935738584232e3 0.28993831774553865375e3 -0.13835205654344741174e3 0.41458767514717546128e2 -0.62843543720560487741e1 0.36488508570871329747e0 0 0 0 0; 0 0 -0.89358450029368883150e0 0.97153548381345233039e1 -0.47882181361696798414e2 0.14105174527639415815e3 -0.27545564288451795418e3 0.37326113384585831300e3 -0.35591415118897425470e3 0.23537219817841487113e3 -0.10078002209916911947e3 0.24486327517266714070e2 -0.31781915325611402199e1 0.21701391114437616116e0 0 0 0; 0 0 0 -0.82079151707601599340e0 0.88371484607293704266e1 -0.43018583681492318832e2 0.12468896705454297098e3 -0.23815343678333249804e3 0.31236211190696585630e3 -0.28229009022198974497e3 0.16669674231526758728e3 -0.59818136859120542509e2 0.13526028855965613027e2 -0.22391799149842653766e1 0.22922038452398770506e0 0 0; 0 0 0 0 -0.75926914003985711330e0 0.80869450361611184028e1 -0.38807566922312997740e2 0.11026873219399300975e3 -0.20445170004300330891e3 0.25511907237023259909e3 -0.20692908752510736686e3 0.10704747746062038309e3 -0.39160480492663455349e2 0.12374519230919224800e2 -0.33811178542850964393e1 0.59247568548574727441e0 0; 0 0 0 0 0 -0.73517682146919325041e0 0.77615133844626073094e1 -0.36756244064664377424e2 0.10222585002150178224e3 -0.18222790883588068825e3 0.20692908752510771174e3 -0.15554757761621697209e3 0.91166185986353555693e2 -0.52255896182014198048e2 0.31492793235845807035e2 -0.19103080454788523638e2 0.70504538217624886828e1; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_11 = sparse(DD_11);
+
+    %[1, -12, 66, -220, 495, -792, 924, -792, 495, -220, 66,-12, 1]
+    DD_12 = (diag(ones(m - 6, 1), 6) - 12 * diag(ones(m - 5, 1), 5) + 66 * diag(ones(m - 4, 1), 4) - 220 * diag(ones(m - 3, 1), 3) + 495 * diag(ones(m - 2, 1), 2) - 792 * diag(ones(m - 1, 1), 1) + 924 * diag(ones(m, 1), 0) - 792 * diag(ones(m - 1, 1), -1) + 495 * diag(ones(m - 2, 1), -2) - 220 * diag(ones(m - 3, 1), -3) + 66 * diag(ones(m - 4, 1), -4) - 12 * diag(ones(m - 5, 1), -5) + diag(ones(m - 6, 1), -6));
+    DD_12(1:12, 1:18) = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0.70504538217624945581e1 -0.19695556140274287325e2 0.34873911090130932535e2 -0.64630415412933476707e2 0.13032666647901711965e3 -0.26259505507683757401e3 0.41385817505021542347e3 -0.43734698120611365179e3 0.30667755006450534672e3 -0.14702497625865750970e3 0.46569080306775643856e2 -0.88221218576303190049e1 0.75926914003985774602e0 0 0 0 0 0; 0 0.56252054413792412864e0 -0.34278021535209417538e1 0.13874841106233553089e2 -0.50022717612825328606e2 0.15842900977125025845e3 -0.35473557861446977176e3 0.51023814474046519819e3 -0.49068408010320794140e3 0.33080619658197902926e3 -0.15523026768925199096e3 0.48521670216966710417e2 -0.91112296804782853596e1 0.77929289272158630803e0 0 0 0 0; 0 0 0.21428192906429919385e0 -0.22961219278627835050e1 0.15615594467315483385e2 -0.78810282483468544556e2 0.25004511347290138092e3 -0.48392586895198241994e3 0.62472422381393171260e3 -0.57156824827999799529e3 0.37406690116362891293e3 -0.17207433472596927533e3 0.53022890764376222560e2 -0.98494982049121919208e1 0.83534896297519895228e0 0 0 0; 0 0 0 0.20501361895561890151e0 -0.33471539032596363119e1 0.29069145008244604383e2 -0.13437336279889215930e3 0.35305829726762230670e3 -0.61013854489538443663e3 0.74652226769171662600e3 -0.66109354292284309003e3 0.42315523582918247446e3 -0.19152872544678719366e3 0.58292129028807139823e2 -0.10723014003524265978e2 0.90225552616201164306e0 0 0; 0 0 0 0 0.35327850260839005288e0 -0.67888982569607603271e1 0.49750521017661055353e2 -0.18446940872459654898e3 0.43490747661830798063e3 -0.71478541604123287255e3 0.84866886029956405384e3 -0.73649402582970522515e3 0.46457252961511832529e3 -0.20795541113739269345e3 0.62742550280338871828e2 -0.11460819134738485882e2 0.95876279102790935799e0 0; 0 0 0 0 0 0.72108566182178027310e0 -0.10828639703191913343e2 0.62613474101195603777e2 -0.21234343150002489927e3 0.48196613763353280887e3 -0.77522833310101589741e3 0.90763645940119576992e3 -0.77994731454339649814e3 0.48839612538974061864e3 -0.21738718877850875015e3 0.65293702141574540142e2 -0.11883142866454242025e2 0.99106616353107873319e0; ];
+    DD_12(m - 11:m, m - 17:m) = [0.99106616353107873319e0 -0.11883142866454242025e2 0.65293702141574540142e2 -0.21738718877850875015e3 0.48839612538974061864e3 -0.77994731454339649814e3 0.90763645940119576992e3 -0.77522833310101589741e3 0.48196613763353280887e3 -0.21234343150002489927e3 0.62613474101195603777e2 -0.10828639703191913343e2 0.72108566182178027310e0 0 0 0 0 0; 0 0.95876279102790935799e0 -0.11460819134738485882e2 0.62742550280338871828e2 -0.20795541113739269345e3 0.46457252961511832529e3 -0.73649402582970522515e3 0.84866886029956405384e3 -0.71478541604123287255e3 0.43490747661830798063e3 -0.18446940872459654898e3 0.49750521017661055353e2 -0.67888982569607603271e1 0.35327850260839005288e0 0 0 0 0; 0 0 0.90225552616201164306e0 -0.10723014003524265978e2 0.58292129028807139823e2 -0.19152872544678719366e3 0.42315523582918247446e3 -0.66109354292284309003e3 0.74652226769171662600e3 -0.61013854489538443663e3 0.35305829726762230670e3 -0.13437336279889215930e3 0.29069145008244604383e2 -0.33471539032596363119e1 0.20501361895561890151e0 0 0 0; 0 0 0 0.83534896297519895228e0 -0.98494982049121919208e1 0.53022890764376222560e2 -0.17207433472596927533e3 0.37406690116362891293e3 -0.57156824827999799529e3 0.62472422381393171260e3 -0.48392586895198241994e3 0.25004511347290138092e3 -0.78810282483468544556e2 0.15615594467315483385e2 -0.22961219278627835050e1 0.21428192906429919385e0 0 0; 0 0 0 0 0.77929289272158630803e0 -0.91112296804782853596e1 0.48521670216966710417e2 -0.15523026768925199096e3 0.33080619658197902926e3 -0.49068408010320794140e3 0.51023814474046519819e3 -0.35473557861446977176e3 0.15842900977125025845e3 -0.50022717612825328606e2 0.13874841106233553089e2 -0.34278021535209417538e1 0.56252054413792412864e0 0; 0 0 0 0 0 0.75926914003985774602e0 -0.88221218576303190049e1 0.46569080306775643856e2 -0.14702497625865750970e3 0.30667755006450534672e3 -0.43734698120611365179e3 0.41385817505021542347e3 -0.26259505507683757401e3 0.13032666647901711965e3 -0.64630415412933476707e2 0.34873911090130932535e2 -0.19695556140274287325e2 0.70504538217624945581e1; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_12 = sparse(DD_12);
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Difference operators %%
+    D1 = H \ Q;
+
+    % Helper functions for constructing D2(c)
+    % TODO: Consider changing sparse(diag(...)) to spdiags(....)
+
+    % Minimal 13 point stencil width
+    function D2 = D2_fun_minimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 5/18 * diag(ones(m - 1, 1), -1) + 5/18 * diag(ones(m - 1, 1), 1) + 4/9 * diag(ones(m, 1), 0); C3(1, 3) = 5/18; C3(m, m - 2) = 5/18;
+        C4 = 5/28 * diag(ones(m - 2, 1), -2) + 9/28 * diag(ones(m - 1, 1), -1) + 5/28 * diag(ones(m - 1, 1), 1) + 9/28 * diag(ones(m, 1), 0); C4(2, 4) = 5/28; C4(1, 3) = 5/28; C4(1, 4) = 9/28; C4(m, m - 2) = 5/28; C4(m, m - 3) = 5/28;
+        C5 = 1/7 * diag(ones(m - 2, 1), -2) + 1/7 * diag(ones(m - 2, 1), 2) + 8/35 * diag(ones(m - 1, 1), -1) + 8/35 * diag(ones(m - 1, 1), 1) + 9/35 * diag(ones(m, 1), 0); C5(2, 4) = 1/7; C5(1, 3) = 1/7; C5(1, 4) = 1/7; C5(1, 5) = 8/35; C5(2, 5) = 1/7; C5(m - 1, m - 4) = 1/7; C5(m, m - 4) = 8/35; C5(m, m - 3) = 1/7;
+        C6 = 1/6 * diag(ones(m - 3, 1), -3) + 1/6 * diag(ones(m - 2, 1), -2) + 1/6 * diag(ones(m - 2, 1), 2) + 1/6 * diag(ones(m - 1, 1), -1) + 1/6 * diag(ones(m - 1, 1), 1) + 1/6 * diag(ones(m, 1), 0);
+        C6(1, 4) = 1/6; C6(1, 5) = 1/6; C6(1, 6) = 1/6; C6(2, 5) = 1/6; C6(2, 6) = 1/6; C6(3, 6) = 1/6; C6(m - 1, m - 5) = 1/6; C6(m, m - 4) = 1/6; C6(m, m - 5) = 1/6;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+        C4 = sparse(diag(C4 * c));
+        C5 = sparse(diag(C5 * c));
+        C6 = sparse(diag(C6 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/30735936 / h) * transpose(DD_12) * C1 * DD_12 + (1/6403320 / h) * transpose(DD_11) * C2 * DD_11 + (1/1293600 / h) * transpose(DD_10) * C3 * DD_10 + (1/249480 / h) * transpose(DD_9) * C4 * DD_9 + (1/44352 / h) * transpose(DD_8) * C5 * DD_8 + (1/6468 / h) * transpose(DD_7) * C6 * DD_7;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Few additional grid point in interior stencil cmp. to minimal
+    function D2 = D2_fun_nonminimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 5/18 * diag(ones(m - 1, 1), -1) + 5/18 * diag(ones(m - 1, 1), 1) + 4/9 * diag(ones(m, 1), 0); C3(1, 3) = 5/18; C3(m, m - 2) = 5/18;
+        C4 = 5/28 * diag(ones(m - 2, 1), -2) + 9/28 * diag(ones(m - 1, 1), -1) + 5/28 * diag(ones(m - 1, 1), 1) + 9/28 * diag(ones(m, 1), 0); C4(2, 4) = 5/28; C4(1, 3) = 5/28; C4(1, 4) = 9/28; C4(m, m - 2) = 5/28; C4(m, m - 3) = 5/28;
+        C5 = 1/7 * diag(ones(m - 2, 1), -2) + 1/7 * diag(ones(m - 2, 1), 2) + 8/35 * diag(ones(m - 1, 1), -1) + 8/35 * diag(ones(m - 1, 1), 1) + 9/35 * diag(ones(m, 1), 0); C5(2, 4) = 1/7; C5(1, 3) = 1/7; C5(1, 4) = 1/7; C5(1, 5) = 8/35; C5(2, 5) = 1/7; C5(m - 1, m - 4) = 1/7; C5(m, m - 4) = 8/35; C5(m, m - 3) = 1/7;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+        C4 = sparse(diag(C4 * c));
+        C5 = sparse(diag(C5 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/30735936 / h) * transpose(DD_12) * C1 * DD_12 + (1/6403320 / h) * transpose(DD_11) * C2 * DD_11 + (1/1293600 / h) * transpose(DD_10) * C3 * DD_10 + (1/249480 / h) * transpose(DD_9) * C4 * DD_9 + (1/44352 / h) * transpose(DD_8) * C5 * DD_8;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Wide stencil
+    function D2 = D2_fun_wide(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        D2 = D1 * C1 * D1;
+    end
+
+    switch options.stencil_width
+        case 'minimal'
+            D2 = @D2_fun_minimal;
+        case 'nonminimal'
+            D2 = @D2_fun_nonminimal;
+        case 'wide'
+            D2 = @D2_fun_wide;
+        otherwise
+            error('No option %s for stencil width', options.stencil_width)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Artificial dissipation operator %%%
+    switch options.AD
+        case 'upwind'
+            % This is the choice that yield 11th order Upwind
+            DI = H \ (transpose(DD_6) * DD_6) * (-1/5544);
+        case 'op'
+            % This choice will preserve the order of the underlying
+            % Non-dissipative D1 SBP operator
+            DI = H \ (transpose(DD_7) * DD_7) * (-1 / (5 * 5544));
+            % Notice that you can use any negative number instead of (-1/(5*5544))
+        otherwise
+            error("Artificial dissipation options '%s' not implemented.", option.AD)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+implementations/d2_noneq_variable_4.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,162 @@
+function [H, HI, D1, D2, DI] = d2_noneq_variable_4(N, h, options)
+    % N: Number of grid points
+    % h: grid spacing
+    % options: struct containing options for constructing the operator
+    %          current options are: 
+    %               options.stencil_type ('minimal','nonminimal','wide')
+    %               options.AD ('upwind', 'op')
+
+    % BP: Number of boundary points
+    % order: Accuracy of interior stencil
+    BP = 4;
+    order = 4;
+    if(N<2*BP)
+        error(['Operator requires at least ' num2str(2*BP) ' grid points']);
+    end
+
+    %%%% Norm matrix %%%%%%%%
+    P = zeros(BP, 1);
+    P0 = 2.1259737557798e-01;
+    P1 = 1.0260290400758e+00;
+    P2 = 1.0775123588954e+00;
+    P3 = 9.8607273802835e-01;
+
+    for i = 0:BP - 1
+        P(i + 1) = eval(['P' num2str(i)]);
+    end
+
+    Hv = ones(N, 1);
+    Hv(1:BP) = P;
+    Hv(end - BP + 1:end) = flip(P);
+    Hv = h * Hv;
+    H = spdiags(Hv, 0, N, N);
+    HI = spdiags(1 ./ Hv, 0, N, N);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Q matrix %%%%%%%%%%%
+    d = [1/12, -2/3, 0, 2/3, -1/12];
+    d = repmat(d, N, 1);
+    Q = spdiags(d, -order / 2:order / 2, N, N);
+
+    % Boundaries
+    Q0_0 = -5.0000000000000e-01;
+    Q0_1 = 6.5605279837843e-01;
+    Q0_2 = -1.9875859409017e-01;
+    Q0_3 = 4.2705795711740e-02;
+    Q0_4 = 0.0000000000000e+00;
+    Q0_5 = 0.0000000000000e+00;
+    Q1_0 = -6.5605279837843e-01;
+    Q1_1 = 0.0000000000000e+00;
+    Q1_2 = 8.1236966439895e-01;
+    Q1_3 = -1.5631686602052e-01;
+    Q1_4 = 0.0000000000000e+00;
+    Q1_5 = 0.0000000000000e+00;
+    Q2_0 = 1.9875859409017e-01;
+    Q2_1 = -8.1236966439895e-01;
+    Q2_2 = 0.0000000000000e+00;
+    Q2_3 = 6.9694440364211e-01;
+    Q2_4 = -8.3333333333333e-02;
+    Q2_5 = 0.0000000000000e+00;
+    Q3_0 = -4.2705795711740e-02;
+    Q3_1 = 1.5631686602052e-01;
+    Q3_2 = -6.9694440364211e-01;
+    Q3_3 = 0.0000000000000e+00;
+    Q3_4 = 6.6666666666667e-01;
+    Q3_5 = -8.3333333333333e-02;
+
+    for i = 1:BP
+
+        for j = 1:BP
+            Q(i, j) = eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+            Q(N + 1 - i, N + 1 - j) = -eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+        end
+
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%% Undivided difference operators %%%%
+    % Closed with zeros at the first boundary nodes.
+    m = N;
+
+    DD_2 = (diag(ones(m - 1, 1), -1) - 2 * diag(ones(m, 1), 0) + diag(ones(m - 1, 1), 1));
+    DD_2(1:3, 1:4) = [0 0 0 0; 0.16138369498429727170e1 -0.26095138364100825853e1 0.99567688656710986834e0 0; 0 0.84859980956172494512e0 -0.17944203477786665350e1 0.94582053821694158989e0; ];
+    DD_2(m - 2:m, m - 3:m) = [0.94582053821694158989e0 -0.17944203477786665350e1 0.84859980956172494512e0 0; 0 0.99567688656710986834e0 -0.26095138364100825853e1 0.16138369498429727170e1; 0 0 0 0; ];
+    DD_2 = sparse(DD_2);
+
+    DD_3 = (-diag(ones(m - 2, 1), -2) + 3 * diag(ones(m - 1, 1), -1) - 3 * diag(ones(m, 1), 0) + diag(ones(m - 1, 1), 1));
+    DD_3(1:4, 1:5) = [0 0 0 0 0; 0 0 0 0 0; -0.17277463987989539852e1 0.37021976718569105700e1 -0.29870306597013296050e1 0.10125793866433730203e1 0; 0 -0.81738495424057284493e0 0.26916305216679998025e1 -0.28374616146508247697e1 0.96321604722339781208e0; ];
+    DD_3(m - 2:m, m - 4:m) = [-0.96321604722339781208e0 0.28374616146508247697e1 -0.26916305216679998025e1 0.81738495424057284493e0 0; 0 -0.10125793866433730203e1 0.29870306597013296050e1 -0.37021976718569105700e1 0.17277463987989539852e1; 0 0 0 0 0; ];
+    DD_3 = sparse(DD_3);
+
+    DD_4 = (diag(ones(m - 2, 1), 2) - 4 * diag(ones(m - 1, 1), 1) + 6 * diag(ones(m, 1), 0) - 4 * diag(ones(m - 1, 1), -1) + diag(ones(m - 2, 1), -2));
+    DD_4(1:4, 1:6) = [0 0 0 0 0 0; 0 0 0 0 0 0; 0.18176226052481525189e1 -0.47546882767009058782e1 0.59740613194026592100e1 -0.40503175465734920811e1 0.10133218986235862303e1 0; 0 0.79462567299107735362e0 -0.35888406955573330700e1 0.56749232293016495393e1 -0.38528641888935912483e1 0.97215598215819742539e0; ];
+    DD_4(m - 3:m, m - 5:m) = [0.97215598215819742539e0 -0.38528641888935912483e1 0.56749232293016495393e1 -0.35888406955573330700e1 0.79462567299107735362e0 0; 0 0.10133218986235862303e1 -0.40503175465734920811e1 0.59740613194026592100e1 -0.47546882767009058782e1 0.18176226052481525189e1; 0 0 0 0 0 0; 0 0 0 0 0 0; ];
+    DD_4 = sparse(DD_4);
+    %%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Difference operators %%%
+    D1 = H \ Q;
+
+    % Helper functions for constructing D2(c)
+    % TODO: Consider changing sparse(diag(...)) to spdiags(....)
+
+    % Minimal 5 point stencil width
+    function D2 = D2_fun_minimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+
+        C2 = sparse(diag(C2 * c));
+
+        % Remainder term added to wide second drivative opereator, to obtain a 5
+        % point narrow stencil.
+        R = (1/144 / h) * transpose(DD_4) * C1 * DD_4 + (1/18 / h) * transpose(DD_3) * C2 * DD_3;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Few additional grid point in interior stencil cmp. to minimal
+    function D2 = D2_fun_nonminimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/144 / h) * transpose(DD_4) * C1 * DD_4;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Wide stencil
+    function D2 = D2_fun_wide(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        D2 = D1 * C1 * D1;
+    end
+
+    switch options.stencil_width
+        case 'minimal'
+            D2 = @D2_fun_minimal;
+        case 'nonminimal'
+            D2 = @D2_fun_nonminimal;
+        case 'wide'
+            D2 = @D2_fun_wide;
+        otherwise
+            error('No option %s for stencil width', options.stencil_width)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Artificial dissipation operator %%%
+    switch options.AD
+        case 'upwind'
+            % This is the choice that yield 3rd order Upwind
+            DI = H \ (transpose(DD_2) * DD_2) * (-1/12);
+        case 'op'
+            % This choice will preserve the order of the underlying
+            % Non-dissipative D1 SBP operator
+            DI = H \ (transpose(DD_3) * DD_3) * (-1 / (5 * 12));
+        otherwise
+            error("Artificial dissipation options '%s' not implemented.", option.AD)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+implementations/d2_noneq_variable_6.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,205 @@
+function [H, HI, D1, D2, DI] = d2_noneq_variable_6(N, h, options)
+    % N: Number of grid points
+    % h: grid spacing
+    % options: struct containing options for constructing the operator
+    %          current options are: 
+    %               options.stencil_type ('minimal','nonminimal','wide')
+    %               options.AD ('upwind', 'op')
+
+    % BP: Number of boundary points
+    % order: Accuracy of interior stencil
+    BP = 6;
+    order = 6;
+    if(N<2*BP)
+        error(['Operator requires at least ' num2str(2*BP) ' grid points']);
+    end
+
+    %%%% Norm matrix %%%%%%%%
+    P = zeros(BP, 1);
+    P0 = 1.3030223027124e-01;
+    P1 = 6.8851501587715e-01;
+    P2 = 9.5166202564389e-01;
+    P3 = 9.9103890475697e-01;
+    P4 = 1.0028757074552e+00;
+    P5 = 9.9950151111941e-01;
+
+    for i = 0:BP - 1
+        P(i + 1) = eval(['P' num2str(i)]);
+    end
+
+    Hv = ones(N, 1);
+    Hv(1:BP) = P;
+    Hv(end - BP + 1:end) = flip(P);
+    Hv = h * Hv;
+    H = spdiags(Hv, 0, N, N);
+    HI = spdiags(1 ./ Hv, 0, N, N);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Q matrix %%%%%%%%%%%
+
+    % interior stencil
+    d = [-1/60, 3/20, -3/4, 0, 3/4, -3/20, 1/60];
+    d = repmat(d, N, 1);
+    Q = spdiags(d, -order / 2:order / 2, N, N);
+
+    % Boundaries
+    Q0_0 = -5.0000000000000e-01;
+    Q0_1 = 6.6042071945824e-01;
+    Q0_2 = -2.2104152954203e-01;
+    Q0_3 = 7.6243679810093e-02;
+    Q0_4 = -1.7298206716724e-02;
+    Q0_5 = 1.6753369904210e-03;
+    Q0_6 = 0.0000000000000e+00;
+    Q0_7 = 0.0000000000000e+00;
+    Q0_8 = 0.0000000000000e+00;
+    Q1_0 = -6.6042071945824e-01;
+    Q1_1 = 0.0000000000000e+00;
+    Q1_2 = 8.7352798702787e-01;
+    Q1_3 = -2.6581719253084e-01;
+    Q1_4 = 5.7458484948314e-02;
+    Q1_5 = -4.7485599871040e-03;
+    Q1_6 = 0.0000000000000e+00;
+    Q1_7 = 0.0000000000000e+00;
+    Q1_8 = 0.0000000000000e+00;
+    Q2_0 = 2.2104152954203e-01;
+    Q2_1 = -8.7352798702787e-01;
+    Q2_2 = 0.0000000000000e+00;
+    Q2_3 = 8.1707122038457e-01;
+    Q2_4 = -1.8881125503769e-01;
+    Q2_5 = 2.4226492138960e-02;
+    Q2_6 = 0.0000000000000e+00;
+    Q2_7 = 0.0000000000000e+00;
+    Q2_8 = 0.0000000000000e+00;
+    Q3_0 = -7.6243679810093e-02;
+    Q3_1 = 2.6581719253084e-01;
+    Q3_2 = -8.1707122038457e-01;
+    Q3_3 = 0.0000000000000e+00;
+    Q3_4 = 7.6798636652679e-01;
+    Q3_5 = -1.5715532552963e-01;
+    Q3_6 = 1.6666666666667e-02;
+    Q3_7 = 0.0000000000000e+00;
+    Q3_8 = 0.0000000000000e+00;
+    Q4_0 = 1.7298206716724e-02;
+    Q4_1 = -5.7458484948314e-02;
+    Q4_2 = 1.8881125503769e-01;
+    Q4_3 = -7.6798636652679e-01;
+    Q4_4 = 0.0000000000000e+00;
+    Q4_5 = 7.5266872305402e-01;
+    Q4_6 = -1.5000000000000e-01;
+    Q4_7 = 1.6666666666667e-02;
+    Q4_8 = 0.0000000000000e+00;
+    Q5_0 = -1.6753369904210e-03;
+    Q5_1 = 4.7485599871040e-03;
+    Q5_2 = -2.4226492138960e-02;
+    Q5_3 = 1.5715532552963e-01;
+    Q5_4 = -7.5266872305402e-01;
+    Q5_5 = 0.0000000000000e+00;
+    Q5_6 = 7.5000000000000e-01;
+    Q5_7 = -1.5000000000000e-01;
+    Q5_8 = 1.6666666666667e-02;
+
+    for i = 1:BP
+
+        for j = 1:BP
+            Q(i, j) = eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+            Q(N + 1 - i, N + 1 - j) = -eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+        end
+
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%% Undivided difference operators %%%%
+    % Closed with zeros at the first boundary nodes.
+    m = N;
+
+    DD_3 = (-diag(ones(m - 2, 1), -2) + 3 * diag(ones(m - 1, 1), -1) - 3 * diag(ones(m, 1), 0) + diag(ones(m - 1, 1), 1));
+    DD_3(1:5, 1:6) = [0 0 0 0 0 0; 0 0 0 0 0 0; -0.46757024540266021836e1 0.88373748766984018738e1 -0.56477423503490435435e1 0.14860699276772438533e1 0 0; 0 -0.13802450758054908946e1 0.36701915175801340778e1 -0.33643068661005748879e1 0.10743604243259317047e1 0; 0 0 -0.10409288946349185618e1 0.30665535320781497878e1 -0.30329117010471766032e1 0.10072870636039453772e1; ];
+    DD_3(m - 3:m, m - 5:m) = [-0.10072870636039453772e1 0.30329117010471766032e1 -0.30665535320781497878e1 0.10409288946349185618e1 0 0; 0 -0.10743604243259317047e1 0.33643068661005748879e1 -0.36701915175801340778e1 0.13802450758054908946e1 0; 0 0 -0.14860699276772438533e1 0.56477423503490435435e1 -0.88373748766984018738e1 0.46757024540266021836e1; 0 0 0 0 0 0; ];
+    DD_3 = sparse(DD_3);
+
+    DD_4 = (diag(ones(m - 2, 1), 2) - 4 * diag(ones(m - 1, 1), 1) + 6 * diag(ones(m, 1), 0) - 4 * diag(ones(m - 1, 1), -1) + diag(ones(m - 2, 1), -2));
+    DD_4(1:5, 1:7) = [0 0 0 0 0 0 0; 0 0 0 0 0 0 0; 0.57302111593550648941e1 -0.12521994384708052700e2 0.11419402572582197931e2 -0.59442797107089754133e1 0.13166603634797652881e1 0 0; 0 0.14441513881249918393e1 -0.49292485821432017638e1 0.67286137322011497757e1 -0.42974416973037268190e1 0.10539251591207869677e1 0; 0 0 0.10466075357769140419e1 -0.40887380427708663837e1 0.60658234020943532065e1 -0.40291482544157815088e1 0.10054553593153806442e1; ];
+    DD_4(m - 4:m, m - 6:m) = [0.10054553593153806442e1 -0.40291482544157815088e1 0.60658234020943532065e1 -0.40887380427708663837e1 0.10466075357769140419e1 0 0; 0 0.10539251591207869677e1 -0.42974416973037268190e1 0.67286137322011497757e1 -0.49292485821432017638e1 0.14441513881249918393e1 0; 0 0 0.13166603634797652881e1 -0.59442797107089754133e1 0.11419402572582197931e2 -0.12521994384708052700e2 0.57302111593550648941e1; 0 0 0 0 0 0 0; 0 0 0 0 0 0 0; ];
+    DD_4 = sparse(DD_4);
+
+    DD_5 = (-diag(ones(m - 3, 1), -3) + 5 * diag(ones(m - 2, 1), -2) - 10 * diag(ones(m - 1, 1), -1) + 10 * diag(ones(m, 1), 0) - 5 * diag(ones(m - 1, 1), 1) + diag(ones(m - 2, 1), 2));
+    DD_5(1:6, 1:8) = [0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0; -0.67194556014531368457e1 0.16377214352871472626e2 -0.19171027475746103125e2 0.14860699276772438533e2 -0.65833018173988264407e1 0.12358712649541552519e1 0 0; 0 -0.14971527633959360324e1 0.61951742553920293904e1 -0.11214356220335249626e2 0.10743604243259317047e2 -0.52696257956039348385e1 0.10423562806837740594e1 0; 0 0 -0.10511702536596915242e1 0.51109225534635829797e1 -0.10109705670157255344e2 0.10072870636039453772e2 -0.50272767965769032212e1 0.10043595308908133377e1; ];
+    DD_5(m - 4:m, m - 7:m) = [-0.10043595308908133377e1 0.50272767965769032212e1 -0.10072870636039453772e2 0.10109705670157255344e2 -0.51109225534635829797e1 0.10511702536596915242e1 0 0; 0 -0.10423562806837740594e1 0.52696257956039348385e1 -0.10743604243259317047e2 0.11214356220335249626e2 -0.61951742553920293904e1 0.14971527633959360324e1 0; 0 0 -0.12358712649541552519e1 0.65833018173988264407e1 -0.14860699276772438533e2 0.19171027475746103125e2 -0.16377214352871472626e2 0.67194556014531368457e1; 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0; ];
+    DD_5 = sparse(DD_5);
+
+    DD_6 = (diag(ones(m - 3, 1), 3) - 6 * diag(ones(m - 2, 1), 2) + 15 * diag(ones(m - 1, 1), 1) - 20 * diag(ones(m, 1), 0) + 15 * diag(ones(m - 1, 1), -1) - 6 * diag(ones(m - 2, 1), -2) + diag(ones(m - 3, 1), -3));
+    DD_6(1:6, 1:9) = [0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; 0.76591061528436941127e1 -0.20373923615000091397e2 0.28913418478606999359e2 -0.29721398553544877066e2 0.19749905452196479322e2 -0.74152275897249315116e1 0.11881196746227271813e1 0 0; 0 0.15426631885693469226e1 -0.74666187707188589528e1 0.16821534330502874439e2 -0.21487208486518634095e2 0.15808877386811804515e2 -0.62541376841026443562e1 0.10348900354561115264e1 0; 0 0 0.10549863219420430611e1 -0.61331070641562995756e1 0.15164558505235883016e2 -0.20145741272078907544e2 0.15081830389730709664e2 -0.60261571853448800265e1 0.10036303046714514054e1; ];
+    DD_6(m - 5:m, m - 8:m) = [0.10036303046714514054e1 -0.60261571853448800265e1 0.15081830389730709664e2 -0.20145741272078907544e2 0.15164558505235883016e2 -0.61331070641562995756e1 0.10549863219420430611e1 0 0; 0 0.10348900354561115264e1 -0.62541376841026443562e1 0.15808877386811804515e2 -0.21487208486518634095e2 0.16821534330502874439e2 -0.74666187707188589528e1 0.15426631885693469226e1 0; 0 0 0.11881196746227271813e1 -0.74152275897249315116e1 0.19749905452196479322e2 -0.29721398553544877066e2 0.28913418478606999359e2 -0.20373923615000091397e2 0.76591061528436941127e1; 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; ];
+    DD_6 = sparse(DD_6);
+
+    %%%% Difference operators %%%
+    D1 = H \ Q;
+
+    % Helper functions for constructing D2(c)
+    % TODO: Consider changing sparse(diag(...)) to spdiags(....)
+
+    % Minimal 7 point stencil width
+    function D2 = D2_fun_minimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 1/3 * diag(ones(m - 1, 1), -1) + 1/3 * diag(ones(m - 1, 1), 1) + 1/3 * diag(ones(m, 1), 0); C3(1, 3) = 1/3; C3(m, m - 2) = 1/3;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/3600 / h) * transpose(DD_6) * C1 * DD_6 + (1/600 / h) * transpose(DD_5) * C2 * DD_5 + (1/80 / h) * transpose(DD_4) * C3 * DD_4;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Few additional grid point in interior stencil cmp. to minimal
+    function D2 = D2_fun_nonminimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+
+        C2 = sparse(diag(C2 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/3600 / h) * transpose(DD_6) * C1 * DD_6 + (1/600 / h) * transpose(DD_5) * C2 * DD_5;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Wide stencil
+    function D2 = D2_fun_wide(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        D2 = D1 * C1 * D1;
+    end
+
+    switch options.stencil_width
+        case 'minimal'
+            D2 = @D2_fun_minimal;
+        case 'nonminimal'
+            D2 = @D2_fun_nonminimal;
+        case 'wide'
+            D2 = @D2_fun_wide;
+        otherwise
+            error('No option %s for stencil width', options.stencil_width)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Artificial dissipation operator %%%
+    switch options.AD
+        case 'upwind'
+            % This is the choice that yield 3rd order Upwind
+            DI = H \ (transpose(DD_3) * DD_3) * (-1/60);
+        case 'op'
+            % This choice will preserve the order of the underlying
+            % Non-dissipative D1 SBP operator
+            DI = H \ (transpose(DD_4) * DD_4) * (-1 / (5 * 60));
+            % Notice that you can use any negative number instead of (-1/(5*60))
+        otherwise
+            error("Artificial dissipation options '%s' not implemented.", option.AD)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/+implementations/d2_noneq_variable_8.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,261 @@
+function [H, HI, D1, D2, DI] = d2_noneq_variable_8(N, h, options)
+    % N: Number of grid points
+    % h: grid spacing
+    % options: struct containing options for constructing the operator
+    %          current options are: 
+    %               options.stencil_type ('minimal','nonminimal','wide')
+    %               options.AD ('upwind', 'op')
+
+    % BP: Number of boundary points
+    % order: Accuracy of interior stencil
+    BP = 8;
+    order = 8;
+    if(N<2*BP)
+        error(['Operator requires at least ' num2str(2*BP) ' grid points']);
+    end
+
+    %%%% Norm matrix %%%%%%%%
+    P = zeros(BP, 1);
+    P0 = 1.0758368078310e-01;
+    P1 = 6.1909685107891e-01;
+    P2 = 9.6971176519117e-01;
+    P3 = 1.1023441350947e+00;
+    P4 = 1.0244688965833e+00;
+    P5 = 9.9533550116831e-01;
+    P6 = 1.0008236941028e+00;
+    P7 = 9.9992060631812e-01;
+
+    for i = 0:BP - 1
+        P(i + 1) = eval(['P' num2str(i)]);
+    end
+
+    Hv = ones(N, 1);
+    Hv(1:BP) = P;
+    Hv(end - BP + 1:end) = flip(P);
+    Hv = h * Hv;
+    H = spdiags(Hv, 0, N, N);
+    HI = spdiags(1 ./ Hv, 0, N, N);
+    %%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Q matrix %%%%%%%%%%%
+
+    % interior stencil
+    d = [1/280, -4/105, 1/5, -4/5, 0, 4/5, -1/5, 4/105, -1/280];
+    d = repmat(d, N, 1);
+    Q = spdiags(d, -order / 2:order / 2, N, N);
+
+    % Boundaries
+    Q0_0 = -5.0000000000000e-01;
+    Q0_1 = 6.7284756079369e-01;
+    Q0_2 = -2.5969732837062e-01;
+    Q0_3 = 1.3519390385721e-01;
+    Q0_4 = -6.9678474730984e-02;
+    Q0_5 = 2.6434024071371e-02;
+    Q0_6 = -5.5992311465618e-03;
+    Q0_7 = 4.9954552590464e-04;
+    Q0_8 = 0.0000000000000e+00;
+    Q0_9 = 0.0000000000000e+00;
+    Q0_10 = 0.0000000000000e+00;
+    Q0_11 = 0.0000000000000e+00;
+    Q1_0 = -6.7284756079369e-01;
+    Q1_1 = 0.0000000000000e+00;
+    Q1_2 = 9.4074021172233e-01;
+    Q1_3 = -4.0511642426516e-01;
+    Q1_4 = 1.9369192209331e-01;
+    Q1_5 = -6.8638079843479e-02;
+    Q1_6 = 1.3146457241484e-02;
+    Q1_7 = -9.7652615479254e-04;
+    Q1_8 = 0.0000000000000e+00;
+    Q1_9 = 0.0000000000000e+00;
+    Q1_10 = 0.0000000000000e+00;
+    Q1_11 = 0.0000000000000e+00;
+    Q2_0 = 2.5969732837062e-01;
+    Q2_1 = -9.4074021172233e-01;
+    Q2_2 = 0.0000000000000e+00;
+    Q2_3 = 9.4316393361096e-01;
+    Q2_4 = -3.5728039257451e-01;
+    Q2_5 = 1.1266686855013e-01;
+    Q2_6 = -1.8334941452280e-02;
+    Q2_7 = 8.2741521740941e-04;
+    Q2_8 = 0.0000000000000e+00;
+    Q2_9 = 0.0000000000000e+00;
+    Q2_10 = 0.0000000000000e+00;
+    Q2_11 = 0.0000000000000e+00;
+    Q3_0 = -1.3519390385721e-01;
+    Q3_1 = 4.0511642426516e-01;
+    Q3_2 = -9.4316393361096e-01;
+    Q3_3 = 0.0000000000000e+00;
+    Q3_4 = 8.7694387866575e-01;
+    Q3_5 = -2.4698058719506e-01;
+    Q3_6 = 4.7291642094198e-02;
+    Q3_7 = -4.0135203618880e-03;
+    Q3_8 = 0.0000000000000e+00;
+    Q3_9 = 0.0000000000000e+00;
+    Q3_10 = 0.0000000000000e+00;
+    Q3_11 = 0.0000000000000e+00;
+    Q4_0 = 6.9678474730984e-02;
+    Q4_1 = -1.9369192209331e-01;
+    Q4_2 = 3.5728039257451e-01;
+    Q4_3 = -8.7694387866575e-01;
+    Q4_4 = 0.0000000000000e+00;
+    Q4_5 = 8.1123946853807e-01;
+    Q4_6 = -2.0267150541446e-01;
+    Q4_7 = 3.8680398901392e-02;
+    Q4_8 = -3.5714285714286e-03;
+    Q4_9 = 0.0000000000000e+00;
+    Q4_10 = 0.0000000000000e+00;
+    Q4_11 = 0.0000000000000e+00;
+    Q5_0 = -2.6434024071371e-02;
+    Q5_1 = 6.8638079843479e-02;
+    Q5_2 = -1.1266686855013e-01;
+    Q5_3 = 2.4698058719506e-01;
+    Q5_4 = -8.1123946853807e-01;
+    Q5_5 = 0.0000000000000e+00;
+    Q5_6 = 8.0108544742793e-01;
+    Q5_7 = -2.0088756283071e-01;
+    Q5_8 = 3.8095238095238e-02;
+    Q5_9 = -3.5714285714286e-03;
+    Q5_10 = 0.0000000000000e+00;
+    Q5_11 = 0.0000000000000e+00;
+    Q6_0 = 5.5992311465618e-03;
+    Q6_1 = -1.3146457241484e-02;
+    Q6_2 = 1.8334941452280e-02;
+    Q6_3 = -4.7291642094198e-02;
+    Q6_4 = 2.0267150541446e-01;
+    Q6_5 = -8.0108544742793e-01;
+    Q6_6 = 0.0000000000000e+00;
+    Q6_7 = 8.0039405922650e-01;
+    Q6_8 = -2.0000000000000e-01;
+    Q6_9 = 3.8095238095238e-02;
+    Q6_10 = -3.5714285714286e-03;
+    Q6_11 = 0.0000000000000e+00;
+    Q7_0 = -4.9954552590464e-04;
+    Q7_1 = 9.7652615479254e-04;
+    Q7_2 = -8.2741521740941e-04;
+    Q7_3 = 4.0135203618880e-03;
+    Q7_4 = -3.8680398901392e-02;
+    Q7_5 = 2.0088756283071e-01;
+    Q7_6 = -8.0039405922650e-01;
+    Q7_7 = 0.0000000000000e+00;
+    Q7_8 = 8.0000000000000e-01;
+    Q7_9 = -2.0000000000000e-01;
+    Q7_10 = 3.8095238095238e-02;
+    Q7_11 = -3.5714285714286e-03;
+
+    for i = 1:BP
+
+        for j = 1:BP
+            Q(i, j) = eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+            Q(N + 1 - i, N + 1 - j) = -eval(['Q' num2str(i - 1) '_' num2str(j - 1)]);
+        end
+
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%% Undivided difference operators %%%%
+    % Closed with zeros at the first boundary nodes.
+    m = N;
+
+    DD_4 = (diag(ones(m - 2, 1), 2) - 4 * diag(ones(m - 1, 1), 1) + 6 * diag(ones(m, 1), 0) - 4 * diag(ones(m - 1, 1), -1) + diag(ones(m - 2, 1), -2));
+    DD_4(1:6, 1:8) = [0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0; 0.70921010190504348684e1 -0.14196080536361841322e2 0.11072881931325435634e2 -0.50473576941871051066e1 0.10784552801730759259e1 0 0 0; 0 0.13740993382151221352e1 -0.42105600869792757010e1 0.54761010136211975317e1 -0.35797005751940657417e1 0.94006031033702177578e0 0 0; 0 0 0.82467928104463767301e0 -0.33274694995849432461e1 0.52587584638857303123e1 -0.37020511582893568152e1 0.94608291294393207601e0 0; 0 0 0 0.86436129166612654748e0 -0.37325441295306179390e1 0.57924699560798105338e1 -0.39066885960487908497e1 0.98240147783347170744e0; ];
+    DD_4(m - 5:m, m - 7:m) = [0.98240147783347170744e0 -0.39066885960487908497e1 0.57924699560798105338e1 -0.37325441295306179390e1 0.86436129166612654748e0 0 0 0; 0 0.94608291294393207601e0 -0.37020511582893568152e1 0.52587584638857303123e1 -0.33274694995849432461e1 0.82467928104463767301e0 0 0; 0 0 0.94006031033702177578e0 -0.35797005751940657417e1 0.54761010136211975317e1 -0.42105600869792757010e1 0.13740993382151221352e1 0; 0 0 0 0.10784552801730759259e1 -0.50473576941871051066e1 0.11072881931325435634e2 -0.14196080536361841322e2 0.70921010190504348684e1; 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0; ];
+    DD_4 = sparse(DD_4);
+
+    DD_5 = (-diag(ones(m - 3, 1), -3) + 5 * diag(ones(m - 2, 1), -2) - 10 * diag(ones(m - 1, 1), -1) + 10 * diag(ones(m, 1), 0) - 5 * diag(ones(m - 1, 1), 1) + diag(ones(m - 2, 1), 2));
+    DD_5(1:7, 1:9) = [0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; -0.82098088052411907132e1 0.18024024120655590699e2 -0.17692096674769448317e2 0.12181944917152947702e2 -0.53922764008653796295e1 0.10882128430674802600e1 0 0 0; 0 -0.13913240333052950751e1 0.50983574122643719988e1 -0.89139255753021486176e1 0.89492514379851643542e1 -0.47003015516851088789e1 0.95794231004301621873e0 0 0; 0 0 -0.80388595981635823711e0 0.40861386922975635215e1 -0.87645974398095505205e1 0.92551278957233920380e1 -0.47304145647196603800e1 0.95763137632461357808e0 0; 0 0 0 -0.85214912336218661144e0 0.46656801619132724238e1 -0.96541165934663508896e1 0.97667214901219771242e1 -0.49120073891673585372e1 0.98587145396064649030e0; ];
+    DD_5(m - 5:m, m - 8:m) = [-0.98587145396064649030e0 0.49120073891673585372e1 -0.97667214901219771242e1 0.96541165934663508896e1 -0.46656801619132724238e1 0.85214912336218661144e0 0 0 0; 0 -0.95763137632461357808e0 0.47304145647196603800e1 -0.92551278957233920380e1 0.87645974398095505205e1 -0.40861386922975635215e1 0.80388595981635823711e0 0 0; 0 0 -0.95794231004301621873e0 0.47003015516851088789e1 -0.89492514379851643542e1 0.89139255753021486176e1 -0.50983574122643719988e1 0.13913240333052950751e1 0; 0 0 0 -0.10882128430674802600e1 0.53922764008653796295e1 -0.12181944917152947702e2 0.17692096674769448317e2 -0.18024024120655590699e2 0.82098088052411907132e1; 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0; ];
+    DD_5 = sparse(DD_5);
+
+    DD_6 = (diag(ones(m - 3, 1), 3) - 6 * diag(ones(m - 2, 1), 2) + 15 * diag(ones(m - 1, 1), 1) - 20 * diag(ones(m, 1), 0) + 15 * diag(ones(m - 1, 1), -1) - 6 * diag(ones(m - 2, 1), -2) + diag(ones(m - 3, 1), -3));
+    DD_6(1:7, 1:10) = [0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; 0.92604272237009487731e1 -0.21899951980342041757e2 0.25706973995952182034e2 -0.23795532642767976509e2 0.16176829202596138889e2 -0.65292770584048815597e1 0.10805312592656301300e1 0 0 0; 0 0.14058275749850312569e1 -0.59637699688344396810e1 0.13135580487711615439e2 -0.17898502875970328708e2 0.14100904655055326637e2 -0.57476538602580973124e1 0.96761398731089236944e0 0 0; 0 0 0.78692381135906040550e0 -0.48340889923923043812e1 0.13146896159714325781e2 -0.18510255791446784076e2 0.14191243694158981140e2 -0.57457882579476814685e1 0.96506937655440259938e0 0; 0 0 0 0.84209241882516286974e0 -0.55988161942959269085e1 0.14481174890199526334e2 -0.19533442980243954248e2 0.14736022167502075612e2 -0.59152287237638789418e1 0.98819842177699528293e0; ];
+    DD_6(m - 6:m, m - 9:m) = [0.98819842177699528293e0 -0.59152287237638789418e1 0.14736022167502075612e2 -0.19533442980243954248e2 0.14481174890199526334e2 -0.55988161942959269085e1 0.84209241882516286974e0 0 0 0; 0 0.96506937655440259938e0 -0.57457882579476814685e1 0.14191243694158981140e2 -0.18510255791446784076e2 0.13146896159714325781e2 -0.48340889923923043812e1 0.78692381135906040550e0 0 0; 0 0 0.96761398731089236944e0 -0.57476538602580973124e1 0.14100904655055326637e2 -0.17898502875970328708e2 0.13135580487711615439e2 -0.59637699688344396810e1 0.14058275749850312569e1 0; 0 0 0 0.10805312592656301300e1 -0.65292770584048815597e1 0.16176829202596138889e2 -0.23795532642767976509e2 0.25706973995952182034e2 -0.21899951980342041757e2 0.92604272237009487731e1; 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0; ];
+    DD_6 = sparse(DD_6);
+
+    DD_7 = (-diag(ones(m - 4, 1), -4) + 7 * diag(ones(m - 3, 1), -3) - 21 * diag(ones(m - 2, 1), -2) + 35 * diag(ones(m - 1, 1), -1) - 35 * diag(ones(m, 1), 0) + 21 * diag(ones(m - 1, 1), 1) - 7 * diag(ones(m - 2, 1), 2) + diag(ones(m - 3, 1), 3));
+    DD_7(1:8, 1:11) = [0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; -0.10257962606384161889e2 0.25816283570514673553e2 -0.35082323899232605336e2 0.40909341259657284245e2 -0.37745934806057657407e2 0.22852469704417085459e2 -0.75637188148594109098e1 0.10718455919447922848e1 0 0 0; 0 -0.14183700945143243060e1 0.68109221539151178574e1 -0.18129991367652287552e2 0.31322380032948075240e2 -0.32902110861795762152e2 0.20116788510903340593e2 -0.67732979111762465861e1 0.97367953737208690559e0 0 0; 0 0 -0.77264857229409862775e0 0.55732122985135573041e1 -0.18405654623600056093e2 0.32392947635031872133e2 -0.33112901953037622660e2 0.20110258902816885140e2 -0.67554856358808181957e1 0.97027194845028099974e0 0; 0 0 0 -0.83355973075426177031e0 0.65319522266785813933e1 -0.20273644846279336868e2 0.34183525215426919935e2 -0.34384051724171509761e2 0.20703300533173576296e2 -0.69173889524389669805e1 0.98986727836499775519e0; ];
+    DD_7(m - 6:m, m - 10:m) = [-0.98986727836499775519e0 0.69173889524389669805e1 -0.20703300533173576296e2 0.34384051724171509761e2 -0.34183525215426919935e2 0.20273644846279336868e2 -0.65319522266785813933e1 0.83355973075426177031e0 0 0 0; 0 -0.97027194845028099974e0 0.67554856358808181957e1 -0.20110258902816885140e2 0.33112901953037622660e2 -0.32392947635031872133e2 0.18405654623600056093e2 -0.55732122985135573041e1 0.77264857229409862775e0 0 0; 0 0 -0.97367953737208690559e0 0.67732979111762465861e1 -0.20116788510903340593e2 0.32902110861795762152e2 -0.31322380032948075240e2 0.18129991367652287552e2 -0.68109221539151178574e1 0.14183700945143243060e1 0; 0 0 0 -0.10718455919447922848e1 0.75637188148594109098e1 -0.22852469704417085459e2 0.37745934806057657407e2 -0.40909341259657284245e2 0.35082323899232605336e2 -0.25816283570514673553e2 0.10257962606384161889e2; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_7 = sparse(DD_7);
+
+    DD_8 = (diag(ones(m - 4, 1), 4) - 8 * diag(ones(m - 3, 1), 3) + 28 * diag(ones(m - 2, 1), 2) - 56 * diag(ones(m - 1, 1), 1) + 70 * diag(ones(m, 1), 0) - 56 * diag(ones(m - 1, 1), -1) + 28 * diag(ones(m - 2, 1), -2) - 8 * diag(ones(m - 3, 1), -3) + diag(ones(m - 4, 1), -4));
+    DD_8(1:8, 1:12) = [0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0.11211983054345146839e2 -0.29767555907566203748e2 0.45789440151310044599e2 -0.64530162797168947524e2 0.75491869612115314813e2 -0.60939919211778894557e2 0.30254875259437643639e2 -0.85747647355583382784e1 0.10642345748642342173e1 0 0 0; 0 0.14294303785648149613e1 -0.76427065234692260122e1 0.23888038475126051744e2 -0.50115808052716920383e2 0.65804221723591524304e2 -0.53644769362408908249e2 0.27093191644704986344e2 -0.77894362989766952447e1 0.97783801558437253538e0 0 0; 0 0 0.76035645561041265933e0 -0.63048462739199410204e1 0.24540872831466741457e2 -0.51828716216050995413e2 0.66225803906075245320e2 -0.53627357074178360373e2 0.27021942543523272783e2 -0.77621755876022479980e1 0.97411941507587258409e0 0; 0 0 0 0.82615990808320718845e0 -0.74650882590612358780e1 0.27031526461705782491e2 -0.54693640344683071895e2 0.68768103448343019521e2 -0.55208801421796203457e2 0.27669555809755867922e2 -0.79189382269199820415e1 0.99112262457261614959e0; ];
+    DD_8(m - 7:m, m - 11:m) = [0.99112262457261614959e0 -0.79189382269199820415e1 0.27669555809755867922e2 -0.55208801421796203457e2 0.68768103448343019521e2 -0.54693640344683071895e2 0.27031526461705782491e2 -0.74650882590612358780e1 0.82615990808320718845e0 0 0 0; 0 0.97411941507587258409e0 -0.77621755876022479980e1 0.27021942543523272783e2 -0.53627357074178360373e2 0.66225803906075245320e2 -0.51828716216050995413e2 0.24540872831466741457e2 -0.63048462739199410204e1 0.76035645561041265933e0 0 0; 0 0 0.97783801558437253538e0 -0.77894362989766952447e1 0.27093191644704986344e2 -0.53644769362408908249e2 0.65804221723591524304e2 -0.50115808052716920383e2 0.23888038475126051744e2 -0.76427065234692260122e1 0.14294303785648149613e1 0; 0 0 0 0.10642345748642342173e1 -0.85747647355583382784e1 0.30254875259437643639e2 -0.60939919211778894557e2 0.75491869612115314813e2 -0.64530162797168947524e2 0.45789440151310044599e2 -0.29767555907566203748e2 0.11211983054345146839e2; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; 0 0 0 0 0 0 0 0 0 0 0 0; ];
+    DD_8 = sparse(DD_8);
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Difference operators %%
+    D1 = H \ Q;
+
+    % Helper functions for constructing D2(c)
+    % TODO: Consider changing sparse(diag(...)) to spdiags(....)
+
+    % Minimal 9 point stencil width
+    function D2 = D2_fun_minimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 3/10 * diag(ones(m - 1, 1), -1) + 3/10 * diag(ones(m - 1, 1), 1) + 2/5 * diag(ones(m, 1), 0); C3(1, 3) = 3/10; C3(m, m - 2) = 3/10;
+        C4 = 1/4 * diag(ones(m - 2, 1), -2) + 1/4 * diag(ones(m - 1, 1), -1) + 1/4 * diag(ones(m - 1, 1), 1) + 1/4 * diag(ones(m, 1), 0); C4(2, 4) = 1/4; C4(1, 3) = 1/4; C4(1, 4) = 1/4; C4(m, m - 2) = 1/4; C4(m, m - 3) = 1/4;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+        C4 = sparse(diag(C4 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/78400 / h) * transpose(DD_8) * C1 * DD_8 + (1/14700 / h) * transpose(DD_7) * C2 * DD_7 + (1/2520 / h) * transpose(DD_6) * C3 * DD_6 + (1/350 / h) * transpose(DD_5) * C4 * DD_5;
+
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Few additional grid point in interior stencil cmp. to minimal
+    function D2 = D2_fun_nonminimal(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        C2 = 1/2 * diag(ones(m - 1, 1), -1) + 1/2 * diag(ones(m, 1), 0); C2(1, 2) = 1/2;
+        C3 = 3/10 * diag(ones(m - 1, 1), -1) + 3/10 * diag(ones(m - 1, 1), 1) + 2/5 * diag(ones(m, 1), 0); C3(1, 3) = 3/10; C3(m, m - 2) = 3/10;
+
+        C2 = sparse(diag(C2 * c));
+        C3 = sparse(diag(C3 * c));
+
+        % Remainder term added to wide second derivative operator
+        R = (1/78400 / h) * transpose(DD_8) * C1 * DD_8 + (1/14700 / h) * transpose(DD_7) * C2 * DD_7 + (1/2520 / h) * transpose(DD_6) * C3 * DD_6;
+        D2 = D1 * C1 * D1 - H \ R;
+    end
+
+    % Wide stencil
+    function D2 = D2_fun_wide(c)
+        % Here we add variable diffusion
+        C1 = sparse(diag(c));
+        D2 = D1 * C1 * D1;
+    end
+
+    switch options.stencil_width
+        case 'minimal'
+            D2 = @D2_fun_minimal;
+        case 'nonminimal'
+            D2 = @D2_fun_nonminimal;
+        case 'wide'
+            D2 = @D2_fun_wide;
+        otherwise
+            error('No option %s for stencil width', options.stencil_width)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+    %%%% Artificial dissipation operator %%%
+    switch options.AD
+        case 'upwind'
+            % This is the choice that yield 7th order Upwind
+            DI = H \ (transpose(DD_4) * DD_4) * (-1/280);
+        case 'op'
+            % This choice will preserve the order of the underlying
+            % Non-dissipative D1 SBP operator
+            DI = H \ (transpose(DD_5) * DD_5) * (-1 / (5 * 280));
+            % Notice that you can use any negative number instead of (-1/(5*280))
+        otherwise
+            error("Artificial dissipation options '%s' not implemented.", option.AD)
+    end
+
+    %%%%%%%%%%%%%%%%%%%%%%%%%%%
+end
\ No newline at end of file
--- a/+sbp/D1Nonequidistant.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/D1Nonequidistant.m	Thu Mar 10 16:54:26 2022 +0100
@@ -19,58 +19,53 @@
             % 'Accurate' operators are optimized for accuracy
             % 'Minimal' operators have the smallest possible boundary
             %  closure
-
-            x_l = lim{1};
-            x_r = lim{2};
-            L = x_r-x_l;
-
             switch option
 
                 case {'Accurate','accurate','A'}
-
+                    [x,h] = sbp.grid.accurateBoundaryOptimizedGrid(lim,m,order);
                     if order == 4
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_4(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_4(m,h);
                     elseif order == 6
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_6(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_6(m,h);
                     elseif order == 8
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_8(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_8(m,h);
                     elseif order == 10
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_10(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_10(m,h);
                     elseif order == 12
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_12(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_12(m,h);
                     else
                         error('Invalid operator order %d.',order);
                     end
 
                 case {'Minimal','minimal','M'}
-
+                    [x,h] = sbp.grid.minimalBoundaryOptimizedGrid(lim,m,order);
                     if order == 4
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_minimal_4(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_minimal_4(m,h);
                     elseif order == 6
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_minimal_6(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_minimal_6(m,h);
                     elseif order == 8
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_minimal_8(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_minimal_8(m,h);
                     elseif order == 10
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_minimal_10(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_minimal_10(m,h);
                     elseif order == 12
-                        [obj.D1,obj.H,obj.x,obj.h] = ...
-                            sbp.implementations.d1_noneq_minimal_12(m,L);
+                        [obj.D1,obj.H] = ...
+                            sbp.implementations.d1_noneq_minimal_12(m,h);
                     else
                         error('Invalid operator order %d.',order);
                     end
 
             end
-
-            obj.x = obj.x + x_l;
+            obj.h = h;
+            obj.x = x;
 
             obj.e_l = sparse(m,1);
             obj.e_r = sparse(m,1);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+sbp/D2Nonequidistant.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,93 @@
+classdef D2Nonequidistant < sbp.OpSet
+    % Implements the boundary optimized variable coefficient
+    % second derivative.
+    %
+    % The boundary closure uses the first and last rows of the
+    % boundary-optimized D1 operator, i.e. the operators are
+    % fully compatible.
+    properties
+        H % Norm matrix
+        HI % H^-1
+        D1 % SBP operator approximating first derivative
+        D2 % SBP operator approximating second derivative
+        DI % Dissipation operator
+        e_l % Left boundary operator
+        e_r % Right boundary operator
+        d1_l % Left boundary first derivative
+        d1_r % Right boundary first derivative
+        m % Number of grid points.
+        h % Step size
+        x % grid
+        borrowing % Struct with borrowing limits for different norm matrices
+        options % Struct holding options used to create the operator
+    end
+
+    methods
+
+        function obj = D2Nonequidistant(m, lim, order, options)
+            % m - number of gridpoints
+            % lim - cell array holding the limits of the domain
+            % order - order of the operator
+            % options - struct holding options used to construct the operator
+            %           struct.stencil_width:   {'minimal', 'nonminimal', 'wide'}
+            %               minimal: minimal compatible stencil width (default)
+            %               nonminimal: a few additional stencil points compared to minimal
+            %               wide: wide stencil obtained by applying D1 twice
+            %           struct.AD: {'op', 'upwind'}
+            %               'op': order-preserving AD (preserving interior stencil order) (default)
+            %               'upwind': upwind AD (order-1 upwind interior stencil)
+            %           struct.variable_coeffs: {true, false}
+            %               true: obj.D2 is a function handle D2(c) returning a matrix 
+            %                     for coefficient vector c (default)
+            %               false: obj.D2 is a matrix.
+            default_arg('options', struct);
+            default_field(options,'stencil_width','minimal');
+            default_field(options,'AD','op');
+            default_field(options,'variable_coeffs',true);
+            [x, h] = sbp.grid.accurateBoundaryOptimizedGrid(lim, m, order);
+            switch order
+                case 4
+                    [obj.H, obj.HI, obj.D1, obj.D2, obj.DI] = sbp.implementations.d2_noneq_variable_4(m, h, options);
+                case 6
+                    [obj.H, obj.HI, obj.D1, obj.D2, obj.DI] = sbp.implementations.d2_noneq_variable_6(m, h, options);
+                case 8
+                    [obj.H, obj.HI, obj.D1, obj.D2, obj.DI] = sbp.implementations.d2_noneq_variable_8(m, h, options);
+                case 10
+                    [obj.H, obj.HI, obj.D1, obj.D2, obj.DI] = sbp.implementations.d2_noneq_variable_10(m, h, options);
+                case 12
+                    [obj.H, obj.HI, obj.D1, obj.D2, obj.DI] = sbp.implementations.d2_noneq_variable_12(m, h, options);
+                otherwise
+                    error('Invalid operator order %d.', order);
+            end
+
+            if ~options.variable_coeffs
+                obj.D2 = obj.D2(ones(m,1));
+            end
+
+            % Boundary operators
+            obj.e_l = sparse(m, 1); obj.e_l(1) = 1;
+            obj.e_r = sparse(m, 1); obj.e_r(m) = 1;
+            obj.d1_l = (obj.e_l' * obj.D1)';
+            obj.d1_r = (obj.e_r' * obj.D1)';
+
+            % Borrowing coefficients
+            obj.borrowing.H11 = obj.H(1, 1) / h; % First element in H/h,
+            obj.borrowing.M.d1 = obj.H(1, 1) / h; % First element in H/h is borrowing also for M
+            obj.borrowing.R.delta_D = inf;
+
+            % grid data
+            obj.x = x;
+            obj.h = h;
+            obj.m = m;
+
+            % misc
+            obj.options = options;
+        end
+
+        function str = string(obj)
+            str = [class(obj) '_' num2str(obj.order)];
+        end
+
+    end
+
+end
--- a/+sbp/D2VariableCompatible.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+sbp/D2VariableCompatible.m	Thu Mar 10 16:54:26 2022 +0100
@@ -59,8 +59,10 @@
 
             % D2 = Hinv * (-M + br*er*d1r^T - bl*el*d1l^T);
             % Replace d1' by e'*D1 in D2.
-            D2_compatible = @(b) D2(b) - obj.HI*(b(m)*e_r*d1_r' - b(m)*e_r*e_r'*D1) ...
-                                       + obj.HI*(b(1)*e_l*d1_l' - b(1)*e_l*e_l'*D1);
+            correction_l = obj.HI*(e_l*d1_l' - e_l*e_l'*D1);
+            correction_r = - obj.HI*(e_r*d1_r' - e_r*e_r'*D1);
+
+            D2_compatible = @(b) D2(b) + b(1)*correction_l + b(m)*correction_r;
 
             obj.D2 = D2_compatible;
             obj.d1_l = (e_l'*D1)';
--- a/+scheme/Beam.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Beam.m	Thu Mar 10 16:54:26 2022 +0100
@@ -86,7 +86,11 @@
         function [closure, penalty] = boundary_condition(obj,boundary,type)
             default_arg('type','dn');
 
-            [e, d1, d2, d3, s] = obj.get_boundary_ops(boundary);
+            e  = obj.getBoundaryOperator('e',  boundary);
+            d1 = obj.getBoundaryOperator('d1', boundary);
+            d2 = obj.getBoundaryOperator('d2', boundary);
+            d3 = obj.getBoundaryOperator('d3', boundary);
+            s = obj.getBoundarySign(boundary);
             gamm = obj.gamm;
             delt = obj.delt;
 
@@ -124,7 +128,7 @@
 
                     closure = obj.Hi*(tau*d2' + sig*d3');
                     penalty{1} = -obj.Hi*tau;
-                    penalty{1} = -obj.Hi*sig;
+                    penalty{2} = -obj.Hi*sig;
 
                 case 'e'
                     alpha = obj.alpha;
@@ -173,14 +177,21 @@
         function [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary, type)
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
-            [e_u,d1_u,d2_u,d3_u,s_u] = obj.get_boundary_ops(boundary);
-            [e_v,d1_v,d2_v,d3_v,s_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
+            e_u  = obj.getBoundaryOperator('e',  boundary);
+            d1_u = obj.getBoundaryOperator('d1', boundary);
+            d2_u = obj.getBoundaryOperator('d2', boundary);
+            d3_u = obj.getBoundaryOperator('d3', boundary);
+            s_u = obj.getBoundarySign(boundary);
 
+            e_v  = neighbour_scheme.getBoundaryOperator('e',  neighbour_boundary);
+            d1_v = neighbour_scheme.getBoundaryOperator('d1', neighbour_boundary);
+            d2_v = neighbour_scheme.getBoundaryOperator('d2', neighbour_boundary);
+            d3_v = neighbour_scheme.getBoundaryOperator('d3', neighbour_boundary);
+            s_v = neighbour_scheme.getBoundarySign(neighbour_boundary);
 
             alpha_u = obj.alpha;
             alpha_v = neighbour_scheme.alpha;
 
-
             switch boundary
                 case 'l'
                     interface_opt = obj.opt.interface_l;
@@ -234,24 +245,37 @@
             penalty = -obj.Hi*(tau*e_v' + sig*d1_v' + phi*alpha_v*d2_v' + psi*alpha_v*d3_v');
         end
 
-        % Returns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e, d1, d2, d3, s] = get_boundary_ops(obj,boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string
+        % boundary  -- string
+        function o = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(op, {'e', 'd1', 'd2', 'd3'})
+            assertIsMember(boundary, {'l', 'r'})
+
+            o = obj.([op, '_', boundary]);
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        % Note: for 1d diffOps, the boundary quadrature is the scalar 1.
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            H_b = 1;
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
             switch boundary
-                case 'l'
-                    e  = obj.e_l;
-                    d1 = obj.d1_l;
-                    d2 = obj.d2_l;
-                    d3 = obj.d3_l;
+                case {'r'}
+                    s = 1;
+                case {'l'}
                     s = -1;
-                case 'r'
-                    e  = obj.e_r;
-                    d1 = obj.d1_r;
-                    d2 = obj.d2_r;
-                    d3 = obj.d3_r;
-                    s = 1;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
             end
         end
 
--- a/+scheme/Beam2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,245 +0,0 @@
-classdef Beam2d < scheme.Scheme
-    properties
-        grid
-        order % Order accuracy for the approximation
-
-        D % non-stabalized scheme operator
-        M % Derivative norm
-        alpha
-
-        H % Discrete norm
-        Hi
-        H_x, H_y % Norms in the x and y directions
-        Hx,Hy % Kroneckerd norms. 1'*Hx*v corresponds to integration in the x dir.
-        Hi_x, Hi_y
-        Hix, Hiy
-        e_w, e_e, e_s, e_n
-        d1_w, d1_e, d1_s, d1_n
-        d2_w, d2_e, d2_s, d2_n
-        d3_w, d3_e, d3_s, d3_n
-        gamm_x, gamm_y
-        delt_x, delt_y
-    end
-
-    methods
-        function obj = Beam2d(m,lim,order,alpha,opsGen)
-            default_arg('alpha',1);
-            default_arg('opsGen',@sbp.Higher);
-
-            if ~isa(grid, 'grid.Cartesian') || grid.D() ~= 2
-                error('Grid must be 2d cartesian');
-            end
-
-            obj.grid = grid;
-            obj.alpha = alpha;
-            obj.order = order;
-
-            m_x = grid.m(1);
-            m_y = grid.m(2);
-
-            h = grid.scaling();
-            h_x = h(1);
-            h_y = h(2);
-
-            ops_x = opsGen(m_x,h_x,order);
-            ops_y = opsGen(m_y,h_y,order);
-
-            I_x = speye(m_x);
-            I_y = speye(m_y);
-
-            D4_x = sparse(ops_x.derivatives.D4);
-            H_x =  sparse(ops_x.norms.H);
-            Hi_x = sparse(ops_x.norms.HI);
-            e_l_x = sparse(ops_x.boundary.e_1);
-            e_r_x = sparse(ops_x.boundary.e_m);
-            d1_l_x = sparse(ops_x.boundary.S_1);
-            d1_r_x = sparse(ops_x.boundary.S_m);
-            d2_l_x  = sparse(ops_x.boundary.S2_1);
-            d2_r_x  = sparse(ops_x.boundary.S2_m);
-            d3_l_x  = sparse(ops_x.boundary.S3_1);
-            d3_r_x  = sparse(ops_x.boundary.S3_m);
-
-            D4_y = sparse(ops_y.derivatives.D4);
-            H_y =  sparse(ops_y.norms.H);
-            Hi_y = sparse(ops_y.norms.HI);
-            e_l_y = sparse(ops_y.boundary.e_1);
-            e_r_y = sparse(ops_y.boundary.e_m);
-            d1_l_y = sparse(ops_y.boundary.S_1);
-            d1_r_y = sparse(ops_y.boundary.S_m);
-            d2_l_y  = sparse(ops_y.boundary.S2_1);
-            d2_r_y  = sparse(ops_y.boundary.S2_m);
-            d3_l_y  = sparse(ops_y.boundary.S3_1);
-            d3_r_y  = sparse(ops_y.boundary.S3_m);
-
-
-            D4 = kr(D4_x, I_y) + kr(I_x, D4_y);
-
-            % Norms
-            obj.H = kr(H_x,H_y);
-            obj.Hx  = kr(H_x,I_x);
-            obj.Hy  = kr(I_x,H_y);
-            obj.Hix = kr(Hi_x,I_y);
-            obj.Hiy = kr(I_x,Hi_y);
-            obj.Hi = kr(Hi_x,Hi_y);
-
-            % Boundary operators
-            obj.e_w  = kr(e_l_x,I_y);
-            obj.e_e  = kr(e_r_x,I_y);
-            obj.e_s  = kr(I_x,e_l_y);
-            obj.e_n  = kr(I_x,e_r_y);
-            obj.d1_w = kr(d1_l_x,I_y);
-            obj.d1_e = kr(d1_r_x,I_y);
-            obj.d1_s = kr(I_x,d1_l_y);
-            obj.d1_n = kr(I_x,d1_r_y);
-            obj.d2_w = kr(d2_l_x,I_y);
-            obj.d2_e = kr(d2_r_x,I_y);
-            obj.d2_s = kr(I_x,d2_l_y);
-            obj.d2_n = kr(I_x,d2_r_y);
-            obj.d3_w = kr(d3_l_x,I_y);
-            obj.d3_e = kr(d3_r_x,I_y);
-            obj.d3_s = kr(I_x,d3_l_y);
-            obj.d3_n = kr(I_x,d3_r_y);
-
-            obj.D = alpha*D4;
-
-            obj.gamm_x = h_x*ops_x.borrowing.N.S2/2;
-            obj.delt_x = h_x^3*ops_x.borrowing.N.S3/2;
-
-            obj.gamm_y = h_y*ops_y.borrowing.N.S2/2;
-            obj.delt_y = h_y^3*ops_y.borrowing.N.S3/2;
-        end
-
-
-        % Closure functions return the opertors applied to the own doamin to close the boundary
-        % Penalty functions return the opertors to force the solution. In the case of an interface it returns the operator applied to the other doamin.
-        %       boundary            is a string specifying the boundary e.g. 'l','r' or 'e','w','n','s'.
-        %       type                is a string specifying the type of boundary condition if there are several.
-        %       data                is a function returning the data that should be applied at the boundary.
-        %       neighbour_scheme    is an instance of Scheme that should be interfaced to.
-        %       neighbour_boundary  is a string specifying which boundary to interface to.
-        function [closure, penalty_e,penalty_d] = boundary_condition(obj,boundary,type,data)
-            default_arg('type','dn');
-            default_arg('data',0);
-
-            [e,d1,d2,d3,s,gamm,delt,halfnorm_inv] = obj.get_boundary_ops(boundary);
-
-            switch type
-                % Dirichlet-neumann boundary condition
-                case {'dn'}
-                    alpha = obj.alpha;
-
-                    % tau1 < -alpha^2/gamma
-                    tuning = 1.1;
-
-                    tau1 = tuning * alpha/delt;
-                    tau4 = s*alpha;
-
-                    sig2 = tuning * alpha/gamm;
-                    sig3 = -s*alpha;
-
-                    tau = tau1*e+tau4*d3;
-                    sig = sig2*d1+sig3*d2;
-
-                    closure = halfnorm_inv*(tau*e' + sig*d1');
-
-                    pp_e = halfnorm_inv*tau;
-                    pp_d = halfnorm_inv*sig;
-                    switch class(data)
-                        case 'double'
-                            penalty_e = pp_e*data;
-                            penalty_d = pp_d*data;
-                        case 'function_handle'
-                            penalty_e = @(t)pp_e*data(t);
-                            penalty_d = @(t)pp_d*data(t);
-                        otherwise
-                            error('Wierd data argument!')
-                    end
-
-                % Unknown, boundary condition
-                otherwise
-                    error('No such boundary condition: type = %s',type);
-            end
-        end
-
-        function [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary, type)
-            % u denotes the solution in the own domain
-            % v denotes the solution in the neighbour domain
-            [e_u,d1_u,d2_u,d3_u,s_u,gamm_u,delt_u, halfnorm_inv] = obj.get_boundary_ops(boundary);
-            [e_v,d1_v,d2_v,d3_v,s_v,gamm_v,delt_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
-
-            tuning = 2;
-
-            alpha_u = obj.alpha;
-            alpha_v = neighbour_scheme.alpha;
-
-            tau1 = ((alpha_u/2)/delt_u + (alpha_v/2)/delt_v)/2*tuning;
-            % tau1 = (alpha_u/2 + alpha_v/2)/(2*delt_u)*tuning;
-            tau4 = s_u*alpha_u/2;
-
-            sig2 = ((alpha_u/2)/gamm_u + (alpha_v/2)/gamm_v)/2*tuning;
-            sig3 = -s_u*alpha_u/2;
-
-            phi2 = s_u*1/2;
-
-            psi1 = -s_u*1/2;
-
-            tau = tau1*e_u  +                     tau4*d3_u;
-            sig =           sig2*d1_u + sig3*d2_u          ;
-            phi =           phi2*d1_u                      ;
-            psi = psi1*e_u                                 ;
-
-            closure =  halfnorm_inv*(tau*e_u' + sig*d1_u' + phi*alpha_u*d2_u' + psi*alpha_u*d3_u');
-            penalty = -halfnorm_inv*(tau*e_v' + sig*d1_v' + phi*alpha_v*d2_v' + psi*alpha_v*d3_v');
-        end
-
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e,d1,d2,d3,s,gamm, delt, halfnorm_inv] = get_boundary_ops(obj,boundary)
-            switch boundary
-                case 'w'
-                    e  = obj.e_w;
-                    d1 = obj.d1_w;
-                    d2 = obj.d2_w;
-                    d3 = obj.d3_w;
-                    s = -1;
-                    gamm = obj.gamm_x;
-                    delt = obj.delt_x;
-                    halfnorm_inv = obj.Hix;
-                case 'e'
-                    e  = obj.e_e;
-                    d1 = obj.d1_e;
-                    d2 = obj.d2_e;
-                    d3 = obj.d3_e;
-                    s = 1;
-                    gamm = obj.gamm_x;
-                    delt = obj.delt_x;
-                    halfnorm_inv = obj.Hix;
-                case 's'
-                    e  = obj.e_s;
-                    d1 = obj.d1_s;
-                    d2 = obj.d2_s;
-                    d3 = obj.d3_s;
-                    s = -1;
-                    gamm = obj.gamm_y;
-                    delt = obj.delt_y;
-                    halfnorm_inv = obj.Hiy;
-                case 'n'
-                    e  = obj.e_n;
-                    d1 = obj.d1_n;
-                    d2 = obj.d2_n;
-                    d3 = obj.d3_n;
-                    s = 1;
-                    gamm = obj.gamm_y;
-                    delt = obj.delt_y;
-                    halfnorm_inv = obj.Hiy;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
-            end
-        end
-
-        function N = size(obj)
-            N = prod(obj.m);
-        end
-
-    end
-end
--- a/+scheme/Elastic2dCurvilinear.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Elastic2dCurvilinear.m	Thu Mar 10 16:54:26 2022 +0100
@@ -387,7 +387,7 @@
 
             % j is the coordinate direction of the boundary
             j = obj.get_boundary_number(boundary);
-            [e, T, tau, H_gamma] = obj.get_boundary_operator({'e','T','tau','H'}, boundary);
+            [e, T, tau, H_gamma] = obj.getBoundaryOperator({'e','T','tau','H'}, boundary);
 
             E = obj.E;
             Hi = obj.Hi;
@@ -457,8 +457,8 @@
             j_v = neighbour_scheme.get_boundary_number(neighbour_boundary);
 
             % Get boundary operators
-            [e, T, tau, H_gamma] = obj.get_boundary_operator({'e','T','tau','H'}, boundary);
-            [e_v, tau_v] = neighbour_scheme.get_boundary_operator({'e','tau'}, neighbour_boundary);
+            [e, T, tau, H_gamma] = obj.getBoundaryOperator({'e','T','tau','H'}, boundary);
+            [e_v, tau_v] = neighbour_scheme.getBoundaryOperator({'e','tau'}, neighbour_boundary);
 
             % Operators and quantities that correspond to the own domain only
             Hi = obj.Hi;
@@ -555,7 +555,7 @@
 
         % Returns the boundary operator op for the boundary specified by the string boundary.
         % op: may be a cell array of strings
-        function [varargout] = get_boundary_operator(obj, op, boundary)
+        function [varargout] = getBoundaryOperator(obj, op, boundary)
 
             switch boundary
                 case {'w','W','west','West', 'e', 'E', 'east', 'East'}
@@ -614,6 +614,27 @@
 
         end
 
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'w'}
+                    H = H_boundary_l{1};
+                case 'e'
+                    H = H_boundary_r{1};
+                case 's'
+                    H = H_boundary_l{2};
+                case 'n'
+                    H = H_boundary_r{2};
+            end
+            I_dim = speye(obj.dim, obj.dim);
+            H = kron(H, I_dim);
+        end
+
         function N = size(obj)
             N = obj.dim*prod(obj.m);
         end
--- a/+scheme/Euler1d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Euler1d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -201,7 +201,8 @@
         % Enforces the boundary conditions
         %  w+ = R*w- + g(t)
         function closure = boundary_condition(obj,boundary, type, varargin)
-            [e_s,e_S,s] = obj.get_boundary_ops(boundary);
+            [e_s, e_S] = obj.getBoundaryOperator({'e', 'E'}, boundary);
+            s = obj.getBoundarySign(boundary);
 
             % Boundary condition on form
             %   w_in = R*w_out + g,       where g is data
@@ -232,7 +233,8 @@
         %
         % Returns closure(q,g)
         function closure = boundary_condition_L(obj, boundary, L_fun, p_in)
-            [e_s,e_S,s] = obj.get_boundary_ops(boundary);
+            [e_s, e_S] = obj.getBoundaryOperator({'e', 'E'}, boundary);
+            s = obj.getBoundarySign(boundary);
 
             p_ot = 1:3;
             p_ot(p_in) = [];
@@ -273,7 +275,8 @@
 
         % Return closure(q,g)
         function closure = boundary_condition_char(obj,boundary)
-            [e_s,e_S,s] = obj.get_boundary_ops(boundary);
+            [e_s, e_S] = obj.getBoundaryOperator({'e', 'E'}, boundary);
+            s = obj.getBoundarySign(boundary);
 
             function o = closure_fun(q, w_data)
                 q_s = e_S'*q;
@@ -314,7 +317,7 @@
 
         % Return closure(q,[v; p])
         function closure = boundary_condition_inflow(obj, boundary)
-            [~,~,s] = obj.get_boundary_ops(boundary);
+            s = obj.getBoundarySign(boundary);
 
              switch s
                 case -1
@@ -335,7 +338,7 @@
 
         % Return closure(q, p)
         function closure = boundary_condition_outflow(obj, boundary)
-            [~,~,s] = obj.get_boundary_ops(boundary);
+            s = obj.getBoundarySign(boundary);
 
              switch s
                 case -1
@@ -352,7 +355,7 @@
 
         % Return closure(q,[v; rho])
         function closure = boundary_condition_inflow_rho(obj, boundary)
-            [~,~,s] = obj.get_boundary_ops(boundary);
+            s = obj.getBoundarySign(boundary);
 
              switch s
                 case -1
@@ -372,7 +375,7 @@
 
         % Return closure(q,rho)
         function closure = boundary_condition_outflow_rho(obj, boundary)
-            [~,~,s] = obj.get_boundary_ops(boundary);
+            s = obj.getBoundarySign(boundary);
 
              switch s
                 case -1
@@ -388,7 +391,8 @@
 
         % Set wall boundary condition v = 0.
         function closure = boundary_condition_wall(obj,boundary)
-            [e_s,e_S,s] = obj.get_boundary_ops(boundary);
+            [e_s, e_S] = obj.getBoundaryOperator({'e', 'E'}, boundary);
+            s = obj.getBoundarySign(boundary);
 
             % Vill vi sätta penalty på karateristikan som är nära noll också eller vill
             % vi låta den vara fri?
@@ -478,18 +482,61 @@
             penalty = -halfnorm_inv*(tau*e_v' + sig*d1_v' + phi*alpha_v*d2_v' + psi*alpha_v*d3_v');
         end
 
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e,E,s] = get_boundary_ops(obj,boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'l'
+                        e = obj.e_l;
+                    case 'r'
+                        e = obj.e_r;
+                    otherwise
+                        error('No such boundary: boundary = %s',boundary);
+                    end
+                    varargout{i} = e;
+
+                case 'E'
+                    switch boundary
+                    case 'l'
+                        E = obj.e_L;
+                    case 'r'
+                        E = obj.e_R;
+                    otherwise
+                        error('No such boundary: boundary = %s',boundary);
+                    end
+                    varargout{i} = E;
+                end
+            end
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        % Note: for 1d diffOps, the boundary quadrature is the scalar 1.
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            H_b = 1;
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
             switch boundary
-                case 'l'
-                    e  = obj.e_l;
-                    E  = obj.e_L;
+                case {'r'}
+                    s = 1;
+                case {'l'}
                     s = -1;
-                case 'r'
-                    e  = obj.e_r;
-                    E  = obj.e_R;
-                    s = 1;
                 otherwise
                     error('No such boundary: boundary = %s',boundary);
             end
--- a/+scheme/Heat2dCurvilinear.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Heat2dCurvilinear.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,9 +1,9 @@
 classdef Heat2dCurvilinear < scheme.Scheme
 
 % Discretizes the Laplacian with variable coefficent, curvilinear,
-% in the Heat equation way (i.e., the discretization matrix is not necessarily 
+% in the Heat equation way (i.e., the discretization matrix is not necessarily
 % symmetric)
-% u_t = div * (kappa * grad u ) 
+% u_t = div * (kappa * grad u )
 % opSet should be cell array of opSets, one per dimension. This
 % is useful if we have periodic BC in one direction.
 
@@ -29,9 +29,9 @@
         e_l, e_r
         d1_l, d1_r % Normal derivatives at the boundary
         alpha % Vector of borrowing constants
-        
+
         % Boundary inner products
-        H_boundary_l, H_boundary_r 
+        H_boundary_l, H_boundary_r
 
         % Metric coefficients
         b % Cell matrix of size dim x dim
@@ -109,7 +109,7 @@
             opSetMetric{1} = sbp.D2Variable(m(1), {0, xmax}, order);
             opSetMetric{2} = sbp.D2Variable(m(2), {0, ymax}, order);
             D1Metric{1} = kron(opSetMetric{1}.D1, I{2});
-            D1Metric{2} = kron(I{1}, opSetMetric{2}.D1); 
+            D1Metric{2} = kron(I{1}, opSetMetric{2}.D1);
 
             x_xi = D1Metric{1}*x;
             x_eta = D1Metric{2}*x;
@@ -157,7 +157,7 @@
             % D2 coefficients
             kappa_coeff = cell(dim,dim);
             for j = 1:dim
-                obj.D2_kappa{j} = sparse(m_tot,m_tot); 
+                obj.D2_kappa{j} = sparse(m_tot,m_tot);
                 kappa_coeff{j} = sparse(m_tot,1);
                 for i = 1:dim
                     kappa_coeff{j} = kappa_coeff{j} + b{i,j}*J*b{i,j}*kappa;
@@ -270,28 +270,20 @@
             default_arg('symmetric', false);
             default_arg('tuning',1.2);
 
-            % j is the coordinate direction of the boundary
-            % nj: outward unit normal component. 
+            % nj: outward unit normal component.
             % nj = -1 for west, south, bottom boundaries
             % nj = 1  for east, north, top boundaries
-            [j, nj] = obj.get_boundary_number(boundary);
-            switch nj
-            case 1
-                e = obj.e_r{j};
-                flux = obj.flux_r{j};
-                H_gamma = obj.H_boundary_r{j};
-            case -1
-                e = obj.e_l{j};
-                flux = obj.flux_l{j};
-                H_gamma = obj.H_boundary_l{j};
-            end
+            nj = obj.getBoundarySign(boundary);
+
+            Hi = obj.Hi;
+            [e, flux] = obj.getBoundaryOperator({'e', 'flux'}, boundary);
+            H_gamma = obj.getBoundaryQuadrature(boundary);
+            alpha = obj.getBoundaryBorrowing(boundary);
 
             Hi = obj.Hi;
             Ji = obj.Ji;
             KAPPA = obj.KAPPA;
-            kappa_gamma = e'*KAPPA*e; 
-            h = obj.h(j);
-            alpha = h*obj.alpha(j);
+            kappa_gamma = e'*KAPPA*e;
 
             switch type
 
@@ -299,19 +291,19 @@
             case {'D','d','dirichlet','Dirichlet'}
 
                 if ~symmetric
-                    closure = -Ji*Hi*flux'*e*H_gamma*(e' ); 
+                    closure = -Ji*Hi*flux'*e*H_gamma*(e' );
                     penalty = Ji*Hi*flux'*e*H_gamma;
                 else
                     closure = Ji*Hi*flux'*e*H_gamma*(e' )...
-                              -tuning*2/alpha*Ji*Hi*e*kappa_gamma*H_gamma*(e' ) ; 
+                              -tuning*2/alpha*Ji*Hi*e*kappa_gamma*H_gamma*(e' ) ;
                     penalty =  -Ji*Hi*flux'*e*H_gamma ...
                               +tuning*2/alpha*Ji*Hi*e*kappa_gamma*H_gamma;
                 end
 
             % Normal flux boundary condition
             case {'N','n','neumann','Neumann'}
-                    closure = -Ji*Hi*e*H_gamma*(e'*flux ); 
-                    penalty =  Ji*Hi*e*H_gamma; 
+                    closure = -Ji*Hi*e*H_gamma*(e'*flux );
+                    penalty =  Ji*Hi*e*H_gamma;
 
             % Unknown boundary condition
             otherwise
@@ -325,57 +317,103 @@
             error('Interface not implemented');
         end
 
-        % Returns the coordinate number and outward normal component for the boundary specified by the string boundary.
-        function [j, nj] = get_boundary_number(obj, boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
-            switch boundary
-                case {'w','W','west','West', 'e', 'E', 'east', 'East'}
-                    j = 1;
-                case {'s','S','south','South', 'n', 'N', 'north', 'North'}
-                    j = 2;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
+            if ~iscell(op)
+                op = {op};
             end
 
-            switch boundary
-                case {'w','W','west','West','s','S','south','South'}
-                    nj = -1;
-                case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                    nj = 1;
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_l{1};
+                    case 'e'
+                        e = obj.e_r{1};
+                    case 's'
+                        e = obj.e_l{2};
+                    case 'n'
+                        e = obj.e_r{2};
+                    end
+                    varargout{i} = e;
+
+                case 'd'
+                    switch boundary
+                    case 'w'
+                        d = obj.d1_l{1};
+                    case 'e'
+                        d = obj.d1_r{1};
+                    case 's'
+                        d = obj.d1_l{2};
+                    case 'n'
+                        d = obj.d1_r{2};
+                    end
+                    varargout{i} = d;
+
+                case 'flux'
+                    switch boundary
+                    case 'w'
+                        flux = obj.flux_l{1};
+                    case 'e'
+                        flux = obj.flux_r{1};
+                    case 's'
+                        flux = obj.flux_l{2};
+                    case 'n'
+                        flux = obj.flux_r{2};
+                    end
+                    varargout{i} = flux;
+                end
             end
         end
 
-        % Returns the coordinate number and outward normal component for the boundary specified by the string boundary.
-        function [return_op] = get_boundary_operator(obj, op, boundary)
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
             switch boundary
-                case {'w','W','west','West', 'e', 'E', 'east', 'East'}
-                    j = 1;
-                case {'s','S','south','South', 'n', 'N', 'north', 'North'}
-                    j = 2;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
-            end
-
-            switch op
+                case 'w'
+                    H_b = obj.H_boundary_l{1};
                 case 'e'
-                    switch boundary
-                        case {'w','W','west','West','s','S','south','South'}
-                            return_op = obj.e_l{j};
-                        case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                            return_op = obj.e_r{j};
-                    end
-                case 'd'
-                    switch boundary
-                        case {'w','W','west','West','s','S','south','South'}
-                            return_op = obj.d1_l{j};
-                        case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                            return_op = obj.d1_r{j};
-                    end
-                otherwise
-                    error(['No such operator: operatr = ' op]);
+                    H_b = obj.H_boundary_r{1};
+                case 's'
+                    H_b = obj.H_boundary_l{2};
+                case 'n'
+                    H_b = obj.H_boundary_r{2};
             end
+        end
 
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'e','n'}
+                    s = 1;
+                case {'w','s'}
+                    s = -1;
+            end
+        end
+
+        % Returns borrowing constant gamma*h
+        % boundary -- string
+        function gamm = getBoundaryBorrowing(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'w','e'}
+                    gamm = obj.h(1)*obj.alpha(1);
+                case {'s','n'}
+                    gamm = obj.h(2)*obj.alpha(2);
+            end
         end
 
         function N = size(obj)
--- a/+scheme/Heat2dVariable.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Heat2dVariable.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,9 +1,9 @@
 classdef Heat2dVariable < scheme.Scheme
 
 % Discretizes the Laplacian with variable coefficent,
-% In the Heat equation way (i.e., the discretization matrix is not necessarily 
+% In the Heat equation way (i.e., the discretization matrix is not necessarily
 % symmetric)
-% u_t = div * (kappa * grad u ) 
+% u_t = div * (kappa * grad u )
 % opSet should be cell array of opSets, one per dimension. This
 % is useful if we have periodic BC in one direction.
 
@@ -29,7 +29,7 @@
         e_l, e_r
         d1_l, d1_r % Normal derivatives at the boundary
         alpha % Vector of borrowing constants
-        
+
         H_boundary % Boundary inner products
 
     end
@@ -162,26 +162,18 @@
             default_arg('symmetric', false);
             default_arg('tuning',1.2);
 
-            % j is the coordinate direction of the boundary
-            % nj: outward unit normal component. 
+            % nj: outward unit normal component.
             % nj = -1 for west, south, bottom boundaries
             % nj = 1  for east, north, top boundaries
-            [j, nj] = obj.get_boundary_number(boundary);
-            switch nj
-            case 1
-                e = obj.e_r;
-                d = obj.d1_r;
-            case -1
-                e = obj.e_l;
-                d = obj.d1_l;
-            end
+            nj = obj.getBoundarySign(boundary);
 
             Hi = obj.Hi;
-            H_gamma = obj.H_boundary{j};
+            [e, d] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            H_gamma = obj.getBoundaryQuadrature(boundary);
+            alpha = obj.getBoundaryBorrowing(boundary);
+
             KAPPA = obj.KAPPA;
-            kappa_gamma = e{j}'*KAPPA*e{j}; 
-            h = obj.h(j);
-            alpha = h*obj.alpha(j);
+            kappa_gamma = e'*KAPPA*e;
 
             switch type
 
@@ -189,19 +181,19 @@
             case {'D','d','dirichlet','Dirichlet'}
 
                 if ~symmetric
-                    closure = -nj*Hi*d{j}*kappa_gamma*H_gamma*(e{j}' ); 
-                    penalty =  nj*Hi*d{j}*kappa_gamma*H_gamma;
+                    closure = -nj*Hi*d*kappa_gamma*H_gamma*(e' );
+                    penalty =  nj*Hi*d*kappa_gamma*H_gamma;
                 else
-                    closure = nj*Hi*d{j}*kappa_gamma*H_gamma*(e{j}' )...
-                              -tuning*2/alpha*Hi*e{j}*kappa_gamma*H_gamma*(e{j}' ) ; 
-                    penalty =  -nj*Hi*d{j}*kappa_gamma*H_gamma ...
-                              +tuning*2/alpha*Hi*e{j}*kappa_gamma*H_gamma;
+                    closure = nj*Hi*d*kappa_gamma*H_gamma*(e' )...
+                              -tuning*2/alpha*Hi*e*kappa_gamma*H_gamma*(e' ) ;
+                    penalty =  -nj*Hi*d*kappa_gamma*H_gamma ...
+                              +tuning*2/alpha*Hi*e*kappa_gamma*H_gamma;
                 end
 
             % Free boundary condition
             case {'N','n','neumann','Neumann'}
-                    closure = -nj*Hi*e{j}*kappa_gamma*H_gamma*(d{j}' ); 
-                    penalty =  Hi*e{j}*kappa_gamma*H_gamma; 
+                    closure = -nj*Hi*e*kappa_gamma*H_gamma*(d' );
+                    penalty =  Hi*e*kappa_gamma*H_gamma;
                     % penalty is for normal derivative and not for derivative, hence the sign.
 
             % Unknown boundary condition
@@ -216,57 +208,90 @@
             error('Interface not implemented');
         end
 
-        % Returns the coordinate number and outward normal component for the boundary specified by the string boundary.
-        function [j, nj] = get_boundary_number(obj, boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
-            switch boundary
-                case {'w','W','west','West', 'e', 'E', 'east', 'East'}
-                    j = 1;
-                case {'s','S','south','South', 'n', 'N', 'north', 'North'}
-                    j = 2;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
+            if ~iscell(op)
+                op = {op};
             end
 
-            switch boundary
-                case {'w','W','west','West','s','S','south','South'}
-                    nj = -1;
-                case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                    nj = 1;
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_l{1};
+                    case 'e'
+                        e = obj.e_r{1};
+                    case 's'
+                        e = obj.e_l{2};
+                    case 'n'
+                        e = obj.e_r{2};
+                    end
+                    varargout{i} = e;
+
+                case 'd'
+                    switch boundary
+                    case 'w'
+                        d = obj.d1_l{1};
+                    case 'e'
+                        d = obj.d1_r{1};
+                    case 's'
+                        d = obj.d1_l{2};
+                    case 'n'
+                        d = obj.d1_r{2};
+                    end
+                    varargout{i} = d;
+                end
             end
         end
 
-        % Returns the coordinate number and outward normal component for the boundary specified by the string boundary.
-        function [return_op] = get_boundary_operator(obj, op, boundary)
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
             switch boundary
-                case {'w','W','west','West', 'e', 'E', 'east', 'East'}
-                    j = 1;
-                case {'s','S','south','South', 'n', 'N', 'north', 'North'}
-                    j = 2;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
-            end
-
-            switch op
+                case 'w'
+                    H_b = obj.H_boundary{1};
                 case 'e'
-                    switch boundary
-                        case {'w','W','west','West','s','S','south','South'}
-                            return_op = obj.e_l{j};
-                        case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                            return_op = obj.e_r{j};
-                    end
-                case 'd'
-                    switch boundary
-                        case {'w','W','west','West','s','S','south','South'}
-                            return_op = obj.d1_l{j};
-                        case {'e', 'E', 'east', 'East','n', 'N', 'north', 'North'}
-                            return_op = obj.d1_r{j};
-                    end
-                otherwise
-                    error(['No such operator: operatr = ' op]);
+                    H_b = obj.H_boundary{1};
+                case 's'
+                    H_b = obj.H_boundary{2};
+                case 'n'
+                    H_b = obj.H_boundary{2};
             end
+        end
 
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'e','n'}
+                    s = 1;
+                case {'w','s'}
+                    s = -1;
+            end
+        end
+
+        % Returns borrowing constant gamma*h
+        % boundary -- string
+        function gamm = getBoundaryBorrowing(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'w','e'}
+                    gamm = obj.h(1)*obj.alpha(1);
+                case {'s','n'}
+                    gamm = obj.h(2)*obj.alpha(2);
+            end
         end
 
         function N = size(obj)
--- a/+scheme/Hypsyst2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Hypsyst2d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -186,10 +186,10 @@
             params = obj.params;
             x = obj.x;
             y = obj.y;
+            e_ = obj.getBoundaryOperator('e', boundary);
 
             switch boundary
                 case {'w','W','west'}
-                    e_ = obj.e_w;
                     mat = obj.A;
                     boundPos = 'l';
                     Hi = obj.Hxi;
@@ -197,7 +197,6 @@
                     L = obj.evaluateCoefficientMatrix(L,x(1),y);
                     side = max(length(y));
                 case {'e','E','east'}
-                    e_ = obj.e_e;
                     mat = obj.A;
                     boundPos = 'r';
                     Hi = obj.Hxi;
@@ -205,7 +204,6 @@
                     L = obj.evaluateCoefficientMatrix(L,x(end),y);
                     side = max(length(y));
                 case {'s','S','south'}
-                    e_ = obj.e_s;
                     mat = obj.B;
                     boundPos = 'l';
                     Hi = obj.Hyi;
@@ -213,7 +211,6 @@
                     L = obj.evaluateCoefficientMatrix(L,x,y(1));
                     side = max(length(x));
                 case {'n','N','north'}
-                    e_ = obj.e_n;
                     mat = obj.B;
                     boundPos = 'r';
                     Hi = obj.Hyi;
@@ -297,5 +294,54 @@
             signVec = [sum(poseig),sum(zeroeig),sum(negeig)];
         end
 
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_w;
+                    case 'e'
+                        e = obj.e_e;
+                    case 's'
+                        e = obj.e_s;
+                    case 'n'
+                        e = obj.e_n;
+                    end
+                    varargout{i} = e;
+                end
+            end
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            e = obj.getBoundaryOperator('e', boundary);
+
+            switch boundary
+                case 'w'
+                    H_b = inv(e'*obj.Hyi*e);
+                case 'e'
+                    H_b = inv(e'*obj.Hyi*e);
+                case 's'
+                    H_b = inv(e'*obj.Hxi*e);
+                case 'n'
+                    H_b = inv(e'*obj.Hxi*e);
+            end
+        end
+
     end
 end
\ No newline at end of file
--- a/+scheme/Hypsyst2dCurve.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Hypsyst2dCurve.m	Thu Mar 10 16:54:26 2022 +0100
@@ -169,31 +169,28 @@
             Y = obj.Y;
             xi = obj.xi;
             eta = obj.eta;
+            e_ = obj.getBoundaryOperator('e', boundary);
 
             switch boundary
                 case {'w','W','west'}
-                    e_ = obj.e_w;
                     mat = obj.Ahat;
                     boundPos = 'l';
                     Hi = obj.Hxii;
                     [V,Vi,D,signVec] = obj.matrixDiag(mat,X(obj.index_w),Y(obj.index_w),obj.X_eta(obj.index_w),obj.Y_eta(obj.index_w));
                     side = max(length(eta));
                 case {'e','E','east'}
-                    e_ = obj.e_e;
                     mat = obj.Ahat;
                     boundPos = 'r';
                     Hi = obj.Hxii;
                     [V,Vi,D,signVec] = obj.matrixDiag(mat,X(obj.index_e),Y(obj.index_e),obj.X_eta(obj.index_e),obj.Y_eta(obj.index_e));
                     side = max(length(eta));
                 case {'s','S','south'}
-                    e_ = obj.e_s;
                     mat = obj.Bhat;
                     boundPos = 'l';
                     Hi = obj.Hetai;
                     [V,Vi,D,signVec] = obj.matrixDiag(mat,X(obj.index_s),Y(obj.index_s),obj.X_xi(obj.index_s),obj.Y_xi(obj.index_s));
                     side = max(length(xi));
                 case {'n','N','north'}
-                    e_ = obj.e_n;
                     mat = obj.Bhat;
                     boundPos = 'r';
                     Hi = obj.Hetai;
@@ -374,5 +371,56 @@
             Vi = [Vi(poseig,:); Vi(zeroeig,:); Vi(negeig,:)];
             signVec = [sum(poseig),sum(zeroeig),sum(negeig)];
         end
+
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_w;
+                    case 'e'
+                        e = obj.e_e;
+                    case 's'
+                        e = obj.e_s;
+                    case 'n'
+                        e = obj.e_n;
+                    end
+                    varargout{i} = e;
+                end
+            end
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            e = obj.getBoundaryOperator('e', boundary);
+
+            switch boundary
+                case 'w'
+                    H_b = inv(e'*obj.Hetai*e);
+                case 'e'
+                    H_b = inv(e'*obj.Hetai*e);
+                case 's'
+                    H_b = inv(e'*obj.Hxii*e);
+                case 'n'
+                    H_b = inv(e'*obj.Hxii*e);
+            end
+        end
+
+
     end
 end
\ No newline at end of file
--- a/+scheme/Laplace1d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Laplace1d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -56,11 +56,13 @@
             default_arg('type','neumann');
             default_arg('data',0);
 
-            [e,d,s] = obj.get_boundary_ops(boundary);
+            e = obj.getBoundaryOperator('e', boundary);
+            d = obj.getBoundaryOperator('d', boundary);
+            s = obj.getBoundarySign(boundary);
 
             switch type
                 % Dirichlet boundary condition
-                case {'D','dirichlet'}
+                case {'D','d','dirichlet'}
                     tuning = 1.1;
                     tau1 = -tuning/obj.gamm;
                     tau2 =  1;
@@ -68,10 +70,10 @@
                     tau = tau1*e + tau2*d;
 
                     closure = obj.a*obj.Hi*tau*e';
-                    penalty = obj.a*obj.Hi*tau;
+                    penalty = -obj.a*obj.Hi*tau;
 
                 % Neumann boundary condition
-                case {'N','neumann'}
+                case {'N','n','neumann'}
                     tau = -e;
 
                     closure = obj.a*obj.Hi*tau*d';
@@ -86,10 +88,13 @@
         function [closure, penalty] = interface(obj, boundary, neighbour_scheme, neighbour_boundary, type)
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
+            e_u = obj.getBoundaryOperator('e', boundary);
+            d_u = obj.getBoundaryOperator('d', boundary);
+            s_u = obj.getBoundarySign(boundary);
 
-            [e_u,d_u,s_u] = obj.get_boundary_ops(boundary);
-            [e_v,d_v,s_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
-
+            e_v = neighbour_scheme.getBoundaryOperator('e', neighbour_boundary);
+            d_v = neighbour_scheme.getBoundaryOperator('d', neighbour_boundary);
+            s_v = neighbour_scheme.getBoundarySign(neighbour_boundary);
 
             a_u = obj.a;
             a_v = neighbour_scheme.a;
@@ -99,7 +104,7 @@
 
             tuning = 1.1;
 
-            tau1 = -(a_u/gamm_u + a_v/gamm_v) * tuning;
+            tau1 = -1/4*(a_u/gamm_u + a_v/gamm_v) * tuning;
             tau2 = 1/2*a_u;
             sig1 = -1/2;
             sig2 = 0;
@@ -111,20 +116,37 @@
             penalty = obj.Hi*(-tau*e_v' + sig*a_v*d_v');
         end
 
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e,d,s] = get_boundary_ops(obj,boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string
+        % boundary  -- string
+        function o = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(op, {'e', 'd'})
+            assertIsMember(boundary, {'l', 'r'})
+
+            o = obj.([op, '_', boundary]);
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        % Note: for 1d diffOps, the boundary quadrature is the scalar 1.
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            H_b = 1;
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
             switch boundary
-                case 'l'
-                    e = obj.e_l;
-                    d = obj.d_l;
+                case {'r'}
+                    s = 1;
+                case {'l'}
                     s = -1;
-                case 'r'
-                    e = obj.e_r;
-                    d = obj.d_r;
-                    s = 1;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
             end
         end
 
@@ -133,14 +155,4 @@
         end
 
     end
-
-    methods(Static)
-        % Calculates the matrcis need for the inteface coupling between boundary bound_u of scheme schm_u
-        % and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_couplong(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
-end
\ No newline at end of file
+end
--- a/+scheme/LaplaceCurvilinear.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/LaplaceCurvilinear.m	Thu Mar 10 16:54:26 2022 +0100
@@ -238,7 +238,10 @@
             default_arg('type','neumann');
             default_arg('parameter', []);
 
-            [e, d, gamm, H_b, ~] = obj.get_boundary_ops(boundary);
+            e = obj.getBoundaryOperator('e', boundary);
+            d = obj.getBoundaryOperator('d', boundary);
+            H_b = obj.getBoundaryQuadrature(boundary);
+            gamm = obj.getBoundaryBorrowing(boundary);
             switch type
                 % Dirichlet boundary condition
                 case {'D','d','dirichlet'}
@@ -298,8 +301,17 @@
 
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
-            [e_u, d_u, gamm_u, H_b_u, I_u] = obj.get_boundary_ops(boundary);
-            [e_v, d_v, gamm_v, H_b_v, I_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
+            e_u    = obj.getBoundaryOperator('e', boundary);
+            d_u    = obj.getBoundaryOperator('d', boundary);
+            H_b_u = obj.getBoundaryQuadrature(boundary);
+            I_u = obj.getBoundaryIndices(boundary);
+            gamm_u = obj.getBoundaryBorrowing(boundary);
+
+            e_v    = neighbour_scheme.getBoundaryOperator('e', neighbour_boundary);
+            d_v    = neighbour_scheme.getBoundaryOperator('d', neighbour_boundary);
+            H_b_v = neighbour_scheme.getBoundaryQuadrature(neighbour_boundary);
+            I_v = neighbour_scheme.getBoundaryIndices(neighbour_boundary);
+            gamm_v = neighbour_scheme.getBoundaryBorrowing(neighbour_boundary);
 
             u = obj;
             v = neighbour_scheme;
@@ -336,8 +348,18 @@
 
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
-            [e_u, d_u, gamm_u, H_b_u, I_u] = obj.get_boundary_ops(boundary);
-            [e_v, d_v, gamm_v, H_b_v, I_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
+            e_u    = obj.getBoundaryOperator('e', boundary);
+            d_u    = obj.getBoundaryOperator('d', boundary);
+            H_b_u  = obj.getBoundaryQuadrature(boundary);
+            I_u    = obj.getBoundaryIndices(boundary);
+            gamm_u = obj.getBoundaryBorrowing(boundary);
+
+            e_v    = neighbour_scheme.getBoundaryOperator('e', neighbour_boundary);
+            d_v    = neighbour_scheme.getBoundaryOperator('d', neighbour_boundary);
+            H_b_v  = neighbour_scheme.getBoundaryQuadrature(neighbour_boundary);
+            I_v    = neighbour_scheme.getBoundaryIndices(neighbour_boundary);
+            gamm_v = neighbour_scheme.getBoundaryBorrowing(neighbour_boundary);
+
 
             % Find the number of grid points along the interface
             m_u = size(e_u, 2);
@@ -378,37 +400,48 @@
 
         end
 
-        % Returns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string
+        % boundary  -- string
+        function o = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(op, {'e', 'd'})
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            o = obj.([op, '_', boundary]);
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
         %
-        %  I -- the indices of the boundary points in the grid matrix
-        function [e, d, gamm, H_b, I] = get_boundary_ops(obj, boundary)
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            H_b = obj.(['H_', boundary]);
+        end
+
+        % Returns the indices of the boundary points in the grid matrix
+        % boundary -- string
+        function I = getBoundaryIndices(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
             ind = grid.funcToMatrix(obj.grid, 1:prod(obj.m));
-
             switch boundary
                 case 'w'
-                    e = obj.e_w;
-                    d = obj.d_w;
-                    H_b = obj.H_w;
                     I = ind(1,:);
                 case 'e'
-                    e = obj.e_e;
-                    d = obj.d_e;
-                    H_b = obj.H_e;
                     I = ind(end,:);
                 case 's'
-                    e = obj.e_s;
-                    d = obj.d_s;
-                    H_b = obj.H_s;
                     I = ind(:,1)';
                 case 'n'
-                    e = obj.e_n;
-                    d = obj.d_n;
-                    H_b = obj.H_n;
                     I = ind(:,end)';
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
             end
+        end
+
+        % Returns borrowing constant gamma
+        % boundary -- string
+        function gamm = getBoundaryBorrowing(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
             switch boundary
                 case {'w','e'}
--- a/+scheme/Scheme.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Scheme.m	Thu Mar 10 16:54:26 2022 +0100
@@ -31,20 +31,10 @@
         %         depending on the particular scheme implementation
         [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary,type)
 
-        % TODO: op = getBoundaryOperator()??
-        %   makes sense to have it available through a method instead of random properties
+        op = getBoundaryOperator(obj, opName, boundary)
+        H_b= getBoundaryQuadrature(obj, boundary)
 
         % Returns the number of degrees of freedom.
         N = size(obj)
     end
-
-    methods(Static)
-        % Calculates the matrcis need for the inteface coupling between
-        % boundary bound_u of scheme schm_u and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_coupling(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
 end
--- a/+scheme/Schrodinger.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Schrodinger.m	Thu Mar 10 16:54:26 2022 +0100
@@ -67,7 +67,8 @@
             default_arg('type','dirichlet');
             default_arg('data',0);
 
-            [e,d,s] = obj.get_boundary_ops(boundary);
+            [e, d] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            s = obj.getBoundarySign(boundary);
 
             switch type
                 % Dirichlet boundary condition
@@ -93,8 +94,11 @@
         function [closure, penalty] = interface(obj, boundary, neighbour_scheme, neighbour_boundary, type)
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
-            [e_u,d_u,s_u] = obj.get_boundary_ops(boundary);
-            [e_v,d_v,s_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
+            [e_u, d_u] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            s_u = obj.getBoundarySign(boundary);
+
+            [e_v, d_v] = neighbour_scheme.getBoundaryOperator({'e', 'd'}, neighbour_boundary);
+            s_v = neighbour_scheme.getBoundarySign(neighbour_boundary);
 
             a =  -s_u* 1/2 * 1i ;
             b =  a';
@@ -106,20 +110,60 @@
             penalty = obj.Hi * (-tau*e_v' - sig*d_v');
         end
 
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e,d,s] = get_boundary_ops(obj,boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'l'
+                        e = obj.e_l;
+                    case 'r'
+                        e = obj.e_r;
+                    end
+                    varargout{i} = e;
+
+                case 'd'
+                    switch boundary
+                    case 'l'
+                        d = obj.d1_l;
+                    case 'r'
+                        d = obj.d1_r;
+                    end
+                    varargout{i} = d;
+                end
+            end
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        % Note: for 1d diffOps, the boundary quadrature is the scalar 1.
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            H_b = 1;
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
             switch boundary
-                case 'l'
-                    e = obj.e_l;
-                    d = obj.d1_l;
+                case {'r'}
+                    s = 1;
+                case {'l'}
                     s = -1;
-                case 'r'
-                    e = obj.e_r;
-                    d = obj.d1_r;
-                    s = 1;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
             end
         end
 
@@ -128,14 +172,4 @@
         end
 
     end
-
-    methods(Static)
-        % Calculates the matrcis need for the inteface coupling between boundary bound_u of scheme schm_u
-        % and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_couplong(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
-end
\ No newline at end of file
+end
--- a/+scheme/Schrodinger2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Schrodinger2d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -162,35 +162,26 @@
             default_arg('type','Neumann');
             default_arg('parameter', []);
 
-            % j is the coordinate direction of the boundary
             % nj: outward unit normal component.
             % nj = -1 for west, south, bottom boundaries
             % nj = 1  for east, north, top boundaries
-            [j, nj] = obj.get_boundary_number(boundary);
-            switch nj
-            case 1
-                e = obj.e_r;
-                d = obj.d1_r;
-            case -1
-                e = obj.e_l;
-                d = obj.d1_l;
-            end
-
+            nj = obj.getBoundarySign(boundary);
+            [e, d] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            H_gamma = obj.getBoundaryQuadrature(boundary);
             Hi = obj.Hi;
-            H_gamma = obj.H_boundary{j};
-            a = e{j}'*obj.a*e{j};
+            a = e'*obj.a*e;
 
             switch type
 
             % Dirichlet boundary condition
             case {'D','d','dirichlet','Dirichlet'}
-                    closure =  nj*Hi*d{j}*a*1i*H_gamma*(e{j}' );
-                    penalty = -nj*Hi*d{j}*a*1i*H_gamma;
+                    closure =  nj*Hi*d*a*1i*H_gamma*(e' );
+                    penalty = -nj*Hi*d*a*1i*H_gamma;
 
             % Free boundary condition
             case {'N','n','neumann','Neumann'}
-                    closure = -nj*Hi*e{j}*a*1i*H_gamma*(d{j}' );
-                    penalty =  nj*Hi*e{j}*a*1i*H_gamma;
+                    closure = -nj*Hi*e*a*1i*H_gamma*(d' );
+                    penalty =  nj*Hi*e*a*1i*H_gamma;
 
             % Unknown boundary condition
             otherwise
@@ -221,13 +212,14 @@
             % v denotes the solution in the neighbour domain
 
             % Get boundary operators
-            [e_neighbour, d_neighbour] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
-            [e, d, H_gamma] = obj.get_boundary_ops(boundary);
+            [e_v, d_v] = neighbour_scheme.getBoundaryOperator({'e', 'd'}, neighbour_boundary);
+            [e_u, d_u] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            H_gamma = obj.getBoundaryQuadrature(boundary);
             Hi = obj.Hi;
             a = obj.a;
 
             % Get outward unit normal component
-            [~, n] = obj.get_boundary_number(boundary);
+            n = obj.getBoundarySign(boundary);
 
             Hi = obj.Hi;
             sigma = -n*1i*a/2;
@@ -247,13 +239,14 @@
 
             % u denotes the solution in the own domain
             % v denotes the solution in the neighbour domain
-            [e_v, d_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
-            [e_u, d_u, H_gamma] = obj.get_boundary_ops(boundary);
+            [e_v, d_v] = neighbour_scheme.getBoundaryOperator({'e', 'd'}, neighbour_boundary);
+            [e_u, d_u] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            H_gamma = obj.getBoundaryQuadrature(boundary);
             Hi = obj.Hi;
             a = obj.a;
 
             % Get outward unit normal component
-            [~, n] = obj.get_boundary_number(boundary);
+            n = obj.getBoundarySign(boundary);
 
             % Find the number of grid points along the interface
             m_u = size(e_u, 2);
@@ -293,29 +286,76 @@
             end
         end
 
-        % Returns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e, d, H_b] = get_boundary_ops(obj, boundary)
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_w;
+                    case 'e'
+                        e = obj.e_e;
+                    case 's'
+                        e = obj.e_s;
+                    case 'n'
+                        e = obj.e_n;
+                    end
+                    varargout{i} = e;
+
+                case 'd'
+                    switch boundary
+                    case 'w'
+                        d = obj.d_w;
+                    case 'e'
+                        d = obj.d_e;
+                    case 's'
+                        d = obj.d_s;
+                    case 'n'
+                        d = obj.d_n;
+                    end
+                    varargout{i} = d;
+                end
+            end
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
 
             switch boundary
                 case 'w'
-                    e = obj.e_w;
-                    d = obj.d_w;
                     H_b = obj.H_boundary{1};
                 case 'e'
-                    e = obj.e_e;
-                    d = obj.d_e;
                     H_b = obj.H_boundary{1};
                 case 's'
-                    e = obj.e_s;
-                    d = obj.d_s;
                     H_b = obj.H_boundary{2};
                 case 'n'
-                    e = obj.e_n;
-                    d = obj.d_n;
                     H_b = obj.H_boundary{2};
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
+            end
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'e','n'}
+                    s = 1;
+                case {'w','s'}
+                    s = -1;
             end
         end
 
--- a/+scheme/TODO.txt	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-% TODO: Rename package and abstract class to diffOp
--- a/+scheme/Utux.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Utux.m	Thu Mar 10 16:54:26 2022 +0100
@@ -72,19 +72,30 @@
 
          end
 
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string
+        % boundary  -- string
+        function o = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(op, {'e'})
+            assertIsMember(boundary, {'l', 'r'})
+
+            o = obj.([op, '_', boundary]);
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        % Note: for 1d diffOps, the boundary quadrature is the scalar 1.
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'l', 'r'})
+
+            H_b = 1;
+        end
+
         function N = size(obj)
             N = obj.m;
         end
 
     end
-
-    methods(Static)
-        % Calculates the matrices needed for the inteface coupling between boundary bound_u of scheme schm_u
-        % and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_coupling(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
-end
\ No newline at end of file
+end
--- a/+scheme/Utux2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Utux2d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -12,6 +12,7 @@
         H % Discrete norm
         H_x, H_y % Norms in the x and y directions
         Hi, Hx, Hy, Hxi, Hyi % Kroneckered norms
+        H_w, H_e, H_s, H_n % Boundary quadratures
 
         % Derivatives
         Dx, Dy
@@ -59,6 +60,10 @@
             Hxi = ops_x.HI;
             Hyi = ops_y.HI;
 
+            obj.H_w = Hy;
+            obj.H_e = Hy;
+            obj.H_s = Hx;
+            obj.H_n = Hx;
             obj.H_x = Hx;
             obj.H_y = Hy;
             obj.H = kron(Hx,Hy);
@@ -139,16 +144,7 @@
             couplingType = type.couplingType;
 
             % Get neighbour boundary operator
-            switch neighbour_boundary
-             case {'e','E','east','East'}
-                 e_neighbour = neighbour_scheme.e_e;
-             case {'w','W','west','West'}
-                 e_neighbour = neighbour_scheme.e_w;
-             case {'n','N','north','North'}
-                 e_neighbour = neighbour_scheme.e_n;
-             case {'s','S','south','South'}
-                 e_neighbour = neighbour_scheme.e_s;
-            end
+            e_neighbour = neighbour_scheme.getBoundaryOperator('e', neighbour_boundary);
 
             switch couplingType
 
@@ -197,16 +193,7 @@
             interpolationDamping = type.interpolationDamping;
 
             % Get neighbour boundary operator
-            switch neighbour_boundary
-             case {'e','E','east','East'}
-                 e_neighbour = neighbour_scheme.e_e;
-             case {'w','W','west','West'}
-                 e_neighbour = neighbour_scheme.e_w;
-             case {'n','N','north','North'}
-                 e_neighbour = neighbour_scheme.e_n;
-             case {'s','S','south','South'}
-                 e_neighbour = neighbour_scheme.e_s;
-            end
+            e_neighbour = neighbour_scheme.getBoundaryOperator('e', neighbour_boundary);
 
             switch couplingType
 
@@ -290,19 +277,29 @@
 
          end
 
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string
+        % boundary  -- string
+        function o = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(op, {'e'})
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            o = obj.([op, '_', boundary]);
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            H_b = obj.(['H_', boundary]);
+        end
+
         function N = size(obj)
             N = obj.m;
         end
 
     end
-
-    methods(Static)
-        % Calculates the matrices needed for the inteface coupling between boundary bound_u of scheme schm_u
-        % and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_coupling(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
-end
\ No newline at end of file
+end
--- a/+scheme/Wave2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+scheme/Wave2d.m	Thu Mar 10 16:54:26 2022 +0100
@@ -106,7 +106,10 @@
             default_arg('type','neumann');
             default_arg('data',0);
 
-            [e,d,s,gamm,halfnorm_inv] = obj.get_boundary_ops(boundary);
+            [e, d] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            gamm = obj.getBoundaryBorrowing(boundary);
+            s = obj.getBoundarySign(boundary);
+            halfnorm_inv = obj.getHalfnormInv(boundary);
 
             switch type
                 % Dirichlet boundary condition
@@ -164,6 +167,15 @@
             [e_u,d_u,s_u,gamm_u, halfnorm_inv] = obj.get_boundary_ops(boundary);
             [e_v,d_v,s_v,gamm_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
 
+            [e_u, d_u] = obj.getBoundaryOperator({'e', 'd'}, boundary);
+            gamm_u = obj.getBoundaryBorrowing(boundary);
+            s_u = obj.getBoundarySign(boundary);
+            halfnorm_inv = obj.getHalfnormInv(boundary);
+
+            [e_v, d_v] = neighbour_scheme.getBoundaryOperator({'e', 'd'}, neighbour_boundary);
+            gamm_v = neighbour_scheme.getBoundaryBorrowing(neighbour_boundary);
+            s_v = neighbour_scheme.getBoundarySign(neighbour_boundary);
+
             tuning = 1.1;
 
             alpha_u = obj.alpha;
@@ -183,36 +195,107 @@
             penalty = halfnorm_inv*(-tau*e_v' - sig*alpha_v*d_v');
         end
 
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        function [e,d,s,gamm, halfnorm_inv] = get_boundary_ops(obj,boundary)
+
+        % Returns the boundary operator op for the boundary specified by the string boundary.
+        % op        -- string or a cell array of strings
+        % boundary  -- string
+        function varargout = getBoundaryOperator(obj, op, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            if ~iscell(op)
+                op = {op};
+            end
+
+            for i = 1:numel(op)
+                switch op{i}
+                case 'e'
+                    switch boundary
+                    case 'w'
+                        e = obj.e_w;
+                    case 'e'
+                        e = obj.e_e;
+                    case 's'
+                        e = obj.e_s;
+                    case 'n'
+                        e = obj.e_n;
+                    end
+                    varargout{i} = e;
+
+                case 'd'
+                    switch boundary
+                    case 'w'
+                        d = obj.d1_w;
+                    case 'e'
+                        d = obj.d1_e;
+                    case 's'
+                        d = obj.d1_s;
+                    case 'n'
+                        d = obj.d1_n;
+                    end
+                    varargout{i} = d;
+                end
+            end
+
+        end
+
+        % Returns square boundary quadrature matrix, of dimension
+        % corresponding to the number of boundary points
+        %
+        % boundary -- string
+        function H_b = getBoundaryQuadrature(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
             switch boundary
                 case 'w'
-                    e = obj.e_w;
-                    d = obj.d1_w;
-                    s = -1;
-                    gamm = obj.gamm_x;
-                    halfnorm_inv = obj.Hix;
+                    H_b = obj.H_y;
                 case 'e'
-                    e = obj.e_e;
-                    d = obj.d1_e;
-                    s = 1;
-                    gamm = obj.gamm_x;
-                    halfnorm_inv = obj.Hix;
+                    H_b = obj.H_y;
                 case 's'
-                    e = obj.e_s;
-                    d = obj.d1_s;
-                    s = -1;
-                    gamm = obj.gamm_y;
-                    halfnorm_inv = obj.Hiy;
+                    H_b = obj.H_x;
                 case 'n'
-                    e = obj.e_n;
-                    d = obj.d1_n;
-                    s = 1;
+                    H_b = obj.H_x;
+            end
+        end
+
+        % Returns borrowing constant gamma
+        % boundary -- string
+        function gamm = getBoundaryBorrowing(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'w','e'}
+                    gamm = obj.gamm_x;
+                case {'s','n'}
                     gamm = obj.gamm_y;
-                    halfnorm_inv = obj.Hiy;
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
+            end
+        end
+
+        % Returns the boundary sign. The right boundary is considered the positive boundary
+        % boundary -- string
+        function s = getBoundarySign(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case {'e','n'}
+                    s = 1;
+                case {'w','s'}
+                    s = -1;
+            end
+        end
+
+        % Returns the halfnorm_inv used in SATs. TODO: better notation
+        function Hinv = getHalfnormInv(obj, boundary)
+            assertIsMember(boundary, {'w', 'e', 's', 'n'})
+
+            switch boundary
+                case 'w'
+                    Hinv = obj.Hix;
+                case 'e'
+                    Hinv = obj.Hix;
+                case 's'
+                    Hinv = obj.Hiy;
+                case 'n'
+                    Hinv = obj.Hiy;
             end
         end
 
@@ -221,14 +304,4 @@
         end
 
     end
-
-    methods(Static)
-        % Calculates the matrcis need for the inteface coupling between boundary bound_u of scheme schm_u
-        % and bound_v of scheme schm_v.
-        %   [uu, uv, vv, vu] = inteface_couplong(A,'r',B,'l')
-        function [uu, uv, vv, vu] = interface_coupling(schm_u,bound_u,schm_v,bound_v)
-            [uu,uv] = schm_u.interface(bound_u,schm_v,bound_v);
-            [vv,vu] = schm_v.interface(bound_v,schm_u,bound_u);
-        end
-    end
-end
\ No newline at end of file
+end
--- a/+scheme/Wave2dCurve.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,359 +0,0 @@
-classdef Wave2dCurve < scheme.Scheme
-    properties
-        m % Number of points in each direction, possibly a vector
-        h % Grid spacing
-
-        grid
-
-        order % Order accuracy for the approximation
-
-        D % non-stabalized scheme operator
-        M % Derivative norm
-        c
-        J, Ji
-        a11, a12, a22
-
-        H % Discrete norm
-        Hi
-        H_u, H_v % Norms in the x and y directions
-        Hu,Hv % Kroneckerd norms. 1'*Hx*v corresponds to integration in the x dir.
-        Hi_u, Hi_v
-        Hiu, Hiv
-        e_w, e_e, e_s, e_n
-        du_w, dv_w
-        du_e, dv_e
-        du_s, dv_s
-        du_n, dv_n
-        gamm_u, gamm_v
-        lambda
-
-        Dx, Dy % Physical derivatives
-
-        x_u
-        x_v
-        y_u
-        y_v
-    end
-
-    methods
-        function obj = Wave2dCurve(g ,order, c, opSet)
-            default_arg('opSet',@sbp.D2Variable);
-            default_arg('c', 1);
-
-            warning('Use LaplaceCruveilinear instead')
-
-            assert(isa(g, 'grid.Curvilinear'))
-
-            m = g.size();
-            m_u = m(1);
-            m_v = m(2);
-            m_tot = g.N();
-
-            h = g.scaling();
-            h_u = h(1);
-            h_v = h(2);
-
-            % Operators
-            ops_u = opSet(m_u, {0, 1}, order);
-            ops_v = opSet(m_v, {0, 1}, order);
-
-            I_u = speye(m_u);
-            I_v = speye(m_v);
-
-            D1_u = ops_u.D1;
-            D2_u = ops_u.D2;
-            H_u =  ops_u.H;
-            Hi_u = ops_u.HI;
-            e_l_u = ops_u.e_l;
-            e_r_u = ops_u.e_r;
-            d1_l_u = ops_u.d1_l;
-            d1_r_u = ops_u.d1_r;
-
-            D1_v = ops_v.D1;
-            D2_v = ops_v.D2;
-            H_v =  ops_v.H;
-            Hi_v = ops_v.HI;
-            e_l_v = ops_v.e_l;
-            e_r_v = ops_v.e_r;
-            d1_l_v = ops_v.d1_l;
-            d1_r_v = ops_v.d1_r;
-
-            Du = kr(D1_u,I_v);
-            Dv = kr(I_u,D1_v);
-
-            % Metric derivatives
-            coords = g.points();
-            x = coords(:,1);
-            y = coords(:,2);
-
-            x_u = Du*x;
-            x_v = Dv*x;
-            y_u = Du*y;
-            y_v = Dv*y;
-
-            J = x_u.*y_v - x_v.*y_u;
-            a11 =  1./J .* (x_v.^2  + y_v.^2);
-            a12 = -1./J .* (x_u.*x_v + y_u.*y_v);
-            a22 =  1./J .* (x_u.^2  + y_u.^2);
-            lambda = 1/2 * (a11 + a22 - sqrt((a11-a22).^2 + 4*a12.^2));
-
-            % Assemble full operators
-            L_12 = spdiags(a12, 0, m_tot, m_tot);
-            Duv = Du*L_12*Dv;
-            Dvu = Dv*L_12*Du;
-
-            Duu = sparse(m_tot);
-            Dvv = sparse(m_tot);
-            ind = grid.funcToMatrix(g, 1:m_tot);
-
-            for i = 1:m_v
-                D = D2_u(a11(ind(:,i)));
-                p = ind(:,i);
-                Duu(p,p) = D;
-            end
-
-            for i = 1:m_u
-                D = D2_v(a22(ind(i,:)));
-                p = ind(i,:);
-                Dvv(p,p) = D;
-            end
-
-            obj.H = kr(H_u,H_v);
-            obj.Hi = kr(Hi_u,Hi_v);
-            obj.Hu  = kr(H_u,I_v);
-            obj.Hv  = kr(I_u,H_v);
-            obj.Hiu = kr(Hi_u,I_v);
-            obj.Hiv = kr(I_u,Hi_v);
-
-            obj.e_w  = kr(e_l_u,I_v);
-            obj.e_e  = kr(e_r_u,I_v);
-            obj.e_s  = kr(I_u,e_l_v);
-            obj.e_n  = kr(I_u,e_r_v);
-            obj.du_w = kr(d1_l_u,I_v);
-            obj.dv_w = (obj.e_w'*Dv)';
-            obj.du_e = kr(d1_r_u,I_v);
-            obj.dv_e = (obj.e_e'*Dv)';
-            obj.du_s = (obj.e_s'*Du)';
-            obj.dv_s = kr(I_u,d1_l_v);
-            obj.du_n = (obj.e_n'*Du)';
-            obj.dv_n = kr(I_u,d1_r_v);
-
-            obj.x_u = x_u;
-            obj.x_v = x_v;
-            obj.y_u = y_u;
-            obj.y_v = y_v;
-
-            obj.m = m;
-            obj.h = [h_u h_v];
-            obj.order = order;
-            obj.grid = g;
-
-            obj.c = c;
-            obj.J = spdiags(J, 0, m_tot, m_tot);
-            obj.Ji = spdiags(1./J, 0, m_tot, m_tot);
-            obj.a11 = a11;
-            obj.a12 = a12;
-            obj.a22 = a22;
-            obj.D = obj.Ji*c^2*(Duu + Duv + Dvu + Dvv);
-            obj.lambda = lambda;
-
-            obj.Dx = spdiag( y_v./J)*Du + spdiag(-y_u./J)*Dv;
-            obj.Dy = spdiag(-x_v./J)*Du + spdiag( x_u./J)*Dv;
-
-            obj.gamm_u = h_u*ops_u.borrowing.M.d1;
-            obj.gamm_v = h_v*ops_v.borrowing.M.d1;
-        end
-
-
-        % Closure functions return the opertors applied to the own doamin to close the boundary
-        % Penalty functions return the opertors to force the solution. In the case of an interface it returns the operator applied to the other doamin.
-        %       boundary            is a string specifying the boundary e.g. 'l','r' or 'e','w','n','s'.
-        %       type                is a string specifying the type of boundary condition if there are several.
-        %       data                is a function returning the data that should be applied at the boundary.
-        %       neighbour_scheme    is an instance of Scheme that should be interfaced to.
-        %       neighbour_boundary  is a string specifying which boundary to interface to.
-        function [closure, penalty] = boundary_condition(obj, boundary, type, parameter)
-            default_arg('type','neumann');
-            default_arg('parameter', []);
-
-            [e, d_n, d_t, coeff_n, coeff_t, s, gamm, halfnorm_inv  ,              ~,          ~, ~, scale_factor] = obj.get_boundary_ops(boundary);
-            switch type
-                % Dirichlet boundary condition
-                case {'D','d','dirichlet'}
-                    % v denotes the solution in the neighbour domain
-                    tuning = 1.2;
-                    % tuning = 20.2;
-                    [e, d_n, d_t, coeff_n, coeff_t, s, gamm, halfnorm_inv_n, halfnorm_inv_t, halfnorm_t] = obj.get_boundary_ops(boundary);
-
-                    a_n = spdiag(coeff_n);
-                    a_t = spdiag(coeff_t);
-
-                    F = (s * a_n * d_n' + s * a_t*d_t')';
-
-                    u = obj;
-
-                    b1 = gamm*u.lambda./u.a11.^2;
-                    b2 = gamm*u.lambda./u.a22.^2;
-
-                    tau  = -1./b1 - 1./b2;
-                    tau = tuning * spdiag(tau);
-                    sig1 = 1;
-
-                    penalty_parameter_1 = halfnorm_inv_n*(tau + sig1*halfnorm_inv_t*F*e'*halfnorm_t)*e;
-
-                    closure = obj.Ji*obj.c^2 * penalty_parameter_1*e';
-                    penalty = -obj.Ji*obj.c^2 * penalty_parameter_1;
-
-
-                % Neumann boundary condition
-                case {'N','n','neumann'}
-                    c = obj.c;
-
-                    a_n = spdiags(coeff_n,0,length(coeff_n),length(coeff_n));
-                    a_t = spdiags(coeff_t,0,length(coeff_t),length(coeff_t));
-                    d = (a_n * d_n' + a_t*d_t')';
-
-                    tau1 = -s;
-                    tau2 = 0;
-                    tau = c.^2 * obj.Ji*(tau1*e + tau2*d);
-
-                    closure = halfnorm_inv*tau*d';
-                    penalty = -halfnorm_inv*tau;
-
-                % Characteristic boundary condition
-                case {'characteristic', 'char', 'c'}
-                    default_arg('parameter', 1);
-                    beta = parameter;
-                    c = obj.c;
-
-                    a_n = spdiags(coeff_n,0,length(coeff_n),length(coeff_n));
-                    a_t = spdiags(coeff_t,0,length(coeff_t),length(coeff_t));
-                    d = s*(a_n * d_n' + a_t*d_t')'; % outward facing normal derivative
-
-                    tau = -c.^2 * 1/beta*obj.Ji*e;
-
-                    warning('is this right?! /c?')
-                    closure{1} = halfnorm_inv*tau/c*spdiag(scale_factor)*e';
-                    closure{2} = halfnorm_inv*tau*beta*d';
-                    penalty = -halfnorm_inv*tau;
-
-                % Unknown, boundary condition
-                otherwise
-                    error('No such boundary condition: type = %s',type);
-            end
-        end
-
-        function [closure, penalty] = interface(obj, boundary, neighbour_scheme, neighbour_boundary, type)
-            % u denotes the solution in the own domain
-            % v denotes the solution in the neighbour domain
-            tuning = 1.2;
-            % tuning = 20.2;
-            [e_u, d_n_u, d_t_u, coeff_n_u, coeff_t_u, s_u, gamm_u, halfnorm_inv_u_n, halfnorm_inv_u_t, halfnorm_u_t, I_u] = obj.get_boundary_ops(boundary);
-            [e_v, d_n_v, d_t_v, coeff_n_v, coeff_t_v, s_v, gamm_v, halfnorm_inv_v_n, halfnorm_inv_v_t, halfnorm_v_t, I_v] = neighbour_scheme.get_boundary_ops(neighbour_boundary);
-
-            a_n_u = spdiag(coeff_n_u);
-            a_t_u = spdiag(coeff_t_u);
-            a_n_v = spdiag(coeff_n_v);
-            a_t_v = spdiag(coeff_t_v);
-
-            F_u = (s_u * a_n_u * d_n_u' + s_u * a_t_u*d_t_u')';
-            F_v = (s_v * a_n_v * d_n_v' + s_v * a_t_v*d_t_v')';
-
-            u = obj;
-            v = neighbour_scheme;
-
-            b1_u = gamm_u*u.lambda(I_u)./u.a11(I_u).^2;
-            b2_u = gamm_u*u.lambda(I_u)./u.a22(I_u).^2;
-            b1_v = gamm_v*v.lambda(I_v)./v.a11(I_v).^2;
-            b2_v = gamm_v*v.lambda(I_v)./v.a22(I_v).^2;
-
-            tau = -1./(4*b1_u) -1./(4*b1_v) -1./(4*b2_u) -1./(4*b2_v);
-            tau = tuning * spdiag(tau);
-            sig1 = 1/2;
-            sig2 = -1/2;
-
-            penalty_parameter_1 = halfnorm_inv_u_n*(e_u*tau + sig1*halfnorm_inv_u_t*F_u*e_u'*halfnorm_u_t*e_u);
-            penalty_parameter_2 = halfnorm_inv_u_n * sig2 * e_u;
-
-
-            closure = obj.Ji*obj.c^2 * ( penalty_parameter_1*e_u' + penalty_parameter_2*F_u');
-            penalty = obj.Ji*obj.c^2 * (-penalty_parameter_1*e_v' + penalty_parameter_2*F_v');
-        end
-
-        % Ruturns the boundary ops and sign for the boundary specified by the string boundary.
-        % The right boundary is considered the positive boundary
-        %
-        %  I -- the indecies of the boundary points in the grid matrix
-        function [e, d_n, d_t, coeff_n, coeff_t, s, gamm, halfnorm_inv_n, halfnorm_inv_t, halfnorm_t, I, scale_factor] = get_boundary_ops(obj, boundary)
-
-            % gridMatrix = zeros(obj.m(2),obj.m(1));
-            % gridMatrix(:) = 1:numel(gridMatrix);
-
-            ind = grid.funcToMatrix(obj.grid, 1:prod(obj.m));
-
-            switch boundary
-                case 'w'
-                    e = obj.e_w;
-                    d_n = obj.du_w;
-                    d_t = obj.dv_w;
-                    s = -1;
-
-                    I = ind(1,:);
-                    coeff_n = obj.a11(I);
-                    coeff_t = obj.a12(I);
-                    scale_factor = sqrt(obj.x_v(I).^2 + obj.y_v(I).^2);
-                case 'e'
-                    e = obj.e_e;
-                    d_n = obj.du_e;
-                    d_t = obj.dv_e;
-                    s = 1;
-
-                    I = ind(end,:);
-                    coeff_n = obj.a11(I);
-                    coeff_t = obj.a12(I);
-                    scale_factor = sqrt(obj.x_v(I).^2 + obj.y_v(I).^2);
-                case 's'
-                    e = obj.e_s;
-                    d_n = obj.dv_s;
-                    d_t = obj.du_s;
-                    s = -1;
-
-                    I = ind(:,1)';
-                    coeff_n = obj.a22(I);
-                    coeff_t = obj.a12(I);
-                    scale_factor = sqrt(obj.x_u(I).^2 + obj.y_u(I).^2);
-                case 'n'
-                    e = obj.e_n;
-                    d_n = obj.dv_n;
-                    d_t = obj.du_n;
-                    s = 1;
-
-                    I = ind(:,end)';
-                    coeff_n = obj.a22(I);
-                    coeff_t = obj.a12(I);
-                    scale_factor = sqrt(obj.x_u(I).^2 + obj.y_u(I).^2);
-                otherwise
-                    error('No such boundary: boundary = %s',boundary);
-            end
-
-            switch boundary
-                case {'w','e'}
-                    halfnorm_inv_n = obj.Hiu;
-                    halfnorm_inv_t = obj.Hiv;
-                    halfnorm_t = obj.Hv;
-                    gamm = obj.gamm_u;
-                case {'s','n'}
-                    halfnorm_inv_n = obj.Hiv;
-                    halfnorm_inv_t = obj.Hiu;
-                    halfnorm_t = obj.Hu;
-                    gamm = obj.gamm_v;
-            end
-        end
-
-        function N = size(obj)
-            N = prod(obj.m);
-        end
-
-
-    end
-end
\ No newline at end of file
--- a/+scheme/error1d.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-function e = error1d(discr, v1, v2)
-    h = discr.h;
-    e = sqrt(h*sum((v1-v2).^2));
-end
\ No newline at end of file
--- a/+scheme/error2d.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-function e = error2d(discr, v1, v2)
-    % If v1 and v2 are more complex types, something like grid functions... Then we may use .getVectorFrom here!
-    h = discr.h;
-    e = sqrt(h.^2*sum((v1-v2).^2));
-end
\ No newline at end of file
--- a/+scheme/errorMax.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-function e = errorMax(~, v1, v2)
-    e = max(abs(v1-v2));
-end
--- a/+scheme/errorRelative.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-function e = errorRelative(~,v1,v2)
-    e = sqrt(sum((v1-v2).^2)/sum(v2.^2));
-end
\ No newline at end of file
--- a/+scheme/errorSbp.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-function e = errorSbp(discr, v1, v2)
-    % If v1 and v2 are more complex types, something like grid functions... Then we may use .getVectorFrom here!
-    H = discr.H;
-    err = v2 - v1;
-    e = sqrt(err'*H*err);
-end
\ No newline at end of file
--- a/+scheme/errorVector.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-function e = errorVector(~, v1, v2)
-    e = v2-v1;
-end
\ No newline at end of file
--- a/+time/+cdiff/cdiff.m	Sat Oct 24 20:58:26 2020 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-% Takes a step of
-%   v_tt = Dv+Ev_t+S
-%
-%   1/k^2 * (v_next - 2v + v_prev) = Dv  + E 1/(2k)(v_next - v_prev) + S
-%
-function [v_next, v] = cdiff(v, v_prev, k, D, E, S)
-    %   1/k^2 * (v_next - 2v + v_prev) = Dv  + E 1/(2k)(v_next - v_prev) + S
-    %       ekv to
-    %   A v_next = B v + C v_prev + S
-    I = speye(size(D));
-    A =  1/k^2 * I - 1/(2*k)*E;
-    B =  2/k^2 * I + D;
-    C = -1/k^2 * I - 1/(2*k)*E;
-
-    v_next = A\(B*v + C*v_prev + S);
-end
\ No newline at end of file
--- a/+time/Cdiff.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+time/Cdiff.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,36 +1,45 @@
 classdef Cdiff < time.Timestepper
     properties
-        D
-        E
-        S
+        A, B, C
+        AA, BB, CC
+        G
         k
         t
-        v
-        v_prev
+        v, v_prev
         n
     end
 
 
     methods
-        % Solves u_tt = Du + Eu_t + S
-        % D, E, S can either all be constants or all be function handles,
-        % They can also be omitted by setting them equal to the empty matrix.
-        % Cdiff(D, E, S, k, t0, n0, v, v_prev)
-        function obj = Cdiff(D, E, S, k, t0, n0, v, v_prev)
-            m = length(v);
-            default_arg('E',sparse(m,m));
-            default_arg('S',sparse(m,1));
+        % Solves
+        %   A*v_tt + B*v_t + C*v = G(t)
+        %   v(t0) = v0
+        %   v_t(t0) = v0t
+        % starting at time t0 with timestep k
+        % Using
+        % A*Dp*Dm*v_n + B*D0*v_n + C*v_n = G(t_n)
+        function obj = Cdiff(A, B, C, G, v0, v0t, k, t0)
+            m = length(v0);
+            default_arg('A', speye(m));
+            default_arg('B', sparse(m,m));
+            default_arg('G', @(t) sparse(m,1));
+            default_arg('t0', 0);
 
-            obj.D = D;
-            obj.E = E;
-            obj.S = S;
+            obj.A = A;
+            obj.B = B;
+            obj.C = C;
+            obj.G = G;
 
+            % Rewrite as AA*v_(n+1) + BB*v_n + CC*v_(n-1) = G(t_n)
+            obj.AA =    A/k^2 + B/(2*k);
+            obj.BB = -2*A/k^2 + C;
+            obj.CC =    A/k^2 - B/(2*k);
 
             obj.k = k;
-            obj.t = t0;
-            obj.n = n0;
-            obj.v = v;
-            obj.v_prev = v_prev;
+            obj.v_prev = v0;
+            obj.v = v0 + k*v0t;
+            obj.t = t0+k;
+            obj.n = 1;
         end
 
         function [v,t] = getV(obj)
@@ -43,10 +52,21 @@
             t = obj.t;
         end
 
+        function E = getEnergy(obj)
+            v  = obj.v;
+            vp = obj.v_prev;
+            vt = (obj.v - obj.v_prev)/obj.k;
+
+            E = vt'*obj.A*vt + v'*obj.C*vp;
+        end
+
         function obj = step(obj)
-            [obj.v, obj.v_prev] = time.cdiff.cdiff(obj.v, obj.v_prev, obj.k, obj.D, obj.E, obj.S);
+            v_next = obj.AA\(-obj.BB*obj.v - obj.CC*obj.v_prev + obj.G(obj.t));
+
+            obj.v_prev = obj.v;
+            obj.v      = v_next;
             obj.t = obj.t + obj.k;
             obj.n = obj.n + 1;
         end
     end
-end
\ No newline at end of file
+end
--- a/+time/CdiffImplicit.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+time/CdiffImplicit.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,7 +1,8 @@
 classdef CdiffImplicit < time.Timestepper
     properties
-        A, B, C, G
+        A, B, C
         AA, BB, CC
+        G
         k
         t
         v, v_prev
@@ -13,61 +14,36 @@
 
     methods
         % Solves
-        %   A*u_tt + B*u + C*v_t = G(t)
-        %   u(t0) = f1
-        %   u_t(t0) = f2
-        % starting at time t0 with timestep k
-
-        % TODO: Fix order of matrices
-        function obj = CdiffImplicit(A, B, C, G, f1, f2, k, t0)
-            default_arg('A', []);
-            default_arg('C', []);
-            default_arg('G', []);
-            default_arg('f1', 0);
-            default_arg('f2', 0);
+        %   A*v_tt + B*v_t + C*v = G(t)
+        %   v(t0) = v0
+        %   v_t(t0) = v0t
+        % starting at time t0 with timestep
+        % Using
+        % A*Dp*Dm*v_n + B*D0*v_n + C*I0*v_n = G(t_n)
+        function obj = CdiffImplicit(A, B, C, G, v0, v0t, k, t0)
+            m = length(v0);
+            default_arg('A', speye(m));
+            default_arg('B', sparse(m,m));
+            default_arg('G', @(t) sparse(m,1));
             default_arg('t0', 0);
 
-            m = size(B,1);
-
-            if isempty(A)
-                A = speye(m);
-            end
-
-            if isempty(C)
-                C = sparse(m,m);
-            end
-
-            if isempty(G)
-                G = @(t) sparse(m,1);
-            end
-
-            if isempty(f1)
-                f1 = sparse(m,1);
-            end
-
-            if isempty(f2)
-                f2 = sparse(m,1);
-            end
-
             obj.A = A;
             obj.B = B;
             obj.C = C;
             obj.G = G;
 
-            AA = 1/k^2*A + 1/2*B + 1/(2*k)*C;
-            BB = -2/k^2*A;
-            CC = 1/k^2*A + 1/2*B - 1/(2*k)*C;
-            % AA*v_next + BB*v + CC*v_prev == G(t_n)
+            % Rewrite as AA*v_(n+1) + BB*v_n + CC*v_(n-1) = G(t_n)
+            AA =    A/k^2 + B/(2*k) + C/2;
+            BB = -2*A/k^2;
+            CC =    A/k^2 - B/(2*k) + C/2;
 
             obj.AA = AA;
             obj.BB = BB;
             obj.CC = CC;
 
-            v_prev = f1;
+            v_prev = v0;
             I = speye(m);
-            % v = (1/k^2*A)\((1/k^2*A - 1/2*B)*f1 + (1/k*I - 1/2*C)*f2 + 1/2*G(0));
-            v = f1 + k*f2;
-
+            v = v0 + k*v0t;
 
             if ~issparse(A) || ~issparse(B) || ~issparse(C)
                 error('LU factorization with full pivoting only works for sparse matrices.')
@@ -80,7 +56,6 @@
             obj.p = p;
             obj.q = q;
 
-
             obj.k = k;
             obj.t = t0+k;
             obj.n = 1;
@@ -94,17 +69,17 @@
         end
 
         function [vt,t] = getVt(obj)
-            % Calculate next time step to be able to do centered diff.
-            v_next = zeros(size(obj.v));
-            b = obj.G(obj.t) - obj.BB*obj.v - obj.CC*obj.v_prev;
+            vt = (obj.v-obj.v_prev)/obj.k; % Could be improved using u_tt = f(u))
+            t = obj.t;
+        end
 
-            y = obj.L\b(obj.p);
-            z = obj.U\y;
-            v_next(obj.q) = z;
+        % Calculate the conserved energy (Dm*v_n)^2_A + Im*v_n^2_B
+        function E = getEnergy(obj)
+            v  = obj.v;
+            vp = obj.v_prev;
+            vt = (obj.v - obj.v_prev)/obj.k;
 
-
-            vt = (v_next-obj.v_prev)/(2*obj.k);
-            t = obj.t;
+            E = vt'*obj.A*vt + 1/2*(v'*obj.C*v + vp'*obj.C*vp);
         end
 
         function obj = step(obj)
@@ -125,30 +100,3 @@
         end
     end
 end
-
-
-
-
-
-%%% Derivation
-% syms A B C G
-% syms n k
-% syms f1 f2
-
-% v = symfun(sym('v(n)'),n);
-
-
-% d = A/k^2 * (v(n+1) - 2*v(n) +v(n-1)) + B/2*(v(n+1)+v(n-1)) + C/(2*k)*(v(n+1) - v(n-1)) == G
-% ic1 = v(0) == f1
-% ic2 = A/k*(v(1)-f1) + k/2*(B*f1 + C*f2 - G) - f2 == 0
-
-% c = collect(d, [v(n) v(n-1) v(n+1)]) % (-(2*A)/k^2)*v(n) + (B/2 + A/k^2 - C/(2*k))*v(n - 1) + (B/2 + A/k^2 + C/(2*k))*v(n + 1) == G
-% syms AA BB CC
-% % AA = B/2 + A/k^2 + C/(2*k)
-% % BB = -(2*A)/k^2
-% % CC = B/2 + A/k^2 - C/(2*k)
-% s = subs(c, [B/2 + A/k^2 + C/(2*k), -(2*A)/k^2, B/2 + A/k^2 - C/(2*k)], [AA, BB, CC])
-
-
-% ic2_a = collect(ic2, [v(1) f1 f2]) % (A/k)*v(1) + ((B*k)/2 - A/k)*f1 + ((C*k)/2 - 1)*f2 - (G*k)/2 == 0
-
--- a/+time/Rungekutta4SecondOrder.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/+time/Rungekutta4SecondOrder.m	Thu Mar 10 16:54:26 2022 +0100
@@ -36,9 +36,32 @@
             obj.S = S;
             obj.m = length(v0);
             obj.n = 0;
-
+            
+            % Construct RHS system on first order form. 
+            % 3 different setups are handeled:
+            % If none of D, E, S are time-dependent (e.g function_handles)
+            % then a complete matrix of the RHS is constructed.
+            % If the source term S is time-depdentent, then the first order system matrix M
+            % is formed via D and E, while S is kept as a function handle
+            % If D or E are time-dependent, then all terns are treated as function handles.
+            if ~isa(D, 'function_handle') && ~isa(E, 'function_handle') && isa(S, 'function_handle')
+                default_arg('D', sparse(obj.m, obj.m));
+                default_arg('E', sparse(obj.m, obj.m));
+                default_arg('S', @(t)sparse(obj.m, 1));
+                
+                I = speye(obj.m);
+                O = sparse(obj.m,obj.m);
 
-            if isa(D, 'function_handle') || isa(E, 'function_handle') || isa(S, 'function_handle')
+                obj.M = [
+                    O, I;
+                    D, E;
+                ];
+
+                obj.k = k;
+                obj.t = t0;
+                obj.w = [v0; v0t];
+                obj.F = @(w,t)obj.rhsTimeDepS(w,t,obj.M,S,obj.m);
+            elseif isa(D, 'function_handle') || isa(E, 'function_handle')
                 default_arg('D', @(t)sparse(obj.m, obj.m));
                 default_arg('E', @(t)sparse(obj.m, obj.m));
                 default_arg('S', @(t)sparse(obj.m, 1)    );
@@ -63,7 +86,6 @@
                     D(t)*w(1:obj.m) + E(t)*w(obj.m+1:end) + S(t);
                 ];
             else
-
                 default_arg('D', sparse(obj.m, obj.m));
                 default_arg('E', sparse(obj.m, obj.m));
                 default_arg('S', sparse(obj.m, 1)    );
@@ -110,6 +132,11 @@
         function k = getTimeStep(lambda)
             k = rk4.get_rk4_time_step(lambda);
         end
+        
+        function F = rhsTimeDepS(w,t,M,S,m)
+            F = M*w;
+            F(m+1:end) = F(m+1:end) + S(t);
+        end
     end
 
 end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/assertLength.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,4 @@
+function assertLength(A,l)
+    assert(isvector(A), sprintf('Expected ''%s'' to be a vector, got matrix of size %s',inputname(1), toString(size(A))));
+    assert(length(A) == l, sprintf('Expected ''%s'' to have length %d, got %d', inputname(1), l, length(A)));
+end
--- a/assertSize.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/assertSize.m	Thu Mar 10 16:54:26 2022 +0100
@@ -2,13 +2,13 @@
 function assertSize(A,varargin)
     if length(varargin) == 1
         s = varargin{1};
-        errmsg = sprintf('Expected %s to have size %s, got: %s',inputname(1), toString(s), toString(size(A)));
-        assert(all(size(A) == s), errmsg);
+        assert(length(size(A)) == length(s), sprintf('Expected ''%s'' to have dimension %d, got %d', inputname(1), length(s), length(size(A))));
+        assert(all(size(A) == s), sprintf('Expected ''%s'' to have size %s, got: %s',inputname(1), toString(s), toString(size(A))));
     elseif length(varargin) == 2
         dim = varargin{1};
         s = varargin{2};
 
-        errmsg = sprintf('Expected %s to have size %d along dimension %d, got: %d',inputname(1), s, dim, size(A,dim));
+        errmsg = sprintf('Expected ''%s'' to have size %d along dimension %d, got: %d',inputname(1), s, dim, size(A,dim));
         assert(size(A,dim) == s, errmsg);
     else
         error('Expected 2 or 3 arguments to assertSize()');
--- a/diracDiscr.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/diracDiscr.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,116 +1,74 @@
-
-function d = diracDiscr(x_s, x, m_order, s_order, H)
-    % n-dimensional delta function
-    % x_s: source point coordinate vector, e.g. [x, y] or [x, y, z].
-    % x: cell array of grid point column vectors for each dimension.
-    % m_order: Number of moment conditions
-    % s_order: Number of smoothness conditions
-    % H: cell array of 1D norm matrices
-
-    dim = length(x_s);
+% n-dimensional delta function
+% g: cartesian grid
+% x_s: source point coordinate vector, e.g. [x; y] or [x; y; z].
+% m_order: Number of moment conditions
+% s_order: Number of smoothness conditions
+% H: cell array of 1D norm matrices
+function d = diracDiscr(g, x_s, m_order, s_order, H)
+    assertType(g, 'grid.Cartesian');
+    
+    dim = g.d;
     d_1D = cell(dim,1);
 
-    % If 1D, non-cell input is accepted
-    if dim == 1 && ~iscell(x)
-        d = diracDiscr1D(x_s, x, m_order, s_order, H);
-
-    else
-        for i = 1:dim
-            d_1D{i} = diracDiscr1D(x_s(i), x{i}, m_order, s_order, H{i});
-        end
-
-        d = d_1D{dim};
-        for i = dim-1: -1: 1
-            % Perform outer product, transpose, and then turn into column vector
-            d = (d_1D{i}*d')';
-            d = d(:);
-        end
+    % Allow for non-cell input in 1D
+    if dim == 1
+        H = {H};
+    end
+    % Create 1D dirac discr for each coordinate dir.
+    for i = 1:dim
+        d_1D{i} = diracDiscr1D(x_s(i), g.x{i}, m_order, s_order, H{i});
     end
 
+    d = d_1D{dim};
+    for i = dim-1: -1: 1
+        % Perform outer product, transpose, and then turn into column vector
+        d = (d_1D{i}*d')';
+        d = d(:);
+    end
 end
 
 
 % Helper function for 1D delta functions
-function ret = diracDiscr1D(x_0in , x , m_order, s_order, H)
-
-m = length(x);
-
-% Return zeros if x0 is outside grid
-if(x_0in < x(1) || x_0in > x(end) )
+function ret = diracDiscr1D(x_s, x, m_order, s_order, H)
 
-    ret = zeros(size(x));
-
-else
-
-    fnorm = diag(H);
-    eta = abs(x-x_0in);
-    tot = m_order+s_order;
+    % Return zeros if x0 is outside grid
+    if x_s < x(1) || x_s > x(end)
+        ret = zeros(size(x));
+        return
+    end
+       
+    tot_order = m_order+s_order; %This is equiv. to the number of equations solved for
     S = [];
     M = [];
 
     % Get interior grid spacing
-    middle = floor(m/2);
-    h = x(middle+1) - x(middle);
-
-    poss = find(tot*h/2 >= eta);
+    middle = floor(length(x)/2);
+    h = x(middle+1) - x(middle); % Use middle point to allow for staggered grids.
 
-    % Ensure that poss is not too long
-    if length(poss) == (tot + 2)
-        poss = poss(2:end-1);
-    elseif length(poss) == (tot + 1)
-        poss = poss(1:end-1);
-    end
-
-    % Use first tot grid points
-    if length(poss)<tot && x_0in < x(1) + ceil(tot/2)*h;
-        index=1:tot;
-        pol=(x(1:tot)-x(1))/(x(tot)-x(1));
-        x_0=(x_0in-x(1))/(x(tot)-x(1));
-        norm=fnorm(1:tot)/h;
+    index = sourceIndices(x_s, x, tot_order, h);
 
-    % Use last tot grid points
-    elseif length(poss)<tot && x_0in > x(end) - ceil(tot/2)*h;
-        index = length(x)-tot+1:length(x);
-        pol = (x(end-tot+1:end)-x(end-tot+1))/(x(end)-x(end-tot+1));
-        norm = fnorm(end-tot+1:end)/h;
-        x_0 = (x_0in-x(end-tot+1))/(x(end)-x(end-tot+1));
+    polynomial = (x(index)-x(index(1)))/(x(index(end))-x(index(1)));
+    x_0 = (x_s-x(index(1)))/(x(index(end))-x(index(1)));
 
-    % Interior, compensate for round-off errors.
-    elseif length(poss) < tot
-        if poss(end)<m
-            poss = [poss; poss(end)+1];
-        else
-            poss = [poss(1)-1; poss];
-        end
-        pol = (x(poss)-x(poss(1)))/(x(poss(end))-x(poss(1)));
-        x_0 = (x_0in-x(poss(1)))/(x(poss(end))-x(poss(1)));
-        norm = fnorm(poss)/h;
-        index = poss;
+    quadrature = diag(H);
+    quadrature_weights = quadrature(index)/h;
 
-    % Interior
-    else
-        pol = (x(poss)-x(poss(1)))/(x(poss(end))-x(poss(1)));
-        x_0 = (x_0in-x(poss(1)))/(x(poss(end))-x(poss(1)));
-        norm = fnorm(poss)/h;
-        index = poss;
-    end
-
-    h_pol = pol(2)-pol(1);
-    b = zeros(m_order+s_order,1);
+    h_polynomial = polynomial(2)-polynomial(1);
+    b = zeros(tot_order,1);
 
     for i = 1:m_order
         b(i,1) = x_0^(i-1);
     end
 
-    for i = 1:(m_order+s_order)
+    for i = 1:tot_order
         for j = 1:m_order
-            M(j,i) = pol(i)^(j-1)*h_pol*norm(i);
+            M(j,i) = polynomial(i)^(j-1)*h_polynomial*quadrature_weights(i);
         end
     end
 
-    for i = 1:(m_order+s_order)
+    for i = 1:tot_order
         for j = 1:s_order
-            S(j,i) = (-1)^(i-1)*pol(i)^(j-1);
+            S(j,i) = (-1)^(i-1)*polynomial(i)^(j-1);
         end
     end
 
@@ -118,13 +76,31 @@
 
     d = A\b;
     ret = x*0;
-    ret(index) = d/h*h_pol;
-end
-
+    ret(index) = d/h*h_polynomial;
 end
 
 
-
+function I = sourceIndices(x_s, x, tot_order, h)
+    % Find the indices that are within range of of the point source location
+    I = find(tot_order*h/2 >= abs(x-x_s));
 
-
-
+    if length(I) > tot_order
+        if length(I) == tot_order + 2
+            I = I(2:end-1);
+        elseif length(I) == tot_order + 1
+            I = I(1:end-1);
+        end
+    elseif length(I) < tot_order
+        if x_s < x(1) + ceil(tot_order/2)*h
+            I = 1:tot_order;
+        elseif x_s > x(end) - ceil(tot_order/2)*h
+            I = length(x)-tot_order+1:length(x);
+        else
+            if I(end) < length(x)
+                I = [I; I(end)+1];
+            else
+                I = [I(1)-1; I];
+            end
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/diracDiscrCurve.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,63 @@
+% 2-dimensional delta function for single-block curvilinear grid
+% x_s:      source point coordinate vector, e.g. [x; y] or [x; y; z].
+% g:        single-block grid containing the source
+% m_order:  Number of moment conditions
+% s_order:  Number of smoothness conditions
+% order:    Order of SBP derivative approximations
+% opSet:    Cell array of function handle to opSet generator
+function d = diracDiscrCurve(x_s, g, m_order, s_order, order, opSet)
+    default_arg('order', m_order);
+    default_arg('opSet', {@sbp.D2Variable, @sbp.D2Variable});
+
+    dim = length(x_s);
+    assert(dim == 2, 'diracDiscrCurve: Only implemented for 2d.');
+    assertType(g, 'grid.Curvilinear');
+
+    % Compute Jacobian
+    J = jacobian(g, opSet, order);
+
+    % Find approximate logical coordinates of point source
+    X = g.points();
+    U = g.logic.points();
+    U_interp = scatteredInterpolant(X, U(:,1));
+    V_interp = scatteredInterpolant(X, U(:,2));
+    uS = U_interp(x_s);
+    vS = V_interp(x_s);
+
+    % Get quadrature matrices for moment conditions
+    m = g.size();
+    ops_u = opSet{1}(m(1), {0, 1}, order);
+    ops_v = opSet{2}(m(2), {0, 1}, order);
+    H_u =  ops_u.H;
+    H_v =  ops_v.H;
+
+    % Get delta function for logical grid and scale by Jacobian
+    d = (1./J).*diracDiscr(g, [uS; vS], m_order, s_order, {H_u, H_v});
+end
+
+function J = jacobian(g, opSet, order)
+    m = g.size();
+    m_u = m(1);
+    m_v = m(2);
+    ops_u = opSet{1}(m_u, {0, 1}, order);
+    ops_v = opSet{2}(m_v, {0, 1}, order);
+    I_u = speye(m_u);
+    I_v = speye(m_v);
+
+    D1_u = ops_u.D1;
+    D1_v = ops_v.D1;
+
+    Du = kr(D1_u,I_v);
+    Dv = kr(I_u,D1_v);
+
+    coords = g.points();
+    x = coords(:,1);
+    y = coords(:,2);
+
+    x_u = Du*x;
+    x_v = Dv*x;
+    y_u = Du*y;
+    y_v = Dv*y;
+
+    J = x_u.*y_v - x_v.*y_u;
+end
--- a/diracDiscrTest.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/diracDiscrTest.m	Thu Mar 10 16:54:26 2022 +0100
@@ -2,42 +2,24 @@
 	    tests = functiontests(localfunctions);
 end
 
-function testLeftGP(testCase)
-
-    orders = [2, 4, 6];
-    mom_conds = orders;
-
-    for o = 1:length(orders)
-        order = orders(o);
-        mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
-
-        % Test left boundary grid points
-        x0s = xl + [0, h, 2*h];
-
-        for j = 1:length(fs)
-                f = fs{j};
-                fx = f(x);
-            for i = 1:length(x0s)
-                x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
-                integral = delta'*H*fx;
-                err = abs(integral - f(x0));
-                testCase.verifyLessThan(err, 1e-12);
-            end
-        end
-    end
-end
+%TODO:
+%   1.  Test discretizing with smoothness conditions.
+%       Only discretization with moment conditions currently tested.
+%   2.  Test using other types of grids. Only equidistant grids currently used.
 
 function testLeftRandom(testCase)
 
     orders = [2, 4, 6];
     mom_conds = orders;
+    rng(1) % Set seed
 
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+        [g, H, fs] = setup1D(order, mom_cond);
+        xl = g.lim{1}{1};
+        h = g.scaling();
+        x = g.x{1};
 
         % Test random points near left boundary
         x0s = xl + 2*h*rand(1,10);
@@ -47,34 +29,7 @@
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
-                integral = delta'*H*fx;
-                err = abs(integral - f(x0));
-                testCase.verifyLessThan(err, 1e-12);
-            end
-        end
-    end
-end
-
-function testRightGP(testCase)
-
-    orders = [2, 4, 6];
-    mom_conds = orders;
-
-    for o = 1:length(orders)
-        order = orders(o);
-        mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
-
-        % Test right boundary grid points
-        x0s = xr-[0, h, 2*h];
-
-        for j = 1:length(fs)
-                f = fs{j};
-                fx = f(x);
-            for i = 1:length(x0s)
-                x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - f(x0));
                 testCase.verifyLessThan(err, 1e-12);
@@ -87,11 +42,15 @@
 
     orders = [2, 4, 6];
     mom_conds = orders;
+    rng(1) % Set seed
 
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+        [g, H, fs] = setup1D(order, mom_cond);
+        xr = g.lim{1}{2};
+        h = g.scaling();
+        x = g.x{1};
 
         % Test random points near right boundary
         x0s = xr - 2*h*rand(1,10);
@@ -101,35 +60,7 @@
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
-                integral = delta'*H*fx;
-                err = abs(integral - f(x0));
-                testCase.verifyLessThan(err, 1e-12);
-            end
-        end
-    end
-end
-
-function testInteriorGP(testCase)
-
-    orders = [2, 4, 6];
-    mom_conds = orders;
-
-    for o = 1:length(orders)
-        order = orders(o);
-        mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
-
-        % Test interior grid points
-        m_half = round(m/2);
-        x0s = xl + (m_half-1:m_half+1)*h;
-
-        for j = 1:length(fs)
-                f = fs{j};
-                fx = f(x);
-            for i = 1:length(x0s)
-                x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - f(x0));
                 testCase.verifyLessThan(err, 1e-12);
@@ -142,11 +73,16 @@
 
     orders = [2, 4, 6];
     mom_conds = orders;
+    rng(1) % Set seed
 
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+        [g, H, fs] = setup1D(order, mom_cond);
+        xl = g.lim{1}{1};
+        xr = g.lim{1}{2};
+        h = g.scaling();
+        x = g.x{1};
 
         % Test random points in interior
         x0s = (xl+2*h) + (xr-xl-4*h)*rand(1,20);
@@ -156,7 +92,7 @@
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - f(x0));
                 testCase.verifyLessThan(err, 1e-12);
@@ -174,7 +110,11 @@
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+        [g, H, fs] = setup1D(order, mom_cond);
+        xl = g.lim{1}{1};
+        xr = g.lim{1}{2};
+        h = g.scaling();
+        x = g.x{1};
 
         % Test points outisde grid
         x0s = [xl-1.1*h, xr+1.1*h];
@@ -184,7 +124,7 @@
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - 0);
                 testCase.verifyLessThan(err, 1e-12);
@@ -201,7 +141,8 @@
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+       [g, H, fs] = setup1D(order, mom_cond);
+        x = g.x{1};
 
         % Test all grid points
         x0s = x;
@@ -211,7 +152,7 @@
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - f(x0));
                 testCase.verifyLessThan(err, 1e-12);
@@ -228,17 +169,18 @@
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond);
+        [g, H, fs] = setup1D(order, mom_cond);
+        x = g.x{1};
 
         % Test halfway between all grid points
-        x0s = 1/2*( x(2:end)+x(1:end-1) );
+        x0s = 1/2*(x(2:end)+x(1:end-1));
 
         for j = 1:length(fs)
                 f = fs{j};
                 fx = f(x);
             for i = 1:length(x0s)
                 x0 = x0s(i);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H*fx;
                 err = abs(integral - f(x0));
                 testCase.verifyLessThan(err, 1e-12);
@@ -247,87 +189,6 @@
     end
 end
 
-% function testAllGPStaggered(testCase)
-
-%     orders = [2, 4, 6];
-%     mom_conds = orders;
-
-%     for o = 1:length(orders)
-%         order = orders(o);
-%         mom_cond = mom_conds(o);
-%         [xl, xr, m, h, x, H, fs] = setupStaggered(order, mom_cond);
-
-%         % Test all grid points
-%         x0s = x;
-
-%         for j = 1:length(fs)
-%                 f = fs{j};
-%                 fx = f(x);
-%             for i = 1:length(x0s)
-%                 x0 = x0s(i);
-%                 delta = diracDiscr(x0, x, mom_cond, 0, H);
-%                 integral = delta'*H*fx;
-%                 err = abs(integral - f(x0));
-%                 testCase.verifyLessThan(err, 1e-12);
-%             end
-%         end
-%     end
-% end
-
-% function testHalfGPStaggered(testCase)
-
-%     orders = [2, 4, 6];
-%     mom_conds = orders;
-
-%     for o = 1:length(orders)
-%         order = orders(o);
-%         mom_cond = mom_conds(o);
-%         [xl, xr, m, h, x, H, fs] = setupStaggered(order, mom_cond);
-
-%         % Test halfway between all grid points
-%         x0s = 1/2*( x(2:end)+x(1:end-1) );
-
-%         for j = 1:length(fs)
-%                 f = fs{j};
-%                 fx = f(x);
-%             for i = 1:length(x0s)
-%                 x0 = x0s(i);
-%                 delta = diracDiscr(x0, x, mom_cond, 0, H);
-%                 integral = delta'*H*fx;
-%                 err = abs(integral - f(x0));
-%                 testCase.verifyLessThan(err, 1e-12);
-%             end
-%         end
-%     end
-% end
-
-% function testRandomStaggered(testCase)
-
-%     orders = [2, 4, 6];
-%     mom_conds = orders;
-
-%     for o = 1:length(orders)
-%         order = orders(o);
-%         mom_cond = mom_conds(o);
-%         [xl, xr, m, h, x, H, fs] = setupStaggered(order, mom_cond);
-
-%         % Test random points within grid boundaries
-%         x0s = xl + (xr-xl)*rand(1,300);
-
-%         for j = 1:length(fs)
-%                 f = fs{j};
-%                 fx = f(x);
-%             for i = 1:length(x0s)
-%                 x0 = x0s(i);
-%                 delta = diracDiscr(x0, x, mom_cond, 0, H);
-%                 integral = delta'*H*fx;
-%                 err = abs(integral - f(x0));
-%                 testCase.verifyLessThan(err, 1e-12);
-%             end
-%         end
-%     end
-% end
-
 %=============== 2D tests ==============================
 function testAllGP2D(testCase)
 
@@ -337,9 +198,9 @@
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xlims, ylims, m, x, X, ~, H, fs] = setup2D(order, mom_cond);
+        [g, H, fs] = setup2D(order, mom_cond);
         H_global = kron(H{1}, H{2});
-
+        X = g.points();
         % Test all grid points
         x0s = X;
 
@@ -348,7 +209,7 @@
                 fx = f(X(:,1), X(:,2));
             for i = 1:length(x0s)
                 x0 = x0s(i,:);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H_global*fx;
                 err = abs(integral - f(x0(1), x0(2)));
                 testCase.verifyLessThan(err, 1e-12);
@@ -361,29 +222,32 @@
 
     orders = [2, 4, 6];
     mom_conds = orders;
+    rng(1) % Set seed
 
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xlims, ylims, m, x, X, h, H, fs] = setup2D(order, mom_cond);
+        [g, H, fs] = setup2D(order, mom_cond);
         H_global = kron(H{1}, H{2});
+        X = g.points();
+        xl = g.lim{1}{1};
+        xr = g.lim{1}{2};
+        yl = g.lim{2}{1};
+        yr = g.lim{2}{2};
+        h = g.scaling();
 
-        xl = xlims{1};
-        xr = xlims{2};
-        yl = ylims{1};
-        yr = ylims{2};
 
         % Test random points, even outside grid
         Npoints = 100;
-        x0s = [(xl-3*h{1}) + (xr-xl+6*h{1})*rand(Npoints,1), ...
-               (yl-3*h{2}) + (yr-yl+6*h{2})*rand(Npoints,1) ];
+        x0s = [(xl-3*h(1)) + (xr-xl+6*h(1))*rand(Npoints,1), ...
+               (yl-3*h(2)) + (yr-yl+6*h(2))*rand(Npoints,1) ];
 
         for j = 1:length(fs)
                 f = fs{j};
                 fx = f(X(:,1), X(:,2));
             for i = 1:length(x0s)
                 x0 = x0s(i,:);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H_global*fx;
 
                 % Integral should be 0 if point is outside grid
@@ -407,9 +271,9 @@
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xlims, ylims, zlims, m, x, X, h, H, fs] = setup3D(order, mom_cond);
+        [g, H, fs] = setup3D(order, mom_cond);
         H_global = kron(kron(H{1}, H{2}), H{3});
-
+        X = g.points();
         % Test all grid points
         x0s = X;
 
@@ -418,7 +282,7 @@
                 fx = f(X(:,1), X(:,2), X(:,3));
             for i = 1:length(x0s)
                 x0 = x0s(i,:);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H_global*fx;
                 err = abs(integral - f(x0(1), x0(2), x0(3)));
                 testCase.verifyLessThan(err, 1e-12);
@@ -431,32 +295,34 @@
 
     orders = [2, 4, 6];
     mom_conds = orders;
+    rng(1) % Set seed
 
     for o = 1:length(orders)
         order = orders(o);
         mom_cond = mom_conds(o);
-        [xlims, ylims, zlims, m, x, X, h, H, fs] = setup3D(order, mom_cond);
+        [g, H, fs] = setup3D(order, mom_cond);
         H_global = kron(kron(H{1}, H{2}), H{3});
-
-        xl = xlims{1};
-        xr = xlims{2};
-        yl = ylims{1};
-        yr = ylims{2};
-        zl = zlims{1};
-        zr = zlims{2};
+        X = g.points();
+        xl = g.lim{1}{1};
+        xr = g.lim{1}{2};
+        yl = g.lim{2}{1};
+        yr = g.lim{2}{2};
+        zl = g.lim{3}{1};
+        zr = g.lim{3}{2};
+        h = g.scaling();
 
         % Test random points, even outside grid
         Npoints = 200;
-        x0s = [(xl-3*h{1}) + (xr-xl+6*h{1})*rand(Npoints,1), ...
-               (yl-3*h{2}) + (yr-yl+6*h{2})*rand(Npoints,1), ...
-               (zl-3*h{3}) + (zr-zl+6*h{3})*rand(Npoints,1) ];
+        x0s = [(xl-3*h(1)) + (xr-xl+6*h(1))*rand(Npoints,1), ...
+               (yl-3*h(2)) + (yr-yl+6*h(2))*rand(Npoints,1), ...
+               (zl-3*h(3)) + (zr-zl+6*h(3))*rand(Npoints,1) ];
 
         for j = 1:length(fs)
                 f = fs{j};
                 fx = f(X(:,1), X(:,2), X(:,3));
             for i = 1:length(x0s)
                 x0 = x0s(i,:);
-                delta = diracDiscr(x0, x, mom_cond, 0, H);
+                delta = diracDiscr(g, x0, mom_cond, 0, H);
                 integral = delta'*H_global*fx;
 
                 % Integral should be 0 if point is outside grid
@@ -475,16 +341,14 @@
 % ======================================================
 % ============== Setup functions =======================
 % ======================================================
-function [xl, xr, m, h, x, H, fs] = setup1D(order, mom_cond)
+function [g, H, fs] = setup1D(order, mom_cond)
 
     % Grid
     xl = -3;
     xr = 900;
     L = xr-xl;
     m = 101;
-    h = (xr-xl)/(m-1);
     g = grid.equidistant(m, {xl, xr});
-    x = g.points();
 
     % Quadrature
     ops = sbp.D2Standard(m, {xl, xr}, order);
@@ -498,18 +362,15 @@
 
 end
 
-function [xlims, ylims, m, x, X, h, H, fs] = setup2D(order, mom_cond)
+function [g, H, fs] = setup2D(order, mom_cond)
 
     % Grid
     xlims = {-3, 20};
     ylims = {-11,5};
     Lx = xlims{2} - xlims{1};
     Ly = ylims{2} - ylims{1};
-
     m = [15, 16];
     g = grid.equidistant(m, xlims, ylims);
-    X = g.points();
-    x = g.x;
 
     % Quadrature
     opsx = sbp.D2Standard(m(1), xlims, order);
@@ -523,16 +384,9 @@
     for p = 0:mom_cond-1
         fs{p+1} = @(x,y) (x/Lx + y/Ly).^p;
     end
-
-    % Grid spacing in interior
-    mm = round(m/2);
-    hx = x{1}(mm(1)+1) - x{1}(mm(1));
-    hy = x{2}(mm(2)+1) - x{2}(mm(2));
-    h = {hx, hy};
-
 end
 
-function [xlims, ylims, zlims, m, x, X, h, H, fs] = setup3D(order, mom_cond)
+function [g, H, fs] = setup3D(order, mom_cond)
 
     % Grid
     xlims = {-3, 20};
@@ -541,11 +395,8 @@
     Lx = xlims{2} - xlims{1};
     Ly = ylims{2} - ylims{1};
     Lz = zlims{2} - zlims{1};
-
     m = [13, 14, 15];
     g = grid.equidistant(m, xlims, ylims, zlims);
-    X = g.points();
-    x = g.x;
 
     % Quadrature
     opsx = sbp.D2Standard(m(1), xlims, order);
@@ -561,35 +412,4 @@
     for p = 0:mom_cond-1
         fs{p+1} = @(x,y,z) (x/Lx + y/Ly + z/Lz).^p;
     end
-
-    % Grid spacing in interior
-    mm = round(m/2);
-    hx = x{1}(mm(1)+1) - x{1}(mm(1));
-    hy = x{2}(mm(2)+1) - x{2}(mm(2));
-    hz = x{3}(mm(3)+1) - x{3}(mm(3));
-    h = {hx, hy, hz};
-
 end
-
-function [xl, xr, m, h, x, H, fs] = setupStaggered(order, mom_cond)
-
-    % Grid
-    xl = -3;
-    xr = 900;
-    L = xr-xl;
-    m = 101;
-    [~, g_dual] = grid.primalDual1D(m, {xl, xr});
-    x = g_dual.points();
-    h = g_dual.h;
-
-    % Quadrature
-    ops = sbp.D1Staggered(m, {xl, xr}, order);
-    H = ops.H_dual;
-
-    % Moment conditions
-    fs = cell(mom_cond,1);
-    for p = 0:mom_cond-1
-        fs{p+1} = @(x) (x/L).^p;
-    end
-
-end
--- a/runtestsAll.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/runtestsAll.m	Thu Mar 10 16:54:26 2022 +0100
@@ -15,7 +15,7 @@
     rootSuite = matlab.unittest.TestSuite.fromFolder(pwd);
     packageSuites = {};
     for i = 1:length(packages)
-        packageSuites{i} = matlab.unittest.TestSuite.fromPackage(packages{i});
+        packageSuites{i} = matlab.unittest.TestSuite.fromPackage(packages{i}, 'IncludingSubpackages', true);
     end
 
     ts = [rootSuite, packageSuites{:}];
--- a/runtestsPackage.m	Sat Oct 24 20:58:26 2020 -0700
+++ b/runtestsPackage.m	Thu Mar 10 16:54:26 2022 +0100
@@ -1,4 +1,4 @@
 function res = runtestsPackage(pkgName)
-    ts = matlab.unittest.TestSuite.fromPackage(pkgName);
+    ts = matlab.unittest.TestSuite.fromPackage(pkgName, 'IncludingSubpackages', true);
     res = ts.run();
-end
\ No newline at end of file
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/show.m	Thu Mar 10 16:54:26 2022 +0100
@@ -0,0 +1,5 @@
+function show(str)
+    assertType(str, {'string', 'char'})
+    val = evalin('caller',str);
+    fprintf('%s => %s\n\n', str, toString(val));
+end
\ No newline at end of file