changeset 592:4422c4476650 feature/utux2D

Merge with feature/grids
author Martin Almquist <martin.almquist@it.uu.se>
date Mon, 11 Sep 2017 14:17:15 +0200
parents 39554f2de783 (current diff) 97b9a0023d38 (diff)
children a2ddaccf5fd1
files +multiblock/BoundaryGroupTest.m textTable.m
diffstat 47 files changed, 1454 insertions(+), 335 deletions(-) [+]
line wrap: on
line diff
--- a/+blockmatrix/fromMatrixTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/fromMatrixTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -46,6 +46,20 @@
                 [23 5; 4 6; 10 12; 11 18], [7 14 16; 13 20 22; 19 21 3; 25 2 9];
             };
         },
+        {
+            {
+                magic(3),
+                {[1 0 2],[1 2 0]}
+            },
+            mat2cell(magic(3),[1 0 2],[1 2 0])
+        },
+        {
+            {
+                zeros(0,1),
+                {0,1},
+            },
+            {zeros(0,1)}
+        },
     };
     for i = 1:length(cases)
         out = convertToFull(blockmatrix.fromMatrix(cases{i}{1}{:}));
--- a/+blockmatrix/getDivision.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/getDivision.m	Mon Sep 11 14:17:15 2017 +0200
@@ -16,7 +16,7 @@
     m = zeros(1,size(C,2));
     for j = 1:size(C,2)
         for i = 1:size(C,1)
-            if isempty(C{i,j})
+            if isNullMatrix(C{i,j})
                 continue
             end
             m(j) = size(C{i,j},2);
@@ -28,10 +28,15 @@
     n = zeros(1,size(C,1));
     for i = 1:size(C,1)
         for j = 1:size(C,2)
-            if isempty(C{i,j})
+            if isNullMatrix(C{i,j})
                 continue
             end
             n(i) = size(C{i,j},1);
         end
     end
-end
\ No newline at end of file
+end
+
+function b = isNullMatrix(A)
+    [n, m] = size(A);
+    b = n == 0 && m == 0;
+end
--- a/+blockmatrix/getDivisionTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/getDivisionTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -56,6 +56,18 @@
             },
             {[2 1], 2}
         },
+        {
+            {zeros(3,0)},
+            {3, 0},
+        },
+        {
+            {zeros(3,0), zeros(3,0)},
+            {3, [0, 0]},
+        },
+        {
+            {zeros(3,0); zeros(2,0)},
+            {[3 2],0},
+        },
     };
 
     for i = 1:length(cases)
--- a/+blockmatrix/isBlockmatrix.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/isBlockmatrix.m	Mon Sep 11 14:17:15 2017 +0200
@@ -4,7 +4,7 @@
         return
     end
 
-    % Make sure all blocks are numerica matrices
+    % Make sure all blocks are numerical matrices
     for i = 1:length(bm)
         if ~isnumeric(bm{i})
             b = false;
--- a/+blockmatrix/isDivision.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/isDivision.m	Mon Sep 11 14:17:15 2017 +0200
@@ -21,11 +21,11 @@
 
 function b = isDivisionVector(v)
     if isempty(v)
-        b = false;
+        b = true;
         return
     end
 
-    if any(v <= 0)
+    if any(v < 0)
         b = false;
         return
     end
--- a/+blockmatrix/isDivisionTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/isDivisionTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -4,14 +4,16 @@
 
 function testIsDivision(testCase)
     cases = {
+        {[1 2] ,false},     % Must be a cell array
+        {{[1 2 3]} ,false}, % Must have two vectors
+        {{[],[]}, true}     % No blocks is a valid blockmatrix
+        {{[1 2],[]} ,true},
+        {{[],[1 2]} ,true},
         {{[2 2 2],[1 2]} ,true},
-        {{[1 2],[1 0]} ,false},
-        {{[0 2],[1 1]} ,false},
-        {{[1 2],[]} ,false},
+        {{[1 2],[1 0]} ,true},
+        {{[0 2],[1 1]} ,true},
         {{[1 2],[1]} ,true},
         {{[1 2],[1], [1 2 3]} ,false},
-        {{[1 2 3]} ,false},
-        {[1 2] ,false},
     };
 
     for i = 1:length(cases)
--- a/+blockmatrix/toMatrixTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/toMatrixTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -51,6 +51,22 @@
             [2 2 1;
              2 1 2],
         },
+        {
+            {zeros(0,0)},
+            [],
+        },
+        {
+            {zeros(3,0), zeros(3,0)},
+            zeros(3,0),
+        },
+        {
+            {zeros(3,0); zeros(2,0)},
+            zeros(5,0),
+        },
+        {
+            {zeros(0,3), zeros(0,2)},
+            zeros(0,5),
+        },
     };
 
     for i = 1:length(cases)
--- a/+blockmatrix/zero.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/zero.m	Mon Sep 11 14:17:15 2017 +0200
@@ -1,6 +1,6 @@
 % Creates a block matrix according to the division with zeros everywhere.
 function bm = zero(div)
-    if ~blockmatrix.isDivision(div);
+    if ~blockmatrix.isDivision(div)
         error('div is not a valid division');
     end
 
--- a/+blockmatrix/zeroTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+blockmatrix/zeroTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -32,6 +32,22 @@
             {[1 2],[2 1]},
             {[0 0],[0];[0 0; 0 0],[0; 0]};
         },
+        {
+            {[3],[0]},
+            {zeros(3,0)},
+        },
+
+        {
+            {[0],[3]},
+            {zeros(0,3)},
+        },
+        {
+            {[0 2],[0 3]},
+            {
+                zeros(0,0), zeros(0,3);
+                zeros(2,0), zeros(2,3);
+            },
+        },
     };
 
     for i = 1:length(cases)
--- a/+grid/Cartesian.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+grid/Cartesian.m	Mon Sep 11 14:17:15 2017 +0200
@@ -15,6 +15,8 @@
             obj.d = length(varargin);
 
             for i = 1:obj.d
+                assert(isvector(varargin{i}), 'Coordinate inputs must be a vectors.')
+
                 obj.x{i} = varargin{i};
                 obj.m(i) = length(varargin{i});
             end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/Empty.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,53 @@
+classdef Empty < grid.Grid & grid.Structured
+    properties
+        dim
+    end
+
+    methods
+        function obj = Empty(D)
+            obj.dim = D;
+        end
+        % n returns the number of points in the grid
+        function o = N(obj)
+            o = 0;
+        end
+
+        % d returns the spatial dimension of the grid
+        function o = D(obj)
+            o = obj.dim;
+        end
+
+        % points returns a n x d matrix containing the coordinates for all points.
+        function X = points(obj)
+            X = sparse(0,obj.dim);
+        end
+
+        % Restricts the grid function gf on obj to the subgrid g.
+        function gf = restrictFunc(obj, gf, g)
+            error('Restrict does not make sense for an empty grid')
+        end
+
+        % Projects the grid function gf on obj to the grid g.
+        function gf = projectFunc(obj, gf, g)
+            error('Project does not make sense for an empty grid')
+        end
+
+        % Return the grid.boundaryIdentifiers of all boundaries in a cell array.
+        function bs = getBoundaryNames(obj)
+            bs = {};
+        end
+
+        % Return coordinates for the given boundary
+        function b = getBoundary(obj, name)
+            b = sparse(0,obj.dim-1);
+        end
+
+        function h = scaling(obj)
+            h = 1;
+        end
+
+        function s = size(obj)
+            s = zeros(1, obj.dim);
+        end
+    end
+end
\ No newline at end of file
--- a/+grid/Grid.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+grid/Grid.m	Mon Sep 11 14:17:15 2017 +0200
@@ -16,7 +16,7 @@
         % Projects the grid function gf on obj to the grid g.
         gf = projectFunc(obj, gf, g)
 
-        % Return the names of all boundaries in this grid.
+        % Return the grid.boundaryIdentifiers of all boundaries in a cell array.
         bs = getBoundaryNames(obj)
 
         % Return coordinates for the given boundary
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/boundaryIdentifier.txt	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,5 @@
+A grid.boundaryIdentifier identifies a boundary of a grid.
+For a Cartesian grid it is simply 's', 'n', 'w', 'e'.
+For a multiblock grid it might be something like {1, 's'}.
+For some other grid it will be up to the developer to chose
+a good way to identify boundaries.
--- a/+grid/evalOn.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+grid/evalOn.m	Mon Sep 11 14:17:15 2017 +0200
@@ -27,7 +27,11 @@
     x = num2cell(g.points());
 
     % Find the number of components
-    x0 = x(1,:);
+    if size(x,1) ~= 0
+        x0 = x(1,:);
+    else
+        x0 = num2cell(ones(1,size(x,2)));
+    end
     f0 = func(x0{:});
     k = length(f0);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+grid/gridFunction.txt	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,1 @@
+TODO: Add documentation for gridfunctions here
\ No newline at end of file
--- a/+multiblock/BoundaryGroup.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+multiblock/BoundaryGroup.m	Mon Sep 11 14:17:15 2017 +0200
@@ -1,30 +1,10 @@
 % BoundaryGroup defines a boundary grouping in a multiblock grid.
-classdef BoundaryGroup
-    properties
-        blockIDs
-        names
-    end
-
+% It workds like a cell array and collects boundary identifiers
+% Within the multiblock package a BoundaryGroup is a valid boundary identifier as well.
+classdef BoundaryGroup < Cell
     methods
-        function obj = BoundaryGroup(varargin)
-            % Input arguemnts are arbitrary number or 1x2 cell arrays
-            % representing each boundary in the group.
-            % The 1st element of the cell array is an integer defining which grid it belongs to.
-            % The 2nd element of the cell array is the name of the boundary within the block.
-            %
-            % Ex:
-            %   bg = multiblock.BoundaryGroup({1,'n'},{1,'s'},{2,'s'})
-
-
-            obj.blockIDs = [];
-            obj.names = {};
-            for i = 1:length(varargin)
-                if ~iscell(varargin{i}) || ~all(size(varargin{i}) == [1 2])
-                    error('multiblock:BoundaryGroup:BoundaryGroup:InvalidInput', 'Inputs must be 1x2 cell arrays');
-                end
-                obj.blockIDs(i) = varargin{i}{1};
-                obj.names{i} = varargin{i}{2};
-            end
+        function obj = BoundaryGroup(data)
+            obj = obj@Cell(data);
         end
 
         function display(obj, name)
@@ -33,19 +13,7 @@
             disp([name, ' ='])
             disp(' ')
 
-            if length(obj.names) == 1
-                fprintf('    {}\n\n')
-                return
-            end
-
-            fprintf('    {')
-
-            fprintf('%d:%s', obj.blockIDs(1), obj.names{1})
-            for i = 2:length(obj.names)
-                fprintf(', %d:%s', obj.blockIDs(i), obj.names{i});
-            end
-
-            fprintf('}\n\n')
+            fprintf('    BoundaryGroup%s\n\n', toString(obj.data));
         end
     end
-end
\ No newline at end of file
+end
--- a/+multiblock/BoundaryGroupTest.m	Mon Sep 11 14:12:54 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-function tests = BoundaryGroupTest()
-    tests = functiontests(localfunctions);
-end
-
-function testCreation(testCase)
-    in = {{3,'n'},{2,'hoho'},{1,'s'}};
-
-    blockIDs = [3 2 1];
-    names = {'n', 'hoho', 's'};
-
-    bg = multiblock.BoundaryGroup(in{:});
-    testCase.verifyEqual(bg.blockIDs, blockIDs);
-    testCase.verifyEqual(bg.names, names);
-end
-
-function testInputError(testCase)
-    in = {
-        {'n', 's'},
-        {{3,'n'},{2,2,'hoho'},{1,'s'}},
-    };
-
-    out = {
-        'multiblock:BoundaryGroup:BoundaryGroup:InvalidInput',
-        'multiblock:BoundaryGroup:BoundaryGroup:InvalidInput',
-    };
-
-    for i = 1:length(in)
-        testCase.verifyError(@()multiblock.BoundaryGroup(in{i}{:}), out{i});
-    end
-end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/Contour.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,65 @@
+classdef Contour < handle
+    properties
+        grid
+        contours
+        nContours
+
+        ZData
+        CData
+
+    end
+
+    methods
+        function obj = Contour(g, gf, nContours)
+            obj.grid = g;
+            obj.nContours = nContours;
+
+            coords = obj.grid.points();
+            X = obj.grid.funcToPlotMatrices(coords(:,1));
+            Y = obj.grid.funcToPlotMatrices(coords(:,2));
+
+            V = obj.grid.funcToPlotMatrices(gf);
+
+
+            holdState = ishold();
+            hold on
+
+            contours = {1, obj.grid.nBlocks};
+            for i = 1:obj.grid.nBlocks
+                [~, contours{i}] = contour(X{i}, Y{i}, V{i},obj.nContours);
+                contours{i}.LevelList = contours{1}.LevelList;
+            end
+
+            if holdState == false
+                hold off
+            end
+
+            obj.contours = [contours{:}];
+
+            obj.ZData = gf;
+            obj.CData = gf;
+        end
+
+        function set(obj, propertyName, propertyValue)
+            set(obj.contours, propertyName, propertyValue);
+        end
+
+        function obj = set.ZData(obj, gf)
+            obj.ZData = gf;
+
+            V = obj.grid.funcToPlotMatrices(gf);
+            for i = 1:obj.grid.nBlocks
+                obj.contours(i).ZData = V{i};
+            end
+        end
+
+        function obj = set.CData(obj, gf)
+            obj.CData = gf;
+
+            V = obj.grid.funcToPlotMatrices(gf);
+            for i = 1:obj.grid.nBlocks
+                obj.contours(i).CData = V{i};
+            end
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/Def.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,101 @@
+classdef Def
+    properties
+        nBlocks
+        blockMaps % Maps from logical blocks to physical blocks build from transfinite interpolation
+        blockNames
+        connections % Cell array specifying connections between blocks
+        boundaryGroups % Structure of boundaryGroups
+    end
+
+    methods
+        % Defines a multiblock setup for transfinite interpolation blocks
+        % TODO: How to bring in plotting of points?
+        function obj = Def(blockMaps, connections, boundaryGroups, blockNames)
+            default_arg('boundaryGroups', struct());
+            default_arg('blockNames',{});
+
+            nBlocks = length(blockMaps);
+
+            obj.nBlocks = nBlocks;
+
+            obj.blockMaps = blockMaps;
+
+            assert(all(size(connections) == [nBlocks, nBlocks]));
+            obj.connections = connections;
+
+
+            if isempty(blockNames)
+                obj.blockNames = cell(1, nBlocks);
+                for i = 1:length(blockMaps)
+                    obj.blockNames{i} = sprintf('%d', i);
+                end
+            else
+                assert(length(blockNames) == nBlocks);
+                obj.blockNames = blockNames;
+            end
+
+            obj.boundaryGroups = boundaryGroups;
+        end
+
+        function g = getGrid(obj, varargin)
+            ms = obj.getGridSizes(varargin{:});
+
+            grids = cell(1, obj.nBlocks);
+            for i = 1:obj.nBlocks
+                grids{i} = grid.equidistantCurvilinear(obj.blockMaps{i}.S, ms{i});
+            end
+
+            g = multiblock.Grid(grids, obj.connections, obj.boundaryGroups);
+        end
+
+        function show(obj, label, gridLines, varargin)
+            default_arg('label', 'name')
+            default_arg('gridLines', false);
+
+            if isempty('label') && ~gridLines
+                for i = 1:obj.nBlocks
+                    obj.blockMaps{i}.show(2,2);
+                end
+                axis equal
+                return
+            end
+
+            if gridLines
+                ms = obj.getGridSizes(varargin{:});
+                for i = 1:obj.nBlocks
+                    obj.blockMaps{i}.show(ms{i}(1),ms{i}(2));
+                end
+            end
+
+
+            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
+
+            for i = 1:obj.nBlocks
+                parametrization.Ti.label(obj.blockMaps{i}, labels{i});
+            end
+
+            axis equal
+        end
+    end
+
+    methods (Abstract)
+        % Returns the grid size of each block in a cell array
+        % The input parameters are determined by the subclass
+        ms = getGridSizes(obj, varargin)
+        % end
+    end
+
+end
+
+
--- a/+multiblock/DiffOp.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+multiblock/DiffOp.m	Mon Sep 11 14:17:15 2017 +0200
@@ -120,27 +120,54 @@
             ops = sparse2cell(op, obj.NNN);
         end
 
-        function op = getBoundaryOperator(obj, op, boundary)
-            if iscell(boundary)
-                localOpName = [op '_' boundary{2}];
-                blockId = boundary{1};
-                localOp = obj.diffOps{blockId}.(localOpName);
+        % Get a boundary operator specified by opName for the given boundary/BoundaryGroup
+        function op = getBoundaryOperator(obj, opName, boundary)
+            switch class(boundary)
+                case 'cell'
+                    localOpName = [opName '_' boundary{2}];
+                    blockId = boundary{1};
+                    localOp = obj.diffOps{blockId}.(localOpName);
 
-                div = {obj.blockmatrixDiv{1}, size(localOp,2)};
-                blockOp = blockmatrix.zero(div);
-                blockOp{blockId,1} = localOp;
-                op = blockmatrix.toMatrix(blockOp);
-                return
-            else
-                % Boundary är en sträng med en boundary group i.
+                    div = {obj.blockmatrixDiv{1}, size(localOp,2)};
+                    blockOp = blockmatrix.zero(div);
+                    blockOp{blockId,1} = localOp;
+                    op = blockmatrix.toMatrix(blockOp);
+                    return
+                case 'multiblock.BoundaryGroup'
+                    op = sparse(size(obj.D,1),0);
+                    for i = 1:length(boundary)
+                        op = [op, obj.getBoundaryOperator(opName, boundary{i})];
+                    end
+                otherwise
+                    error('Unknown boundary indentifier')
             end
         end
 
         % Creates the closure and penalty matrix for a given boundary condition,
         %    boundary -- the name of the boundary on the form {id,name} where
         %                id is the number of a block and name is the name of a
-        %                boundary of that block example: {1,'s'} or {3,'w'}
+        %                boundary of that block example: {1,'s'} or {3,'w'}. It
+        %                can also be a boundary group
         function [closure, penalty] = boundary_condition(obj, boundary, type)
+            switch class(boundary)
+                case 'cell'
+                    [closure, penalty] = obj.singleBoundaryCondition(boundary, type);
+                case 'multiblock.BoundaryGroup'
+                    [n,m] = size(obj.D);
+                    closure = sparse(n,m);
+                    penalty = sparse(n,0);
+                    for i = 1:length(boundary)
+                        [closurePart, penaltyPart] = obj.boundary_condition(boundary{i}, type);
+                        closure = closure + closurePart;
+                        penalty = [penalty, penaltyPart];
+                    end
+                otherwise
+                    error('Unknown boundary indentifier')
+            end
+
+        end
+
+        function [closure, penalty] = singleBoundaryCondition(obj, boundary, type)
             I = boundary{1};
             name = boundary{2};
 
@@ -177,7 +204,7 @@
         end
 
         function [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary)
-
+            error('not implemented')
         end
 
         % Size returns the number of degrees of freedom
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/EmptyGrid.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,47 @@
+classdef EmptyGrid < grid.Empty & multiblock.Grid
+    methods
+        function obj = EmptyGrid(D)
+            obj@multiblock.Grid({},cell(0,0));
+            obj@grid.Empty(D);
+        end
+
+        % n returns the number of points in the grid
+        function o = N(obj)
+            o = N@grid.Empty(obj);
+        end
+
+        % d returns the spatial dimension of the grid
+        function o = D(obj)
+            o = D@grid.Empty(obj);
+        end
+
+        % points returns a n x d matrix containing the coordinates for all points.
+        function X = points(obj)
+            X = points@grid.Empty(obj);
+        end
+
+        % Restricts the grid function gf on obj to the subgrid g.
+        function gf = restrictFunc(obj, gf, g)
+            gf = restrictFunc@grid.Empty(obj);
+        end
+
+        % Projects the grid function gf on obj to the grid g.
+        function gf = projectFunc(obj, gf, g)
+            gf = projectFunc@grid.Empty(obj);
+        end
+
+        % Return the grid.boundaryIdentifiers of all boundaries in a cell array.
+        function bs = getBoundaryNames(obj)
+            bs = getBoundaryNames@grid.Empty(obj);
+        end
+
+        % Return coordinates for the given boundary
+        function b = getBoundary(obj, name)
+            b = getBoundary@grid.Empty(name);
+        end
+
+        function s = size(obj)
+            s = size@multiblock.Grid(obj);
+        end
+    end
+end
--- a/+multiblock/Grid.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+multiblock/Grid.m	Mon Sep 11 14:17:15 2017 +0200
@@ -9,16 +9,20 @@
 
     % General multiblock grid
     methods
-
-        % grids -- cell array of N grids
-        % connections -- NxN upper triangular cell matrix. connections{i,j}
-        %                specifies the connection between block i and j. If
-        %                it's empty there is no connection otherwise it's a 2
-        %                -cell-vector with strings naming the boundaries to be
-        %                connected. (inverted coupling?)
-        %% Should we have boundary groups at all? maybe it can be handled in a
-        %% cleaner way outside of the class. Maybe the grid class doesn't need to care at all. All boundaryGroup interaction is in DiffOp?
+        % grids          -- cell array of N grids
+        % connections    -- NxN upper triangular cell matrix. connections{i,j}
+        %                   specifies the connection between block i and j. If
+        %                   it's empty there is no connection otherwise it's a 2
+        %                   -cell-vector with strings naming the boundaries to be
+        %                   connected. (inverted coupling?)
+        % boundaryGroups -- A struct of BoundaryGroups. The field names of the
+        %                   struct are the names of each boundary group.
+        %                   The boundary groups can be used to collect block
+        %                   boundaries into physical boundaries to simplify
+        %                   getting boundary operators and setting boundary conditions
         function obj = Grid(grids, connections, boundaryGroups)
+            default_arg('boundaryGroups', struct());
+            assertType(grids, 'cell')
             obj.grids = grids;
             obj.connections = connections;
 
@@ -27,7 +31,7 @@
                 obj.nPoints = obj.nPoints + grids{i}.N();
             end
 
-            % if iscell(boundaryGroups)
+            obj.boundaryGroups = boundaryGroups;
         end
 
         function n = size(obj)
@@ -42,7 +46,7 @@
         % Ns returns the number of points in each sub grid as a vector
         function o = Ns(obj)
             ns = zeros(1,obj.nBlocks);
-            for i = 1:obj.nBlocks;
+            for i = 1:obj.nBlocks
                 ns(i) = obj.grids{i}.N();
             end
             o = ns;
@@ -59,7 +63,7 @@
 
         % points returns a n x d matrix containing the coordinates for all points.
         function X = points(obj)
-            X = [];
+            X = sparse(0,obj.D());
             for i = 1:length(obj.grids)
                 X = [X; obj.grids{i}.points];
             end
@@ -76,12 +80,19 @@
                 N(i) = obj.grids{i}.N();
             end
 
-            gfs = mat2cell(gf, N, 1);
+            gfs = blockmatrix.fromMatrix(gf, {N,1});
         end
 
+        % TODO: Split op?
+        % Should the method to split an operator be moved here instead of being in multiblock.DiffOp?
+
         % Converts a gridfunction to a set of plot matrices
         % Takes a grid function and and a structured grid.
         function F = funcToPlotMatrices(obj, gf)
+            % TODO: This method should problably not be here.
+            % The funcToPlotMatrix uses .size poperty of the grids
+            % Which doesn't always exist for all types of grids.
+            % It's only valid for structured grids
             gfs = obj.splitFunc(gf);
 
             F = cell(1, obj.nBlocks());
@@ -121,13 +132,43 @@
 
         end
 
+        % Find all non interface boundaries of all blocks.
+        % Return their grid.boundaryIdentifiers in a cell array.
         function bs = getBoundaryNames(obj)
-            bs = [];
+            bs = {};
+            for i = 1:obj.nBlocks()
+                candidates = obj.grids{i}.getBoundaryNames();
+                for j = 1:obj.nBlocks()
+                    if ~isempty(obj.connections{i,j})
+                        candidates = setdiff(candidates, obj.connections{i,j}{1});
+                    end
+
+                    if ~isempty(obj.connections{j,i})
+                        candidates = setdiff(candidates, obj.connections{j,i}{2});
+                    end
+                end
+
+                for k = 1:length(candidates)
+                    bs{end+1} = {i, candidates{k}};
+                end
+            end
         end
 
-        % Return coordinates for the given boundary
-        function b = getBoundary(obj, name)
-            b = [];
+        % Return coordinates for the given boundary/boundaryGroup
+        function b = getBoundary(obj, boundary)
+            switch class(boundary)
+                case 'cell'
+                    I = boundary{1};
+                    name = boundary{2};
+                    b = obj.grids{I}.getBoundary(name);
+                case 'multiblock.BoundaryGroup'
+                    b = sparse(0,obj.D());
+                    for i = 1:length(boundary)
+                        b = [b; obj.getBoundary(boundary{i})];
+                    end
+                otherwise
+                    error('Unknown boundary indentifier')
+            end
         end
     end
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/Line.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,47 @@
+classdef Line < handle
+    properties
+        grid
+        lines
+
+        YData
+    end
+
+    methods
+        function obj = Line(g, gf)
+            assertType(g, 'multiblock.Grid')
+            obj.grid = g;
+
+            X = obj.grid.splitFunc(obj.grid.points());
+            Y = obj.grid.splitFunc(gf);
+
+            holdState = ishold();
+            hold on
+
+            lines = cell(1, obj.grid.nBlocks);
+            for i = 1:obj.grid.nBlocks
+                lines{i} = plot(X{i}, Y{i});
+            end
+
+            if holdState == false
+                hold off
+            end
+
+            obj.lines = [lines{:}];
+
+            obj.YData = gf;
+        end
+
+        function set(obj, propertyName, propertyValue)
+            set(obj.lines, propertyName, propertyValue);
+        end
+
+        function obj = set.YData(obj, gf)
+            obj.YData = gf;
+
+            Y = obj.grid.funcToPlotMatrices(gf);
+            for i = 1:obj.grid.nBlocks
+                obj.lines(i).YData = Y{i};
+            end
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/+multiblock/Surface.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,62 @@
+classdef Surface < handle
+    properties
+        grid
+        surfs
+
+        ZData
+        CData
+
+    end
+
+    methods
+        function obj = Surface(g, gf)
+            obj.grid = g;
+
+            coords = obj.grid.points();
+            X = obj.grid.funcToPlotMatrices(coords(:,1));
+            Y = obj.grid.funcToPlotMatrices(coords(:,2));
+
+            V = obj.grid.funcToPlotMatrices(gf);
+
+
+            holdState = ishold();
+            hold on
+
+            surfs = cell(1, obj.grid.nBlocks);
+            for i = 1:obj.grid.nBlocks
+                surfs{i} = surf(X{i}, Y{i}, V{i});
+            end
+
+            if holdState == false
+                hold off
+            end
+
+            obj.surfs = [surfs{:}];
+
+            obj.ZData = gf;
+            obj.CData = gf;
+        end
+
+        function set(obj, propertyName, propertyValue)
+            set(obj.surfs, propertyName, propertyValue);
+        end
+
+        function obj = set.ZData(obj, gf)
+            obj.ZData = gf;
+
+            V = obj.grid.funcToPlotMatrices(gf);
+            for i = 1:obj.grid.nBlocks
+                obj.surfs(i).ZData = V{i};
+            end
+        end
+
+        function obj = set.CData(obj, gf)
+            obj.CData = gf;
+
+            V = obj.grid.funcToPlotMatrices(gf);
+            for i = 1:obj.grid.nBlocks
+                obj.surfs(i).CData = V{i};
+            end
+        end
+    end
+end
--- a/+parametrization/Ti.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+parametrization/Ti.m	Mon Sep 11 14:17:15 2017 +0200
@@ -182,6 +182,15 @@
             obj = parametrization.Ti(g1,g2,g3,g4);
         end
 
+        function obj = rectangle(a, b)
+            p1 = a;
+            p2 = [b(1), a(2)];
+            p3 = b;
+            p4 = [a(1), b(2)];
+
+            obj = parametrization.Ti.points(p1,p2,p3,p4);
+        end
+
         % Like the constructor but allows inputing line curves as 2-cell arrays:
         %     example: parametrization.Ti.linesAndCurves(g1, g2, {a, b} g4)
         function obj = linesAndCurves(C1, C2, C3, C4)
--- a/+scheme/Beam.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+scheme/Beam.m	Mon Sep 11 14:17:15 2017 +0200
@@ -90,6 +90,9 @@
             gamm = obj.gamm;
             delt = obj.delt;
 
+
+            % TODO: Can this be simplifed? Can I handle conditions on u on its own, u_x on its own ...
+
             switch type
                 case {'dn', 'clamped'} % Dirichlet-neumann boundary condition
                     alpha = obj.alpha;
--- a/+scheme/LaplaceCurvilinear.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+scheme/LaplaceCurvilinear.m	Mon Sep 11 14:17:15 2017 +0200
@@ -7,42 +7,48 @@
 
         order % Order accuracy for the approximation
 
-        D % non-stabalized scheme operator
-        M % Derivative norm
-        a,b
+        a,b % Parameters of the operator
+
+
+        % Inner products and operators for physical coordinates
+        D % Laplace operator
+        H, Hi % Inner product
+        e_w, e_e, e_s, e_n
+        d_w, d_e, d_s, d_n % Normal derivatives at the boundary
+        H_w, H_e, H_s, H_n % Boundary inner products
+        Dx, Dy % Physical derivatives
+        M % Gradient inner product
+
+        % Metric coefficients
         J, Ji
         a11, a12, a22
+        x_u
+        x_v
+        y_u
+        y_v
 
-        H % Discrete norm
-        Hi
+        % Inner product and operators for logical coordinates
         H_u, H_v % Norms in the x and y directions
+        Hi_u, Hi_v
         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
         % Implements  a*div(b*grad(u)) as a SBP scheme
+        % TODO: Implement proper H, it should be the real physical quadrature, the logic quadrature may be but in a separate variable (H_logic?)
+
         function obj = LaplaceCurvilinear(g ,order, a, b, opSet)
             default_arg('opSet',@sbp.D2Variable);
             default_arg('a', 1);
             default_arg('b', 1);
 
-
             if b ~=1
                 error('Not implemented yet')
             end
@@ -58,7 +64,8 @@
             h_u = h(1);
             h_v = h(2);
 
-            % Operators
+
+            % 1D operators
             ops_u = opSet(m_u, {0, 1}, order);
             ops_v = opSet(m_v, {0, 1}, order);
 
@@ -83,10 +90,30 @@
             d1_l_v = ops_v.d1_l;
             d1_r_v = ops_v.d1_r;
 
+
+            % Logical operators
             Du = kr(D1_u,I_v);
             Dv = kr(I_u,D1_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);
 
-            % Metric derivatives
+            e_w  = kr(e_l_u,I_v);
+            e_e  = kr(e_r_u,I_v);
+            e_s  = kr(I_u,e_l_v);
+            e_n  = kr(I_u,e_r_v);
+            obj.du_w = kr(d1_l_u,I_v);
+            obj.dv_w = (e_w'*Dv)';
+            obj.du_e = kr(d1_r_u,I_v);
+            obj.dv_e = (e_e'*Dv)';
+            obj.du_s = (e_s'*Du)';
+            obj.dv_s = kr(I_u,d1_l_v);
+            obj.du_n = (e_n'*Du)';
+            obj.dv_n = kr(I_u,d1_r_v);
+
+
+            % Metric coefficients
             coords = g.points();
             x = coords(:,1);
             y = coords(:,2);
@@ -102,8 +129,14 @@
             a22 =  1./J .* (x_u.^2  + y_u.^2);
             lambda = 1/2 * (a11 + a22 - sqrt((a11-a22).^2 + 4*a12.^2));
 
+            obj.x_u = x_u;
+            obj.x_v = x_v;
+            obj.y_u = y_u;
+            obj.y_v = y_v;
+
+
             % Assemble full operators
-            L_12 = spdiags(a12, 0, m_tot, m_tot);
+            L_12 = spdiag(a12);
             Duv = Du*L_12*Dv;
             Dvu = Dv*L_12*Du;
 
@@ -123,31 +156,55 @@
                 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);
+
+            % Physical operators
+            obj.J = spdiag(J);
+            obj.Ji = spdiag(1./J);
+
+            obj.D = obj.Ji*a*(Duu + Duv + Dvu + Dvv);
+            obj.H = obj.J*kr(H_u,H_v);
+            obj.Hi = obj.Ji*kr(Hi_u,Hi_v);
+
+            obj.e_w = e_w;
+            obj.e_e = e_e;
+            obj.e_s = e_s;
+            obj.e_n = e_n;
+
+            %% normal derivatives
+            I_w = ind(1,:);
+            I_e = ind(end,:);
+            I_s = ind(:,1);
+            I_n = ind(:,end);
 
-            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);
+            a11_w = spdiag(a11(I_w));
+            a12_w = spdiag(a12(I_w));
+            a11_e = spdiag(a11(I_e));
+            a12_e = spdiag(a12(I_e));
+            a22_s = spdiag(a22(I_s));
+            a12_s = spdiag(a12(I_s));
+            a22_n = spdiag(a22(I_n));
+            a12_n = spdiag(a12(I_n));
+
+            s_w = sqrt((e_w'*x_v).^2 + (e_w'*y_v).^2);
+            s_e = sqrt((e_e'*x_v).^2 + (e_e'*y_v).^2);
+            s_s = sqrt((e_s'*x_u).^2 + (e_s'*y_u).^2);
+            s_n = sqrt((e_n'*x_u).^2 + (e_n'*y_u).^2);
 
-            obj.x_u = x_u;
-            obj.x_v = x_v;
-            obj.y_u = y_u;
-            obj.y_v = y_v;
+            obj.d_w = -1*(spdiag(1./s_w)*(a11_w*obj.du_w' + a12_w*obj.dv_w'))';
+            obj.d_e =    (spdiag(1./s_e)*(a11_e*obj.du_e' + a12_e*obj.dv_e'))';
+            obj.d_s = -1*(spdiag(1./s_s)*(a22_s*obj.dv_s' + a12_s*obj.du_s'))';
+            obj.d_n =    (spdiag(1./s_n)*(a22_n*obj.dv_n' + a12_n*obj.du_n'))';
+
+            obj.Dx = spdiag( y_v./J)*Du + spdiag(-y_u./J)*Dv;
+            obj.Dy = spdiag(-x_v./J)*Du + spdiag( x_u./J)*Dv;
 
+            %% Boundary inner products
+            obj.H_w = H_v*spdiag(s_w);
+            obj.H_e = H_v*spdiag(s_e);
+            obj.H_s = H_u*spdiag(s_s);
+            obj.H_n = H_u*spdiag(s_n);
+
+            % Misc.
             obj.m = m;
             obj.h = [h_u h_v];
             obj.order = order;
@@ -155,17 +212,11 @@
 
             obj.a = a;
             obj.b = b;
-            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*a*(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
@@ -182,62 +233,34 @@
             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);
+            [e, d, gamm, H_b, ~] = 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*obj.lambda./obj.a11.^2;
+                    b2 = gamm*obj.lambda./obj.a22.^2;
 
-                    b1 = gamm*u.lambda./u.a11.^2;
-                    b2 = gamm*u.lambda./u.a22.^2;
+                    tau1 = tuning * spdiag(-1./b1 - 1./b2);
+                    tau2 = 1;
 
-                    tau  = -1./b1 - 1./b2;
-                    tau = tuning * spdiag(tau);
-                    sig1 = 1;
+                    tau = (tau1*e + tau2*d)*H_b;
 
-                    penalty_parameter_1 = halfnorm_inv_n*(tau + sig1*halfnorm_inv_t*F*e'*halfnorm_t)*e;
-
-                    closure = obj.Ji*obj.a * penalty_parameter_1*e';
-                    penalty = -obj.Ji*obj.a * penalty_parameter_1;
+                    closure =  obj.a*obj.Hi*tau*e';
+                    penalty = -obj.a*obj.Hi*tau;
 
 
                 % Neumann boundary condition
                 case {'N','n','neumann'}
-                    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;
+                    tau1 = -1;
                     tau2 = 0;
-                    tau = obj.a * obj.Ji*(tau1*e + tau2*d);
-
-                    closure = halfnorm_inv*tau*d';
-                    penalty = -halfnorm_inv*tau;
+                    tau = (tau1*e + tau2*d)*H_b;
 
-                % Characteristic boundary condition
-                case {'characteristic', 'char', 'c'}
-                    default_arg('parameter', 1);
-                    beta = parameter;
+                    closure =  obj.a*obj.Hi*tau*d';
+                    penalty = -obj.a*obj.Hi*tau;
 
-                    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 = -obj.a * 1/beta*obj.Ji*e;
-
-                    closure{1} = halfnorm_inv*tau*spdiag(scale_factor)*e';
-                    closure{2} = halfnorm_inv*tau*beta*d';
-                    penalty = -halfnorm_inv*tau;
 
                 % Unknown, boundary condition
                 otherwise
@@ -250,16 +273,8 @@
             % 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')';
+            [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);
 
             u = obj;
             v = neighbour_scheme;
@@ -269,24 +284,25 @@
             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;
+            tau1 = -1./(4*b1_u) -1./(4*b1_v) -1./(4*b2_u) -1./(4*b2_v);
+            tau1 = tuning * spdiag(tau1);
+            tau2 = 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;
+            sig1 = -1/2;
+            sig2 = 0;
 
+            tau = (e_u*tau1 + tau2*d_u)*H_b_u;
+            sig = (sig1*e_u + sig2*d_u)*H_b_u;
 
-            closure = obj.Ji*obj.a * ( penalty_parameter_1*e_u' + penalty_parameter_2*F_u');
-            penalty = obj.Ji*obj.a * (-penalty_parameter_1*e_v' + penalty_parameter_2*F_v');
+            closure = obj.a*obj.Hi*( tau*e_u' + sig*d_u');
+            penalty = obj.a*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
         %
         %  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)
+        function [e, d, gamm, H_b, I] = get_boundary_ops(obj, boundary)
 
             % gridMatrix = zeros(obj.m(2),obj.m(1));
             % gridMatrix(:) = 1:numel(gridMatrix);
@@ -296,58 +312,32 @@
             switch boundary
                 case 'w'
                     e = obj.e_w;
-                    d_n = obj.du_w;
-                    d_t = obj.dv_w;
-                    s = -1;
-
+                    d = obj.d_w;
+                    H_b = obj.H_w;
                     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;
-
+                    d = obj.d_e;
+                    H_b = obj.H_e;
                     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;
-
+                    d = obj.d_s;
+                    H_b = obj.H_s;
                     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;
-
+                    d = obj.d_n;
+                    H_b = obj.H_n;
                     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
@@ -355,7 +345,5 @@
         function N = size(obj)
             N = prod(obj.m);
         end
-
-
     end
-end
\ No newline at end of file
+end
--- a/+scheme/Scheme.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+scheme/Scheme.m	Mon Sep 11 14:17:15 2017 +0200
@@ -25,9 +25,12 @@
         %       neighbour_boundary  is a string specifying which boundary to
         %                           interface to.
         %       penalty  may be a cell array if there are several penalties with different weights
-        [closure, penalty] = boundary_condition(obj,boundary,type)
+        [closure, penalty] = boundary_condition(obj,boundary,type) % TODO: Change name to boundaryCondition
         [closure, penalty] = interface(obj,boundary,neighbour_scheme,neighbour_boundary)
 
+        % TODO: op = getBoundaryOperator()??
+        %   makes sense to have it available through a method instead of random properties
+
         % Returns the number of degrees of freedom.
         N = size(obj)
     end
--- a/+time/Rungekutta4SecondOrder.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/+time/Rungekutta4SecondOrder.m	Mon Sep 11 14:17:15 2017 +0200
@@ -15,7 +15,19 @@
 
 
     methods
-        % Solves u_tt = Du + Eu_t + S
+        % Solves u_tt = Du + Eu_t + S by
+        % Rewriting on first order form:
+        %   w_t = M*w + C(t)
+        % where
+        %   M = [
+        %      0, I;
+        %      D, E;
+        %   ]
+        % and
+        %   C(t) = [
+        %      0;
+        %      S(t)
+        %   ]
         % 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.
         function obj = Rungekutta4SecondOrder(D, E, S, k, t0, v0, v0t)
@@ -41,23 +53,15 @@
                     S = @(t)S;
                 end
 
-                I = speye(obj.m);
-                O = sparse(obj.m,obj.m);
-
-                obj.M = @(t)[
-                       O,    I;
-                    D(t), E(t);
-                ];
-                obj.C = @(t)[
-                    zeros(obj.m,1);
-                              S(t);
-                ];
-
                 obj.k = k;
                 obj.t = t0;
                 obj.w = [v0; v0t];
 
-                obj.F = @(w,t)(obj.M(t)*w + obj.C(t));
+                % Avoid matrix formulation because it is VERY slow
+                obj.F = @(w,t)[
+                    w(obj.m+1:end);
+                    D(t)*w(1:obj.m) + E(t)*w(obj.m+1:end) + S(t);
+                ];
             else
 
                 default_arg('D', sparse(obj.m, obj.m));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Cell.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,92 @@
+% Cell is a reimplementation of matlabs cell array with the benefit that it is subclassable
+% It might be used for giving a typename to a cellarray to increase readability of the code.
+classdef Cell
+    properties
+        data
+    end
+
+    methods
+        function obj = Cell(data)
+            default_arg('data', {});
+            if ~iscell(data)
+                error('Input argument to Cell must be a cell array');
+            end
+
+            obj.data = data;
+        end
+
+        function str = toString(obj)
+            str = sprintf('%s%s', class(obj), toString(obj.data));
+        end
+
+        function s = size(A)
+            s = size(A.data);
+        end
+
+        function b = isempty(A)
+            b = prod(size(A)) == 0;
+        end
+
+        function l = length(A)
+            l = length(A.data);
+        end
+
+        function ind = end(A,k,n)
+            ind = builtin('end',A.data, k, n);
+        end
+
+        function B = transpose(A)
+            b = A.data.';
+            B = callConstructor(A, b);
+        end
+
+        function B = ctranspose(A)
+            b = A.data';
+            B = callConstructor(A, b);
+        end
+
+        function A = subsasgn(A, S, B)
+            a = subsasgn(A.data, S, B);
+            A = callConstructor(A, a);
+        end
+
+        function B = subsref(A, S)
+            switch S(1).type
+                case '()'
+                    b = subsref(A.data, S(1));
+                    B = callConstructor(A, b);
+                    if length(S) > 1
+                        B = subsref(B,S(2:end));
+                    end
+                case '{}'
+                    B = subsref(A.data, S);
+                case '.'
+                    B = builtin('subsref',A, S);
+                otherwise
+                    error('unreachable');
+            end
+        end
+
+        function C = horzcat(varargin)
+            dataArray = cell(1, length(varargin));
+
+            for i = 1:length(varargin)
+                dataArray{i} = varargin{i}.data;
+            end
+
+            c = horzcat(dataArray{:});
+            C = callConstructor(varargin{1}, c);
+        end
+
+        function C = vertcat(varargin)
+            dataArray = cell(1, length(varargin));
+
+            for i = 1:length(varargin)
+                dataArray{i} = varargin{i}.data;
+            end
+
+            c = vertcat(dataArray{:});
+            C = callConstructor(varargin{1}, c);
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CellTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,279 @@
+function tests = CellTest()
+    tests = functiontests(localfunctions);
+end
+
+function testSize(testCase)
+    cases = {
+        {{}, [0, 0]},
+        {{1}, [1, 1]},
+        {{1, 2}, [1, 2]},
+        {{1; 2}, [2, 1]},
+        {{1, 2; 3, 4}, [2,2]},
+    };
+
+    for i = 1:length(cases)
+        A = Cell(cases{i}{1});
+        expected = cases{i}{2};
+
+        testCase.verifyEqual(size(A),expected);
+    end
+end
+
+function testLength(testCase)
+    cases = {
+        {{}, 0},
+        {{1}, 1},
+        {{1, 2}, 2},
+        {{1; 2}, 2},
+        {{1, 2; 3, 4}, 2},
+    };
+
+    for i = 1:length(cases)
+        A = Cell(cases{i}{1});
+        expected = cases{i}{2};
+
+        testCase.verifyEqual(length(A),expected);
+    end
+end
+
+function testIsEmpty(testCase)
+    cases = {
+        {cell(0,0), true},
+        {cell(1,0), true},
+        {cell(0,1), true},
+        {cell(1,1), false},
+    };
+
+    for i = 1:length(cases)
+        A = Cell(cases{i}{1});
+        expected = cases{i}{2};
+        testCase.verifyEqual(isempty(A),expected);
+    end
+end
+
+function testTranspose(testCase)
+    testCase.verifyEqual(Cell({1i, 2}).', Cell({1i; 2}));
+    testCase.verifyEqual(Cell({1i; 2}).', Cell({1i, 2}));
+end
+
+function testCtranspose(testCase)
+    testCase.verifyEqual(Cell({1i, 2})', Cell({1i; 2}));
+    testCase.verifyEqual(Cell({1i; 2})', Cell({1i, 2}));
+end
+
+function testRoundIndexWithProperty(testCase)
+    A = Cell({3,2,1});
+
+    result = A([1,3]).data;
+    testCase.verifyEqual(result, {3, 1});
+end
+
+function testSubAssignmentRound(testCase)
+    cases = {
+        % {
+        %     lArray,
+        %     index,
+        %     rhs,
+        %     expectedResult
+        % },
+        {
+            {},
+            1,
+            {'a'},
+            {'a'},
+        },
+        {
+            {1},
+            1,
+            {'a'},
+            {'a'},
+        },
+        {
+            {1,2,3},
+            2,
+            {'a'},
+            {1,'a',3},
+        },
+        {
+            {1,2,3},
+            2,
+            [],
+            {1,3},
+        },
+    };
+
+    for i = 1:length(cases)
+        lArray         = Cell(cases{i}{1});
+        index          = cases{i}{2};
+        rhs            = cases{i}{3};
+        expectedResult = cases{i}{4};
+
+        lArray(index) = rhs;
+
+        testCase.verifyEqual(lArray.data, expectedResult)
+    end
+end
+
+function testSubAssignmentCurly(testCase)
+    cases = {
+        % {
+        %     lArray,
+        %     index,
+        %     rhs,
+        %     expectedResult
+        % },
+        {
+            {},
+            1,
+            'a',
+            {'a'},
+        },
+        {
+            {1},
+            1,
+            'a',
+            {'a'},
+        },
+        {
+            {1,2,3},
+            2,
+            'a',
+            {1,'a',3},
+        },
+    };
+
+    for i = 1:length(cases)
+        lArray         = Cell(cases{i}{1});
+        index          = cases{i}{2};
+        rhs            = cases{i}{3};
+        expectedResult = cases{i}{4};
+
+        lArray{index} = rhs;
+
+        testCase.verifyEqual(lArray.data, expectedResult)
+    end
+end
+
+function testIndexreferenceRound(testCase)
+    cases = {
+        % {
+        %     array,
+        %     index,
+        %     roundResult
+        % },
+        {
+            {1,2,3},
+            1,
+            {1},
+        },
+        {
+            {1,3,2},
+            2,
+            {3},
+        },
+        {
+            {1,3,2},
+            [1 3],
+            {1, 2},
+        },
+    };
+
+
+    for i = 1:length(cases)
+        array = Cell(cases{i}{1});
+        index = cases{i}{2};
+        expected = cases{i}{3};
+
+        result = array(index);
+
+        testCase.verifyTrue(isa(result, 'Cell'));
+        testCase.verifyEqual(result.data, expected);
+    end
+end
+
+function testEndIndexing(testCase)
+    C = Cell({1,2,3});
+
+    testCase.verifyEqual(C(end), Cell({3}));
+    testCase.verifyEqual(C{end}, 3);
+end
+
+function testColonIndexing(testCase)
+    C = Cell({1, 2, 3});
+    D = Cell({1; 2; 3});
+
+    testCase.verifyEqual(C(:), D);
+    testCase.verifyEqual(D(:), D);
+end
+
+function testIndexreferenceCurly(testCase)
+    cases = {
+        % {
+        %     array,
+        %     index,
+        %     curlyResult
+        % },
+        {
+            {1,2,3},
+            1,
+            1
+        },
+        {
+            {1,3,2},
+            2,
+            3
+        },
+    };
+
+
+    for i = 1:length(cases)
+        array = Cell(cases{i}{1});
+        index = cases{i}{2};
+        expected = cases{i}{3};
+
+        result = array{index};
+
+        testCase.verifyEqual(result, expected);
+    end
+end
+
+function testConcat(testCase)
+    cases = {
+        {{},{}},
+        {{1},{}},
+        {{},{1}},
+        {{1},{2}},
+        {{1, 2},{3, 4}},
+        {{1; 2},{3; 4}},
+    };
+
+    horzCat = {
+        {},
+        {1},
+        {1},
+        {1,2},
+        {1, 2, 3, 4},
+        {1, 3; 2, 4},
+    };
+
+    vertCat = {
+        {},
+        {1},
+        {1},
+        {1; 2},
+        {1, 2; 3, 4},
+        {1; 2; 3; 4},
+    };
+
+    for i = 1:length(cases)
+        A = Cell(cases{i}{1});
+        B = Cell(cases{i}{2});
+
+        C_horz = [A, B];
+        C_vert = [A; B];
+
+        testCase.verifyEqual(C_horz.data, horzCat{i});
+        testCase.verifyEqual(C_vert.data, vertCat{i});
+
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/TextTable.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,122 @@
+classdef TextTable < handle
+    properties
+        data
+        fmtArray
+        vertDiv
+        horzDiv
+
+        nCols
+        nRows
+    end
+
+    methods
+        function obj = TextTable(data, vertDiv, horzDiv);
+            default_arg('vertDiv', []);
+            default_arg('horzDiv', []);
+
+
+            obj.data = data;
+            obj.vertDiv = vertDiv;
+            obj.horzDiv = horzDiv;
+
+            [obj.nRows, obj.nCols] = size(data);
+            obj.fmtArray = cell(size(data));
+            obj.formatAll('%s');
+
+        end
+
+        function formatAll(obj, fmt)
+            obj.fmtArray(:,:) = {fmt};
+        end
+
+        function formatCell(obj, i, j, fmt)
+            obj.fmtArray{i,j} = fmt;
+        end
+
+        function formatRow(obj, i, fmt)
+            obj.fmtArray(i,:) = {fmt};
+        end
+
+        function formatColumn(obj, j, fmt)
+            obj.fmtArray(:,j) = {fmt};
+        end
+
+        function widths = getWidths(obj)
+            strArray = obj.getStringArray();
+
+            widths = zeros(1,obj.nCols);
+            for j = 1:obj.nCols
+                for i = 1:obj.nRows
+                    widths(j) = max(widths(j), length(strArray{i,j}));
+                end
+            end
+        end
+
+        function str = toString(obj)
+            strArray = obj.getStringArray();
+            widths = obj.getWidths();
+
+            str = '';
+
+            % First horzDiv
+            if ismember(0, obj.horzDiv)
+                str = [str, obj.getHorzDiv(widths)];
+            end
+
+            for i = 1:obj.nRows
+                str = [str, TextTable.rowToString(strArray(i,:), widths, obj.vertDiv, obj.horzDiv)];
+
+                % Interior horzDiv
+                if ismember(i, obj.horzDiv)
+                    str = [str, obj.getHorzDiv(widths)];
+                end
+            end
+        end
+
+        function str = getHorzDiv(obj, widths)
+            str = TextTable.rowToString(cell(1,obj.nCols), widths, obj.vertDiv, obj.horzDiv);
+            str(find(' ' == str)) = '-';
+            str(find('|' == str)) = '+';
+        end
+
+        function strArray = getStringArray(obj)
+            strArray = cell(size(obj.data));
+
+            for i = 1:obj.nRows
+                for j = 1:obj.nCols
+                    strArray{i,j} = sprintf(obj.fmtArray{i,j}, obj.data{i,j});
+                end
+            end
+        end
+    end
+
+    methods (Static)
+        function str = rowToString(strs, widths, vertDiv, horzDiv)
+            % First vertDiv
+            if ismember(0, vertDiv)
+                str = '| ';
+            else
+                str = ' ';
+            end
+
+            % Interior cols
+            for j = 1:length(strs) - 1
+                str = [str, sprintf('%*s ', widths(j), strs{j})];
+
+                % Interior vertDiv
+                if ismember(j, vertDiv)
+                    str = [str, '| '];
+                end
+            end
+
+            % Last col
+            str = [str, sprintf('%*s ', widths(end), strs{end})];
+
+            if ismember(length(strs), vertDiv)
+                str = [str, '|'];
+            end
+
+            str = [str, sprintf('\n')];
+        end
+    end
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/assertSymbolic.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,3 @@
+function assertSymbolic(s)
+    assert(logical(simplify(s)));
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/assertType.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,11 @@
+function assertType(obj, type)
+    if ~iscell(type)
+        if ~isa(obj, type)
+            error('sbplib:assertType:wrongType', '"%s" must have type "%s", found "%s"', inputname(1), type, class(obj));
+        end
+    else
+        if ~isAnyOf(obj, type)
+            error('sbplib:assertType:wrongType', '"%s" must be one of the types %s, found "%s"', inputname(1), toString(type), class(obj));
+        end
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/callConstructor.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,6 @@
+% Calls the constructor of an object.
+% Might be usefull to call the constructor of a subclass object in the superclass
+function obj = callConstructor(subclassObj, varargin)
+    fun = str2func(class(subclassObj));
+    obj = fun(varargin{:});
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gaussian.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,3 @@
+function z = gaussian(x,x0,d)
+    z = exp(-norm(x-x0).^2/d^2);
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/isAnyOf.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,11 @@
+% Returns true of obj is any of he types in the cell array types
+%    b = isAnyOf(obj, types)
+function b = isAnyOf(obj, types)
+    for i = 1:length(types)
+        if isa(obj, types{i});
+            b = true;
+            return
+        end
+    end
+    b = false;
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/isEquidistant.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,19 @@
+% Tests if consecutive elements of vector v are euidistant
+function b = isEquidistant(v)
+    if length(v) < 2
+        error('sbplib:isEquidistant:inputTooShort', 'Input vector is too short');
+    end
+
+    tol = 1e-8;
+
+    d = v(2:end) - v(1:end-1);
+    err = abs(d - d(1));
+
+    relErr = err./abs(d);
+
+    I_zero = find(d < tol);
+
+    relErr(I_zero) = err(I_zero);
+
+    b = all(relErr < tol);
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/isEquidistantTest.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,31 @@
+function tests = isEquidistantTest()
+    tests = functiontests(localfunctions);
+end
+
+function testTooShortInput(testCase)
+    testCase.verifyError(@()isEquidistant([]), 'sbplib:isEquidistant:inputTooShort')
+end
+
+function testCorrectOutput(testCase)
+    cases = {
+        % {input, expected},
+        {[0,0,0,0,0], true},
+        {[1,1,1,1,1], true},
+        {[1,2,3,4,5], true},
+        {[1,3,4,5], false},
+        {[1,2,3,5], false},
+        {[1,2,4,5], false},
+        {linspace(0,pi, 3), true},
+        {linspace(0,1, 4), true},
+        {linspace(0,1, 4123), true},
+        {linspace(0,sin(1), 123), true},
+    };
+
+    for i = 1:length(cases)
+        input = cases{i}{1};
+        expected = cases{i}{2};
+        result = isEquidistant(input);
+
+        testCase.verifyEqual(result,expected);
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/logsurf.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,19 @@
+function [srfHandle, cbHandle] = logsurf(X,Y,Z, lim)
+    absLogZ = log10(abs(Z));
+    srfHandle = surf(X,Y,absLogZ);
+
+    cbHandle = colorbar();
+    colormap(hot(256));
+    ah = gca();
+    ah.CLim = lim;
+
+    oldTickLabels = cbHandle.TickLabels;
+
+    newTickLabels = {};
+
+    for i = 1:length(oldTickLabels)
+        newTickLabels{i} = sprintf('10^{%s}',oldTickLabels{i});
+    end
+
+    cbHandle.TickLabels = newTickLabels;
+end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/skewPart.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,4 @@
+% Returns the skew of A
+function S = skewPart(A, tol)
+    S = 1/2*(A - A');
+end
--- a/spdiag.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/spdiag.m	Mon Sep 11 14:17:15 2017 +0200
@@ -1,5 +1,10 @@
 function A = spdiag(a,i)
     default_arg('i',0);
+
+    if isrow(a)
+        a = a';
+    end
+
     n = length(a)-abs(i);
     A = spdiags(a,i,n,n);
 end
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spzeros.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,18 @@
+function S = spzeros(varargin)
+    switch length(varargin)
+        case 2
+            S = sparse(varargin{1}, varargin{2});
+        case 1
+            v = varargin{1};
+            switch length(v)
+                case 1
+                    S = sparse(v,v);
+                case 2
+                    S = sparse(v(1), v(2));
+                otherwise
+                    error('Input must be either one integer, two integers or a vector with two integers');
+            end
+        otherwise
+            error('Too many input arguments.');
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/surfSym.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,54 @@
+function h = surfSym(X,Y,Z)
+    if isvector(X) && isvector(Y)
+        [X,Y] = meshgrid(X,Y);
+    end
+
+    V_nodes = [X(:), Y(:), Z(:)];
+
+    X_centers = neighbourMean(X);
+    Y_centers = neighbourMean(Y);
+    Z_centers = neighbourMean(Z);
+    V_centers = [X_centers(:), Y_centers(:), Z_centers(:)];
+
+
+    N = prod(size(X));
+    nodeIndecies = reshape(1:N, size(X));
+    centerIndecies = reshape(N+(1:prod(size(X)-[1,1])), size(X) - [1,1]);
+
+    % figure()
+    % h = line(V_nodes(:,1),V_nodes(:,2),V_nodes(:,3));
+    % h.LineStyle = 'none';
+    % h.Marker = '.';
+    % h = line(V_centers(:,1),V_centers(:,2),V_centers(:,3));
+    % h.LineStyle = 'none';
+    % h.Marker = '.';
+    % h.Color = Color.red;
+    % axis equal
+
+
+    I_0 = nodeIndecies(1:end-1, 1:end-1);
+    I_1 = nodeIndecies(2:end, 1:end-1);
+    I_2 = nodeIndecies(2:end, 2:end);
+    I_3 = nodeIndecies(1:end-1, 2:end);
+    I_C = centerIndecies;
+
+    S.Vertices = [
+        V_nodes;
+        V_centers;
+    ];
+
+    S.Faces = [
+        I_0(:), I_1(:), I_C(:);
+        I_1(:), I_2(:), I_C(:);
+        I_2(:), I_3(:), I_C(:);
+        I_3(:), I_0(:), I_C(:);
+    ];
+
+    % figure()
+    h = patch(S, 'FaceVertexCData',  S.Vertices(:,3),'FaceColor','flat');
+end
+
+% Calculate the mean of four neighbours around a patch
+function M = neighbourMean(A)
+    M = (A(1:end-1, 1:end-1) + A(2:end, 1:end-1) + A(1:end-1, 2:end) + A(2:end, 2:end))/4;
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/symmetricPart.m	Mon Sep 11 14:17:15 2017 +0200
@@ -0,0 +1,4 @@
+% Returns the symmetric of A
+function S = symmetricPart(A, tol)
+    S = 1/2*(A + A');
+end
--- a/textTable.m	Mon Sep 11 14:12:54 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-
-% data           -- cell array of numbers
-% leftColstrings -- cell array of strings, for left column
-% topRowStrings  -- cell array of strings, for top row
-% dataFormat     -- (optional) format specifier, e.g. '%.2f' 
-function textTable(data, leftColStrings, topRowStrings, dataFormat)
-
-    default_arg('dataFormat','%.2f')
-    
-    nRows = length(leftColStrings);
-    nCols = length(topRowStrings);
-    [m,n] = size(data);
-    
-    if(m ~= nRows || n ~=nCols)
-        error('Data dimensions must match labels');
-    end
-
-    % Find column widths
-    headerLength = 0;
-    for i = 1:nCols
-        headerLength = max(headerLength, length(topRowStrings{i} ));
-    end
- 
-    dataLength = 0;
-    for i = 1:nRows
-        for j = 1:nCols
-            temp = length(sprintf(dataFormat, data{i,j}));
-            dataLength = max(dataLength, temp);
-        end
-    end
-    dataLength = length(sprintf(dataFormat, data{1,1}));
-    
-    colWidth = max(headerLength,dataLength);
-
-    % Print headers
-    fprintf(' %*s |',colWidth,'')
-    for i = 1:nCols
-        fprintf(' %-*s |', colWidth, topRowStrings{i});
-    end
-    fprintf('\n');
-
-    % Print divider
-    m_dev = repmat('-',1,colWidth);
-    column_dev = repmat('-',1,colWidth);
-    fprintf('-%s-+',m_dev);
-    for i = 1:nCols
-        fprintf('-%s-+', column_dev);
-    end
-    fprintf('\n');
-    
-
-    % Print each row
-    dataFormat = ['%*' dataFormat(2:end)];
-    for i = 1:nRows
-        fprintf(' %*s |',colWidth,leftColStrings{i});
-        for j = 1:nCols
-            fprintf([' ' dataFormat ' |'], colWidth, data{i,j});
-        end
-        fprintf('\n');
-    end
-
-    fprintf('\n');
-
-end
\ No newline at end of file
--- a/toString.m	Mon Sep 11 14:12:54 2017 +0200
+++ b/toString.m	Mon Sep 11 14:17:15 2017 +0200
@@ -51,6 +51,14 @@
 end
 
 function str = struct2string(s)
+    if isscalar(s)
+        str = structScalar2string(s);
+    else
+        str = structArray2string(s);
+    end
+end
+
+function str = structScalar2string(s)
     fn = fieldnames(s);
 
     if length(fn) == 0
@@ -68,3 +76,32 @@
     str = [str sprintf('%s: %s}',fn{end}, value2string(value))];
 end
 
+function str = structArray2string(s)
+    fn = fieldnames(s);
+
+    if length(fn) == 0
+        str = '{}';
+        return
+    end
+
+    stringArray = cell(length(s)+1, length(fn)+1);
+
+    stringArray(1,2:end) = fn;
+
+    for i = 1:length(s)
+        stringArray{i+1,1} = i;
+        for j = 1:length(fn)
+            valueStr = value2string(s(i).(fn{j}));
+            stringArray{i+1,j+1} = valueStr;
+        end
+    end
+
+    tt = TextTable(stringArray);
+    tt.fmtArray(2:end, 1) = {'%d'};
+    tt.vertDiv = [1];
+    tt.horzDiv = [1];
+    str = tt.toString();
+end
+
+
+