changeset 231:fbabfd4e8f20

Merge in boundary_conditions
author Jonatan Werpers <jonatan@werpers.com>
date Wed, 26 Jun 2019 15:07:47 +0200
parents ce56727e4232 (current diff) 3d94f60f6fae (diff)
children 0f94dc29c4bf
files AbstractGrid.jl EquidistantGrid.jl d2_2nd.txt d2_4th.txt diffOp.jl grid.jl h_2nd.txt h_4th.txt index.jl sbpD2.jl stencil.jl
diffstat 44 files changed, 1302 insertions(+), 516 deletions(-) [+]
line wrap: on
line diff
--- a/AbstractGrid.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-abstract type AbstractGrid end
-
-function dimension(grid::AbstractGrid)
-    error("Not implemented for abstact type AbstractGrid")
-end
-
-function points(grid::AbstractGrid)
-    error("Not implemented for abstact type AbstractGrid")
-end
-
-# Evaluate function f on the grid g
-function evalOn(g::AbstractGrid, f::Function)
-    F(x) = f(x...)
-    return F.(points(g))
-end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DiffOps/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,61 @@
+# This file is machine-generated - editing it directly is not advised
+
+[[Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+
+[[Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+
+[[Grids]]
+deps = ["RegionIndices"]
+path = "../Grids"
+uuid = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+version = "0.1.0"
+
+[[InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+
+[[Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+
+[[Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+
+[[OffsetArrays]]
+git-tree-sha1 = "1af2f79c7eaac3e019a0de41ef63335ff26a0a57"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "0.11.1"
+
+[[Random]]
+deps = ["Serialization"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+
+[[RegionIndices]]
+path = "../RegionIndices"
+uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
+version = "0.1.0"
+
+[[SbpOperators]]
+deps = ["RegionIndices"]
+path = "../SbpOperators"
+uuid = "204357d8-97fd-11e9-05e7-010897a14cd0"
+version = "0.1.0"
+
+[[Serialization]]
+uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
+
+[[Sockets]]
+uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
+
+[[Test]]
+deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[TiledIteration]]
+deps = ["OffsetArrays", "Test"]
+git-tree-sha1 = "58f6f07d3b54a363ec283a8f5fc9fb4ecebde656"
+uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
+version = "0.2.3"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DiffOps/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,16 @@
+name = "DiffOps"
+uuid = "39474f48-97ec-11e9-01fc-6ddcbe5918df"
+authors = ["Jonatan Werpers <jonatan.werpers@it.uu.se>"]
+version = "0.1.0"
+
+[deps]
+Grids = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+RegionIndices = "5d527584-97f1-11e9-084c-4540c7ecf219"
+SbpOperators = "204357d8-97fd-11e9-05e7-010897a14cd0"
+TiledIteration = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
+
+[extras]
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DiffOps/src/DiffOps.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,103 @@
+module DiffOps
+
+using RegionIndices
+using SbpOperators
+using Grids
+
+"""
+    DiffOp
+
+Supertype of differential operator discretisations.
+The action of the DiffOp is defined in the method
+    apply(D::DiffOp, v::AbstractVector, I...)
+"""
+abstract type DiffOp end
+
+function apply end
+
+function matrixRepresentation(D::DiffOp)
+    error("not implemented")
+end
+
+abstract type DiffOpCartesian{Dim} <: DiffOp end
+
+# DiffOp must have a grid of dimension Dim!!!
+function apply!(D::DiffOpCartesian{Dim}, u::AbstractArray{T,Dim}, v::AbstractArray{T,Dim}) where {T,Dim}
+    for I ∈ eachindex(D.grid)
+        u[I] = apply(D, v, I)
+    end
+
+    return nothing
+end
+export apply!
+
+function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T
+    apply_region!(D, u, v, Lower, Lower)
+    apply_region!(D, u, v, Lower, Interior)
+    apply_region!(D, u, v, Lower, Upper)
+    apply_region!(D, u, v, Interior, Lower)
+    apply_region!(D, u, v, Interior, Interior)
+    apply_region!(D, u, v, Interior, Upper)
+    apply_region!(D, u, v, Upper, Lower)
+    apply_region!(D, u, v, Upper, Interior)
+    apply_region!(D, u, v, Upper, Upper)
+    return nothing
+end
+
+# Maybe this should be split according to b3fbef345810 after all?! Seems like it makes performance more predictable
+function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T
+    for I ∈ regionindices(D.grid.size, closureSize(D.op), (r1,r2))
+        @inbounds indextuple = (Index{r1}(I[1]), Index{r2}(I[2]))
+        @inbounds u[I] = apply(D, v, indextuple)
+    end
+    return nothing
+end
+export apply_region!
+
+function apply_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T
+    apply_region_tiled!(D, u, v, Lower, Lower)
+    apply_region_tiled!(D, u, v, Lower, Interior)
+    apply_region_tiled!(D, u, v, Lower, Upper)
+    apply_region_tiled!(D, u, v, Interior, Lower)
+    apply_region_tiled!(D, u, v, Interior, Interior)
+    apply_region_tiled!(D, u, v, Interior, Upper)
+    apply_region_tiled!(D, u, v, Upper, Lower)
+    apply_region_tiled!(D, u, v, Upper, Interior)
+    apply_region_tiled!(D, u, v, Upper, Upper)
+    return nothing
+end
+
+using TiledIteration
+function apply_region_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T
+    ri = regionindices(D.grid.size, closureSize(D.op), (r1,r2))
+    # TODO: Pass Tilesize to function
+    for tileaxs ∈ TileIterator(axes(ri), padded_tilesize(T, (5,5), 2))
+        for j ∈ tileaxs[2], i ∈ tileaxs[1]
+            I = ri[i,j]
+            u[I] = apply(D, v, (Index{r1}(I[1]), Index{r2}(I[2])))
+        end
+    end
+    return nothing
+end
+export apply_region_tiled!
+
+function apply(D::DiffOp, v::AbstractVector)::AbstractVector
+    u = zeros(eltype(v), size(v))
+    apply!(D,v,u)
+    return u
+end
+
+export apply
+
+"""
+A BoundaryCondition should implement the method
+    sat(::DiffOp, v::AbstractArray, data::AbstractArray, ...)
+"""
+abstract type BoundaryCondition end
+
+
+include("laplace.jl")
+export Laplace
+
+
+end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DiffOps/src/laplace.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,131 @@
+struct NormalDerivative{N,M,K}
+	op::D2{Float64,N,M,K}
+	grid::EquidistantGrid
+	bId::CartesianBoundary
+end
+
+function apply_transpose(d::NormalDerivative, v::AbstractArray, I::Integer)
+	u = selectdim(v,3-dim(d.bId),I)
+	return apply_d(d.op, d.grid.inverse_spacing[dim(d.bId)], u, region(d.bId))
+end
+
+# Not correct abstraction level
+# TODO: Not type stable D:<
+function apply(d::NormalDerivative, v::AbstractArray, I::Tuple{Integer,Integer})
+	i = I[dim(d.bId)]
+	j = I[3-dim(d.bId)]
+	N_i = d.grid.size[dim(d.bId)]
+
+	r = getregion(i, closureSize(d.op), N_i)
+
+	if r != region(d.bId)
+		return 0
+	end
+
+	if r == Lower
+		# Note, closures are indexed by offset. Fix this D:<
+		return d.grid.inverse_spacing[dim(d.bId)]*d.op.dClosure[i-1]*v[j]
+	elseif r == Upper
+		return d.grid.inverse_spacing[dim(d.bId)]*d.op.dClosure[N_i-j]*v[j]
+	end
+end
+
+struct BoundaryValue{N,M,K}
+	op::D2{Float64,N,M,K}
+	grid::EquidistantGrid
+	bId::CartesianBoundary
+end
+
+function apply(e::BoundaryValue, v::AbstractArray, I::Tuple{Integer,Integer})
+	i = I[dim(e.bId)]
+	j = I[3-dim(e.bId)]
+	N_i = e.grid.size[dim(e.bId)]
+
+	r = getregion(i, closureSize(e.op), N_i)
+
+	if r != region(e.bId)
+		return 0
+	end
+
+	if r == Lower
+		# Note, closures are indexed by offset. Fix this D:<
+		return e.op.eClosure[i-1]*v[j]
+	elseif r == Upper
+		return e.op.eClosure[N_i-j]*v[j]
+	end
+end
+
+function apply_transpose(e::BoundaryValue, v::AbstractArray, I::Integer)
+	u = selectdim(v,3-dim(e.bId),I)
+	return apply_e(e.op, u, region(e.bId))
+end
+
+struct Laplace{Dim,T<:Real,N,M,K} <: DiffOpCartesian{Dim}
+    grid::EquidistantGrid{Dim,T}
+    a::T
+    op::D2{Float64,N,M,K}
+    # e::BoundaryValue
+    # d::NormalDerivative
+end
+
+function apply(L::Laplace{Dim}, v::AbstractArray{T,Dim} where T, I::CartesianIndex{Dim}) where Dim
+    error("not implemented")
+end
+
+# u = L*v
+function apply(L::Laplace{1}, v::AbstractVector, i::Int)
+    uᵢ = L.a * SbpOperators.apply(L.op, L.grid.spacing[1], v, i)
+    return uᵢ
+end
+
+@inline function apply(L::Laplace{2}, v::AbstractArray{T,2} where T, I::Tuple{Index{R1}, Index{R2}}) where {R1, R2}
+    # 2nd x-derivative
+    @inbounds vx = view(v, :, Int(I[2]))
+    @inbounds uᵢ = L.a*SbpOperators.apply(L.op, L.grid.inverse_spacing[1], vx , I[1])
+    # 2nd y-derivative
+    @inbounds vy = view(v, Int(I[1]), :)
+    @inbounds uᵢ += L.a*SbpOperators.apply(L.op, L.grid.inverse_spacing[2], vy, I[2])
+    # NOTE: the package qualifier 'SbpOperators' can problably be removed once all "applying" objects use LazyTensors
+    return uᵢ
+end
+
+# Slow but maybe convenient?
+function apply(L::Laplace{2}, v::AbstractArray{T,2} where T, i::CartesianIndex{2})
+    I = Index{Unknown}.(Tuple(i))
+    apply(L, v, I)
+end
+
+
+struct Neumann{Bid<:BoundaryIdentifier} <: BoundaryCondition end
+
+function sat(L::Laplace{2,T}, bc::Neumann{Bid}, v::AbstractArray{T,2}, g::AbstractVector{T}, I::CartesianIndex{2}) where {T,Bid}
+    e = BoundaryValue(L.op, L.grid, Bid())
+    d = NormalDerivative(L.op, L.grid, Bid())
+    Hᵧ = BoundaryQuadrature(L.op, L.grid, Bid())
+    # TODO: Implement BoundaryQuadrature method
+
+    return -L.Hi*e*Hᵧ*(d'*v - g)
+    # Need to handle d'*v - g so that it is an AbstractArray that TensorMappings can act on
+end
+
+struct Dirichlet{Bid<:BoundaryIdentifier} <: BoundaryCondition
+    tau::Float64
+end
+
+function sat(L::Laplace{2,T}, bc::Dirichlet{Bid}, v::AbstractArray{T,2}, g::AbstractVector{T}, i::CartesianIndex{2}) where {T,Bid}
+    e = BoundaryValue(L.op, L.grid, Bid())
+    d = NormalDerivative(L.op, L.grid, Bid())
+    Hᵧ = BoundaryQuadrature(L.op, L.grid, Bid())
+    # TODO: Implement BoundaryQuadrature method
+
+    return -L.Hi*(tau/h*e + d)*Hᵧ*(e'*v - g)
+    # Need to handle scalar multiplication and addition of TensorMapping
+end
+
+# function apply(s::MyWaveEq{D},  v::AbstractArray{T,D}, i::CartesianIndex{D}) where D
+# 	return apply(s.L, v, i) +
+# 		sat(s.L, Dirichlet{CartesianBoundary{1,Lower}}(s.tau),  v, s.g_w, i) +
+# 		sat(s.L, Dirichlet{CartesianBoundary{1,Upper}}(s.tau),  v, s.g_e, i) +
+# 		sat(s.L, Dirichlet{CartesianBoundary{2,Lower}}(s.tau),  v, s.g_s, i) +
+# 		sat(s.L, Dirichlet{CartesianBoundary{2,Upper}}(s.tau),  v, s.g_n, i)
+# end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DiffOps/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,4 @@
+using DiffOps
+using Test
+
+@test_broken false
--- a/EquidistantGrid.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-# EquidistantGrid is a grid with equidistant grid spacing per coordinat
-# direction. The domain is defined through the two points P1 = x̄₁, P2 = x̄₂
-# by the exterior product of the vectors obtained by projecting (x̄₂-x̄₁) onto
-# the coordinate directions. E.g for a 2D grid with x̄₁=(-1,0) and x̄₂=(1,2)
-# the domain is defined as (-1,1)x(0,2).
-
-struct EquidistantGrid{Dim,T<:Real} <: AbstractGrid
-    size::NTuple{Dim, Int} # First coordinate direction stored first
-    limit_lower::NTuple{Dim, T}
-    limit_upper::NTuple{Dim, T}
-    inverse_spacing::NTuple{Dim, T} # The reciprocal of the grid spacing
-
-    # General constructor
-    function EquidistantGrid(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) where Dim where T
-        @assert all(size.>0)
-        @assert all(limit_upper.-limit_lower .!= 0)
-        inverse_spacing = (size.-1)./abs.(limit_upper.-limit_lower)
-        return new{Dim,T}(size, limit_lower, limit_upper, inverse_spacing)
-    end
-end
-
-function Base.eachindex(grid::EquidistantGrid)
-    CartesianIndices(grid.size)
-end
-
-# Returns the number of dimensions of an EquidistantGrid.
-#
-# @Input: grid - an EquidistantGrid
-# @Return: dimension - The dimension of the grid
-function dimension(grid::EquidistantGrid)
-    return length(grid.size)
-end
-
-# Returns the spacing of the grid
-#
-function spacing(grid::EquidistantGrid)
-    return 1.0./grid.inverse_spacing
-end
-
-# Computes the points of an EquidistantGrid as an array of tuples with
-# the same dimension as the grid.
-#
-# @Input: grid - an EquidistantGrid
-# @Return: points - the points of the grid.
-function points(grid::EquidistantGrid)
-    # TODO: Make this return an abstract array?
-    indices = Tuple.(CartesianIndices(grid.size))
-    h = spacing(grid)
-    return broadcast(I -> grid.limit_lower .+ (I.-1).*h, indices)
-end
-
-function pointsalongdim(grid::EquidistantGrid, dim::Integer)
-    @assert dim<=dimension(grid)
-    @assert dim>0
-    points = collect(range(grid.limit_lower[dim],stop=grid.limit_upper[dim],length=grid.size[dim]))
-end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,6 @@
+# This file is machine-generated - editing it directly is not advised
+
+[[RegionIndices]]
+path = "../RegionIndices"
+uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
+version = "0.1.0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,13 @@
+name = "Grids"
+uuid = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+authors = ["Jonatan Werpers <jonatan.werpers@it.uu.se>"]
+version = "0.1.0"
+
+[deps]
+RegionIndices = "5d527584-97f1-11e9-084c-4540c7ecf219"
+
+[extras]
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/src/AbstractGrid.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,24 @@
+"""
+     AbstractGrid
+
+Should implement
+    dimension(grid::AbstractGrid)
+    points(grid::AbstractGrid)
+
+"""
+abstract type AbstractGrid end
+
+function dimension end
+function points end
+export dimension, points
+
+"""
+    evalOn(g::AbstractGrid, f::Function)
+
+Evaluate function f on the grid g
+"""
+function evalOn(g::AbstractGrid, f::Function)
+    F(x) = f(x...)
+    return F.(points(g))
+end
+export evalOn
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/src/EquidistantGrid.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,58 @@
+# EquidistantGrid is a grid with equidistant grid spacing per coordinat
+# direction. The domain is defined through the two points P1 = x̄₁, P2 = x̄₂
+# by the exterior product of the vectors obtained by projecting (x̄₂-x̄₁) onto
+# the coordinate directions. E.g for a 2D grid with x̄₁=(-1,0) and x̄₂=(1,2)
+# the domain is defined as (-1,1)x(0,2).
+
+export EquidistantGrid
+
+struct EquidistantGrid{Dim,T<:Real} <: AbstractGrid
+    size::NTuple{Dim, Int} # First coordinate direction stored first
+    limit_lower::NTuple{Dim, T}
+    limit_upper::NTuple{Dim, T}
+    inverse_spacing::NTuple{Dim, T} # The reciprocal of the grid spacing
+
+    # General constructor
+    function EquidistantGrid(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) where Dim where T
+        @assert all(size.>0)
+        @assert all(limit_upper.-limit_lower .!= 0)
+        inverse_spacing = (size.-1)./abs.(limit_upper.-limit_lower)
+        return new{Dim,T}(size, limit_lower, limit_upper, inverse_spacing)
+    end
+end
+
+function Base.eachindex(grid::EquidistantGrid)
+    CartesianIndices(grid.size)
+end
+
+# Returns the number of dimensions of an EquidistantGrid.
+#
+# @Input: grid - an EquidistantGrid
+# @Return: dimension - The dimension of the grid
+function dimension(grid::EquidistantGrid)
+    return length(grid.size)
+end
+
+# Returns the spacing of the grid
+#
+function spacing(grid::EquidistantGrid)
+    return 1.0./grid.inverse_spacing
+end
+
+# Computes the points of an EquidistantGrid as an array of tuples with
+# the same dimension as the grid.
+#
+# @Input: grid - an EquidistantGrid
+# @Return: points - the points of the grid.
+function points(grid::EquidistantGrid)
+    # TODO: Make this return an abstract array?
+    indices = Tuple.(CartesianIndices(grid.size))
+    h = spacing(grid)
+    return broadcast(I -> grid.limit_lower .+ (I.-1).*h, indices)
+end
+
+function pointsalongdim(grid::EquidistantGrid, dim::Integer)
+    @assert dim<=dimension(grid)
+    @assert dim>0
+    points = collect(range(grid.limit_lower[dim],stop=grid.limit_upper[dim],length=grid.size[dim]))
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/src/Grids.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,15 @@
+module Grids
+
+using RegionIndices
+
+export BoundaryIdentifier, CartesianBoundary
+
+abstract type BoundaryIdentifier end
+struct CartesianBoundary{Dim, R<:Region} <: BoundaryIdentifier end
+dim(::CartesianBoundary{Dim, R}) where {Dim, R} = Dim
+region(::CartesianBoundary{Dim, R}) where {Dim, R} = R
+
+include("AbstractGrid.jl")
+include("EquidistantGrid.jl")
+
+end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Grids/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,4 @@
+using Grids
+using Test
+
+@test_broken false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,2 @@
+# This file is machine-generated - editing it directly is not advised
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,10 @@
+name = "LazyTensors"
+uuid = "62fbed2c-918d-11e9-279b-eb3a325b37d3"
+authors = ["Jonatan Werpers <jonatan.werpers@it.uu.se>"]
+version = "0.1.0"
+
+[extras]
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/src/LazyTensors.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,6 @@
+module LazyTensors
+
+include("tensor_mapping.jl")
+include("lazy_operations.jl")
+
+end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/src/lazy_operations.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,193 @@
+"""
+    LazyArray{T,D} <: AbstractArray{T,D}
+
+Array which is calcualted lazily when indexing.
+
+A subtype of `LazyArray` will use lazy version of `+`, `-`, `*`, `/`.
+"""
+abstract type LazyArray{T,D} <: AbstractArray{T,D} end
+export LazyArray
+
+
+
+"""
+    LazyTensorMappingApplication{T,R,D} <: LazyArray{T,R}
+
+Struct for lazy application of a TensorMapping. Created using `*`.
+
+Allows the result of a `TensorMapping` applied to a vector to be treated as an `AbstractArray`.
+With a mapping `m` and a vector `v` the LazyTensorMappingApplication object can be created by `m*v`.
+The actual result will be calcualted when indexing into `m*v`.
+"""
+struct LazyTensorMappingApplication{T,R,D} <: LazyArray{T,R}
+    t::TensorMapping{T,R,D}
+    o::AbstractArray{T,D}
+end
+export LazyTensorMappingApplication
+
+Base.:*(tm::TensorMapping{T,R,D}, o::AbstractArray{T,D}) where {T,R,D} = LazyTensorMappingApplication(tm,o)
+
+Base.getindex(ta::LazyTensorMappingApplication{T,R,D}, I::Vararg) where {T,R,D} = apply(ta.t, ta.o, I...)
+Base.size(ta::LazyTensorMappingApplication{T,R,D}) where {T,R,D} = range_size(ta.t,size(ta.o))
+# TODO: What else is needed to implement the AbstractArray interface?
+
+# # We need the associativity to be a→b→c = a→(b→c), which is the case for '→'
+Base.:*(args::Union{TensorMapping{T}, AbstractArray{T}}...) where T = foldr(*,args)
+# # Should we overload some other infix binary operator?
+# →(tm::TensorMapping{T,R,D}, o::AbstractArray{T,D}) where {T,R,D} = LazyTensorMappingApplication(tm,o)
+# TODO: We need to be really careful about good error messages.
+# For example what happens if you try to multiply LazyTensorMappingApplication with a TensorMapping(wrong order)?
+
+
+
+"""
+    LazyElementwiseOperation{T,D,Op, T1<:AbstractArray{T,D}, T2 <: AbstractArray{T,D}} <: AbstractArray{T,D}
+
+Struct allowing for lazy evaluation of elementwise operations on AbstractArrays.
+
+A LazyElementwiseOperation contains two AbstractArrays of equal size,
+together with an operation. The operations are carried out when the
+LazyElementwiseOperation is indexed.
+"""
+struct LazyElementwiseOperation{T,D,Op, T1<:AbstractArray{T,D}, T2 <: AbstractArray{T,D}} <: LazyArray{T,D}
+    a::T1
+    b::T2
+
+    @inline function LazyElementwiseOperation{T,D,Op}(a::T1,b::T2) where {T,D,Op, T1<:AbstractArray{T,D}, T2<:AbstractArray{T,D}}
+        @boundscheck if size(a) != size(b)
+            throw(DimensionMismatch("dimensions must match"))
+        end
+        return new{T,D,Op,T1,T2}(a,b)
+    end
+end
+# TODO: Move Op to be the first parameter? Compare to Binary operations
+
+Base.size(v::LazyElementwiseOperation) = size(v.a)
+
+# TODO: Make sure boundschecking is done properly and that the lenght of the vectors are equal
+# NOTE: Boundschecking in getindex functions now assumes that the size of the
+# vectors in the LazyElementwiseOperation are the same size. If we remove the
+# size assertion in the constructor we might have to handle
+# boundschecking differently.
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:+}, I...) where {T,D}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],[I...]))
+    end
+    return leo.a[I...] + leo.b[I...]
+end
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:-}, I...) where {T,D}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],[I...]))
+    end
+    return leo.a[I...] - leo.b[I...]
+end
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:*}, I...) where {T,D}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],[I...]))
+    end
+    return leo.a[I...] * leo.b[I...]
+end
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:/}, I...) where {T,D}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],[I...]))
+    end
+    return leo.a[I...] / leo.b[I...]
+end
+
+# Define lazy operations for AbstractArrays. Operations constructs a LazyElementwiseOperation which
+# can later be indexed into. Lazy operations are denoted by the usual operator followed by a tilde
+Base.@propagate_inbounds +̃(a::AbstractArray{T,D}, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:+}(a,b)
+Base.@propagate_inbounds -̃(a::AbstractArray{T,D}, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:-}(a,b)
+Base.@propagate_inbounds *̃(a::AbstractArray{T,D}, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:*}(a,b)
+Base.@propagate_inbounds /̃(a::AbstractArray{T,D}, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:/}(a,b)
+
+# NOTE: Är det knas att vi har till exempel * istället för .* ??
+# Oklart om det ens går att lösa..
+Base.@propagate_inbounds Base.:+(a::LazyArray{T,D}, b::LazyArray{T,D}) where {T,D} = a +̃ b
+Base.@propagate_inbounds Base.:+(a::LazyArray{T,D}, b::AbstractArray{T,D}) where {T,D} = a +̃ b
+Base.@propagate_inbounds Base.:+(a::AbstractArray{T,D}, b::LazyArray{T,D}) where {T,D} = a +̃ b
+
+Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::LazyArray{T,D}) where {T,D} = a -̃ b
+Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::AbstractArray{T,D}) where {T,D} = a -̃ b
+Base.@propagate_inbounds Base.:-(a::AbstractArray{T,D}, b::LazyArray{T,D}) where {T,D} = a -̃ b
+
+# Element wise operation for `*` and `\` are not overloaded due to conflicts with the behavior
+# of regular `*` and `/` for AbstractArrays. Use tilde versions instead.
+
+export +̃, -̃, *̃, /̃
+
+
+
+"""
+    LazyTensorMappingTranspose{T,R,D} <: TensorMapping{T,D,R}
+
+Struct for lazy transpose of a TensorMapping.
+
+If a mapping implements the the `apply_transpose` method this allows working with
+the transpose of mapping `m` by using `m'`. `m'` will work as a regular TensorMapping lazily calling
+the appropriate methods of `m`.
+"""
+struct LazyTensorMappingTranspose{T,R,D} <: TensorMapping{T,D,R}
+    tm::TensorMapping{T,R,D}
+end
+export LazyTensorMappingTranspose
+
+# # TBD: Should this be implemented on a type by type basis or through a trait to provide earlier errors?
+Base.adjoint(t::TensorMapping) = LazyTensorMappingTranspose(t)
+Base.adjoint(t::LazyTensorMappingTranspose) = t.tm
+
+apply(tm::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,R}, I::Vararg) where {T,R,D} = apply_transpose(tm.tm, v, I...)
+apply_transpose(tm::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {T,R,D} = apply(tm.tm, v, I...)
+
+range_size(tmt::LazyTensorMappingTranspose{T,R,D}, d_size::NTuple{R,Integer}) where {T,R,D} = domain_size(tmt.tm, domain_size)
+domain_size(tmt::LazyTensorMappingTranspose{T,R,D}, r_size::NTuple{D,Integer}) where {T,R,D} = range_size(tmt.tm, range_size)
+
+
+
+
+struct LazyTensorMappingBinaryOperation{Op,T,R,D,T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}} <: TensorMapping{T,D,R}
+    A::T1
+    B::T2
+
+    @inline function LazyTensorMappingBinaryOperation{Op,T,R,D}(A::T1,B::T2) where {Op,T,R,D, T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}}
+        return new{Op,T,R,D,T1,T2}(A,B)
+    end
+end
+
+apply(mb::LazyTensorMappingBinaryOperation{:+,T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {T,R,D} = apply(mb.A, v, I...) + apply(mb.B,v,I...)
+apply(mb::LazyTensorMappingBinaryOperation{:-,T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {T,R,D} = apply(mb.A, v, I...) - apply(mb.B,v,I...)
+
+range_size(mp::LazyTensorMappingBinaryOperation{Op,T,R,D}, domain_size::NTuple{D,Integer}) where {Op,T,R,D} = range_size(mp.A, domain_size)
+domain_size(mp::LazyTensorMappingBinaryOperation{Op,T,R,D}, range_size::NTuple{R,Integer}) where {Op,T,R,D} = domain_size(mp.A, range_size)
+
+Base.:+(A::TensorMapping{T,R,D}, B::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:+,T,R,D}(A,B)
+Base.:-(A::TensorMapping{T,R,D}, B::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:-,T,R,D}(A,B)
+
+
+# TODO: Write tests and documentation for LazyTensorMappingComposition
+# struct LazyTensorMappingComposition{T,R,K,D} <: TensorMapping{T,R,D}
+#     t1::TensorMapping{T,R,K}
+#     t2::TensorMapping{T,K,D}
+# end
+
+# Base.:∘(s::TensorMapping{T,R,K}, t::TensorMapping{T,K,D}) where {T,R,K,D} = LazyTensorMappingComposition(s,t)
+
+# function range_size(tm::LazyTensorMappingComposition{T,R,K,D}, domain_size::NTuple{D,Integer}) where {T,R,K,D}
+#     range_size(tm.t1, domain_size(tm.t2, domain_size))
+# end
+
+# function domain_size(tm::LazyTensorMappingComposition{T,R,K,D}, range_size::NTuple{R,Integer}) where {T,R,K,D}
+#     domain_size(tm.t1, domain_size(tm.t2, range_size))
+# end
+
+# function apply(c::LazyTensorMappingComposition{T,R,K,D}, v::AbstractArray{T,D}, I::Vararg) where {T,R,K,D}
+#     apply(c.t1, LazyTensorMappingApplication(c.t2,v), I...)
+# end
+
+# function apply_transpose(c::LazyTensorMappingComposition{T,R,K,D}, v::AbstractArray{T,D}, I::Vararg) where {T,R,K,D}
+#     apply_transpose(c.t2, LazyTensorMappingApplication(c.t1',v), I...)
+# end
+
+# # Have i gone too crazy with the type parameters? Maybe they aren't all needed?
+
+# export →
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/src/tensor_mapping.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,78 @@
+"""
+    TensorMapping{T,R,D}
+
+Describes a mapping of a D dimension tensor to an R dimension tensor.
+The action of the mapping is implemented through the method
+
+    apply(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T}
+
+The size of range tensor should be dependent on the size of the domain tensor
+and the type should implement the methods
+
+    range_size(::TensorMapping{T,R,D}, domain_size::NTuple{D,Integer}) where {T,R,D}
+    domain_size(::TensorMapping{T,R,D}, range_size::NTuple{R,Integer}) where {T,R,D}
+
+to allow querying for one or the other.
+
+Optionally the action of the transpose may be defined through
+    apply_transpose(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T}
+"""
+abstract type TensorMapping{T,R,D} end
+export TensorMapping
+
+"""
+    apply(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T}
+
+Return the result of the mapping for a given index.
+"""
+function apply end
+export apply
+
+"""
+    apply_transpose(t::TensorMapping{T,R,D}, v::AbstractArray{T,R}, I::Vararg) where {R,D,T}
+
+Return the result of the transposed mapping for a given index.
+"""
+function apply_transpose end
+export apply_transpose
+
+"""
+Return the dimension of the range space of a given mapping
+"""
+range_dim(::TensorMapping{T,R,D}) where {T,R,D} = R
+
+"""
+Return the dimension of the domain space of a given mapping
+"""
+domain_dim(::TensorMapping{T,R,D}) where {T,R,D} = D
+
+export range_dim, domain_dim
+
+"""
+    range_size(M::TensorMapping, domain_size)
+
+Return the resulting range size for the mapping applied to a given domain_size
+"""
+function range_size end
+
+"""
+    domain_size(M::TensorMapping, range_size)
+
+Return the resulting domain size for the mapping applied to a given range_size
+"""
+function domain_size end
+
+export range_size, domain_size
+# TODO: Think about boundschecking!
+
+
+"""
+    TensorOperator{T,D}
+
+A `TensorMapping{T,D,D}` where the range and domain tensor have the same number of
+dimensions and the same size.
+"""
+abstract type TensorOperator{T,D} <: TensorMapping{T,D,D} end
+export TensorOperator
+domain_size(::TensorOperator{T,D}, range_size::NTuple{D,Integer}) where {T,D} = range_size
+range_size(::TensorOperator{T,D}, domain_size::NTuple{D,Integer}) where {T,D} = domain_size
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,130 @@
+using Test
+using LazyTensors
+
+@testset "Generic Mapping methods" begin
+    struct DummyMapping{T,R,D} <: TensorMapping{T,R,D} end
+    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i) where {T,R,D} = :apply
+    @test range_dim(DummyMapping{Int,2,3}()) == 2
+    @test domain_dim(DummyMapping{Int,2,3}()) == 3
+    @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),0) == :apply
+end
+
+@testset "Generic Operator methods" begin
+    struct DummyOperator{T,D} <: TensorOperator{T,D} end
+    @test range_size(DummyOperator{Int,2}(), (3,5)) == (3,5)
+    @test domain_size(DummyOperator{Float64, 3}(), (3,3,1)) == (3,3,1)
+end
+
+@testset "Mapping transpose" begin
+    struct DummyMapping{T,R,D} <: TensorMapping{T,R,D} end
+
+    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i) where {T,R,D} = :apply
+    LazyTensors.apply_transpose(m::DummyMapping{T,R,D}, v, i) where {T,R,D} = :apply_transpose
+
+    LazyTensors.range_size(m::DummyMapping{T,R,D}, domain_size) where {T,R,D} = :range_size
+    LazyTensors.domain_size(m::DummyMapping{T,R,D}, range_size) where {T,R,D} = :domain_size
+
+    m = DummyMapping{Float64,2,3}()
+    @test m'' == m
+    @test apply(m',zeros(Float64,(0,0)),0) == :apply_transpose
+    @test apply(m'',zeros(Float64,(0,0,0)),0) == :apply
+    @test apply_transpose(m', zeros(Float64,(0,0,0)),0) == :apply
+
+    @test range_size(m', (0,0)) == :domain_size
+    @test domain_size(m', (0,0,0)) == :range_size
+end
+
+@testset "TensorApplication" begin
+    struct DummyMapping{T,R,D} <: TensorMapping{T,R,D} end
+
+    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i) where {T,R,D} = (:apply,v,i)
+    LazyTensors.apply_transpose(m::DummyMapping{T,R,D}, v, i) where {T,R,D} = :apply_transpose
+
+    LazyTensors.range_size(m::DummyMapping{T,R,D}, domain_size) where {T,R,D} = 2 .* domain_size
+    LazyTensors.domain_size(m::DummyMapping{T,R,D}, range_size) where {T,R,D} = range_size.÷2
+
+
+    m = DummyMapping{Int, 1, 1}()
+    v = [0,1,2]
+    @test m*v isa AbstractVector{Int}
+    @test size(m*v) == 2 .*size(v)
+    @test (m*v)[0] == (:apply,v,0)
+    @test m*m*v isa AbstractVector{Int}
+    @test (m*m*v)[1] == (:apply,m*v,1)
+    @test (m*m*v)[3] == (:apply,m*v,3)
+    @test (m*m*v)[6] == (:apply,m*v,6)
+    @test_broken BoundsError == (m*m*v)[0]
+    @test_broken BoundsError == (m*m*v)[7]
+end
+
+@testset "TensorMapping binary operations" begin
+    struct ScalarMapping{T,R,D} <: TensorMapping{T,R,D}
+        λ::T
+    end
+
+    LazyTensors.apply(m::ScalarMapping{T,R,D}, v, i) where {T,R,D} = m.λ*v[i]
+    LazyTensors.range_size(m::ScalarMapping, domain_size) = domain_size
+    LazyTensors.domain_size(m::ScalarMapping, range_sizes) = range_sizes
+
+    A = ScalarMapping{Float64,1,1}(2.0)
+    B = ScalarMapping{Float64,1,1}(3.0)
+
+    v = [1.1,1.2,1.3]
+
+    for i ∈ eachindex(v)
+        @test ((A+B)*v)[i] == 2*v[i] + 3*v[i]
+    end
+
+    for i ∈ eachindex(v)
+        @test ((A-B)*v)[i] == 2*v[i] - 3*v[i]
+    end
+
+    @test range_size(A+B, (3,)) == range_size(A, (3,)) == range_size(B,(3,))
+    @test domain_size(A+B, (3,)) == domain_size(A, (3,)) == domain_size(B,(3,))
+end
+
+@testset "LazyArray" begin
+    struct DummyArray{T,D, T1<:AbstractArray{T,D}} <: LazyArray{T,D}
+        data::T1
+    end
+    Base.size(v::DummyArray) = size(v.data)
+    Base.getindex(v::DummyArray, I...) = v.data[I...]
+
+    # Test lazy operations
+    v1 = [1, 2.3, 4]
+    v2 = [1., 2, 3]
+    r_add = v1 .+ v2
+    r_sub = v1 .- v2
+    r_times = v1 .* v2
+    r_div = v1 ./ v2
+    @test isa(v1 +̃ v2, LazyArray)
+    @test isa(v1 -̃ v2, LazyArray)
+    @test isa(v1 *̃ v2, LazyArray)
+    @test isa(v1 /̃ v2, LazyArray)
+    for i ∈ eachindex(v1)
+        @test (v1 +̃ v2)[i] == r_add[i]
+        @test (v1 -̃ v2)[i] == r_sub[i]
+        @test (v1 *̃ v2)[i] == r_times[i]
+        @test (v1 /̃ v2)[i] == r_div[i]
+    end
+    @test_throws BoundsError (v1 +̃  v2)[4]
+    v2 = [1., 2, 3, 4]
+    # Test that size of arrays is asserted when not specified inbounds
+    @test_throws DimensionMismatch v1 +̃ v2
+
+    # Test operations on LazyArray
+    v1 = DummyArray([1, 2.3, 4])
+    v2 = [1., 2, 3]
+    @test isa(v1 + v2, LazyArray)
+    @test isa(v2 + v1, LazyArray)
+    @test isa(v1 - v2, LazyArray)
+    @test isa(v2 - v1, LazyArray)
+    for i ∈ eachindex(v2)
+        @test (v1 + v2)[i] == (v2 + v1)[i] == r_add[i]
+        @test (v1 - v2)[i] == -(v2 - v1)[i] == r_sub[i]
+    end
+    @test_throws BoundsError (v1 + v2)[4]
+    v2 = [1., 2, 3, 4]
+    # Test that size of arrays is asserted when not specified inbounds
+    @test_throws DimensionMismatch v1 + v2
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,72 @@
+# This file is machine-generated - editing it directly is not advised
+
+[[Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+
+[[DiffOps]]
+deps = ["Grids", "RegionIndices", "SbpOperators", "TiledIteration"]
+path = "DiffOps"
+uuid = "39474f48-97ec-11e9-01fc-6ddcbe5918df"
+version = "0.1.0"
+
+[[Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+
+[[Grids]]
+deps = ["RegionIndices"]
+path = "Grids"
+uuid = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+version = "0.1.0"
+
+[[InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+
+[[LazyTensors]]
+path = "LazyTensors"
+uuid = "62fbed2c-918d-11e9-279b-eb3a325b37d3"
+version = "0.1.0"
+
+[[Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+
+[[Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+
+[[OffsetArrays]]
+git-tree-sha1 = "1af2f79c7eaac3e019a0de41ef63335ff26a0a57"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "0.11.1"
+
+[[Random]]
+deps = ["Serialization"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+
+[[RegionIndices]]
+path = "RegionIndices"
+uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
+version = "0.1.0"
+
+[[SbpOperators]]
+deps = ["RegionIndices"]
+path = "SbpOperators"
+uuid = "204357d8-97fd-11e9-05e7-010897a14cd0"
+version = "0.1.0"
+
+[[Serialization]]
+uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
+
+[[Sockets]]
+uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
+
+[[Test]]
+deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[TiledIteration]]
+deps = ["OffsetArrays", "Test"]
+git-tree-sha1 = "58f6f07d3b54a363ec283a8f5fc9fb4ecebde656"
+uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
+version = "0.2.3"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,6 @@
+[deps]
+DiffOps = "39474f48-97ec-11e9-01fc-6ddcbe5918df"
+Grids = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+LazyTensors = "62fbed2c-918d-11e9-279b-eb3a325b37d3"
+RegionIndices = "5d527584-97f1-11e9-084c-4540c7ecf219"
+SbpOperators = "204357d8-97fd-11e9-05e7-010897a14cd0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RegionIndices/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,10 @@
+name = "RegionIndices"
+uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
+authors = ["Jonatan Werpers <jonatan.werpers@it.uu.se>"]
+version = "0.1.0"
+
+[extras]
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RegionIndices/src/RegionIndices.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,81 @@
+module RegionIndices
+
+abstract type Region end
+struct Interior <: Region end
+struct Lower    <: Region end
+struct Upper    <: Region end
+struct Unknown  <: Region end
+
+export Region, Interior, Lower, Upper, Unknown
+
+struct Index{R<:Region, T<:Integer}
+    i::T
+
+    Index{R,T}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i)
+    Index{R}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i)
+    Index(i::T, ::Type{R}) where {R<:Region,T<:Integer} = Index{R,T}(i)
+    Index(t::Tuple{T, DataType}) where {R<:Region,T<:Integer} = Index{t[2],T}(t[1]) # TBD: This is not very specific in what types are allowed in t[2]. Can this be fixed?
+end
+
+export Index
+
+# Index(R::Type{<:Region}) = Index{R}
+
+## Vill kunna skriva
+## IndexTupleType(Int, (Lower, Interior))
+Index(R::Type{<:Region}, T::Type{<:Integer}) = Index{R,T}
+IndexTupleType(T::Type{<:Integer},R::NTuple{N, DataType} where N) = Tuple{Index.(R, T)...}
+
+Base.convert(::Type{T}, i::Index{R,T} where R) where T = i.i
+Base.convert(::Type{CartesianIndex}, I::NTuple{N,Index} where N) = CartesianIndex(convert.(Int, I))
+
+Base.Int(I::Index) = I.i
+
+function Index(i::Integer, boundary_width::Integer, dim_size::Integer)
+    return Index{getregion(i,boundary_width,dim_size)}(i)
+end
+
+IndexTuple(t::Vararg{Tuple{T, DataType}}) where T<:Integer = Index.(t)
+export IndexTuple
+
+# TODO: Use the values of the region structs, e.g. Lower(), for the region parameter instead of the types.
+# For example the following works:
+#   (Lower(),Upper()) isa NTuple{2, Region} -> true
+#   typeof((Lower(),Upper()))               -> Tuple{Lower,Upper}
+function regionindices(gridsize::NTuple{Dim,Integer}, closuresize::Integer, region::NTuple{Dim,DataType}) where Dim
+    return regionindices(gridsize, ntuple(x->closuresize,Dim), region)
+end
+
+function regionindices(gridsize::NTuple{Dim,Integer}, closuresize::NTuple{Dim,Integer}, region::NTuple{Dim,DataType}) where Dim
+    regions = map(getrange,gridsize,closuresize,region)
+    return CartesianIndices(regions)
+end
+
+export regionindices
+
+function getregion(i::Integer, boundary_width::Integer, dim_size::Integer)
+	if 0 < i <= boundary_width
+        return Lower
+    elseif boundary_width < i <= dim_size-boundary_width
+        return Interior
+    elseif dim_size-boundary_width < i <= dim_size
+        return Upper
+    else
+        error("Bounds error") # TODO: Make this more standard
+    end
+end
+
+function getrange(gridsize::Integer, closuresize::Integer, region::DataType)
+    if region == Lower
+        r = 1:closuresize
+    elseif region == Interior
+        r = (closuresize+1):(gridsize - closuresize)
+    elseif region == Upper
+        r = (gridsize - closuresize + 1):gridsize
+    else
+        error("Unspecified region")
+    end
+    return r
+end
+
+end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RegionIndices/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,4 @@
+using RegionIndices
+using Test
+
+@test_broken false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,6 @@
+# This file is machine-generated - editing it directly is not advised
+
+[[RegionIndices]]
+path = "../RegionIndices"
+uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
+version = "0.1.0"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/Project.toml	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,13 @@
+name = "SbpOperators"
+uuid = "204357d8-97fd-11e9-05e7-010897a14cd0"
+authors = ["Jonatan Werpers <jonatan.werpers@it.uu.se>"]
+version = "0.1.0"
+
+[deps]
+RegionIndices = "5d527584-97f1-11e9-084c-4540c7ecf219"
+
+[extras]
+Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[targets]
+test = ["Test"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/operators/d2_2nd.txt	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,13 @@
+# D2 order 2
+
+boundary_stencils
+1 -2 1
+
+inner_stencil
+1 -2 1
+
+e
+1
+
+d1
+-3/2 2 -1/2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/operators/d2_4th.txt	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,16 @@
+# D2 order 4
+
+boundary_stencils
+     2    -5       4      -1     0     0
+     1    -2       1       0     0     0
+ -4/43 59/43 -110/43   59/43 -4/43     0
+ -1/49     0   59/49 -118/49 64/49 -4/49
+
+inner_stencil
+-1/12     4/3  -5/2   4/3 -1/12
+
+e
+1
+
+d1
+-11/6 3 -3/2 1/3
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/operators/h_2nd.txt	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,7 @@
+# H order 2
+
+closure
+1/2
+
+inner_stencil
+1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/operators/h_4th.txt	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,7 @@
+# H order 4
+
+closure
+17/48 59/48 43/48 49/48
+
+inner_stencil
+1
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/SbpOperators.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,163 @@
+module SbpOperators
+
+using RegionIndices
+
+include("stencil.jl")
+
+export D2, closureSize, apply, readOperator, apply_e, apply_d
+
+abstract type ConstantStencilOperator end
+
+# Apply for different regions Lower/Interior/Upper or Unknown region
+@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Lower})
+    return @inbounds h*h*apply(op.closureStencils[Int(i)], v, Int(i))
+end
+
+@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Interior})
+    return @inbounds h*h*apply(op.innerStencil, v, Int(i))
+end
+
+@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Upper})
+    N = length(v)
+    return @inbounds h*h*Int(op.parity)*apply_backwards(op.closureStencils[N-Int(i)+1], v, Int(i))
+end
+
+@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, index::Index{Unknown})
+    cSize = closureSize(op)
+    N = length(v)
+
+    i = Int(index)
+
+    if 0 < i <= cSize
+        return apply(op, h, v, Index{Lower}(i))
+    elseif cSize < i <= N-cSize
+        return apply(op, h, v, Index{Interior}(i))
+    elseif N-cSize < i <= N
+        return apply(op, h, v, Index{Upper}(i))
+    else
+        error("Bounds error") # TODO: Make this more standard
+    end
+end
+
+
+# Wrapper functions for using regular indecies without specifying regions
+@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Int)
+    return apply(op, h, v, Index{Unknown}(i))
+end
+
+@enum Parity begin
+    odd = -1
+    even = 1
+end
+
+struct D2{T,N,M,K} <: ConstantStencilOperator
+    quadratureClosure::NTuple{M,T}
+    innerStencil::Stencil{T,N}
+    closureStencils::NTuple{M,Stencil{T,K}}
+    eClosure::Stencil{T,M}
+    dClosure::Stencil{T,M}
+    parity::Parity
+end
+
+function closureSize(D::D2)::Int
+    return length(D.quadratureClosure)
+end
+
+function readOperator(D2fn, Hfn)
+    d = readSectionedFile(D2fn)
+    h = readSectionedFile(Hfn)
+
+    # Create inner stencil
+    innerStencilWeights = stringToTuple(Float64, d["inner_stencil"][1])
+    width = length(innerStencilWeights)
+    r = (-div(width,2), div(width,2))
+
+    innerStencil = Stencil(r, innerStencilWeights)
+
+    # Create boundary stencils
+    boundarySize = length(d["boundary_stencils"])
+    closureStencils = Vector{typeof(innerStencil)}() # TBD: is the the right way to get the correct type?
+
+    for i ∈ 1:boundarySize
+        stencilWeights = stringToTuple(Float64, d["boundary_stencils"][i])
+        width = length(stencilWeights)
+        r = (1-i,width-i)
+        closureStencils = (closureStencils..., Stencil(r, stencilWeights))
+    end
+
+    quadratureClosure = pad_tuple(stringToTuple(Float64, h["closure"][1]), boundarySize)
+    eClosure = Stencil((0,boundarySize-1), pad_tuple(stringToTuple(Float64, d["e"][1]), boundarySize))
+    dClosure = Stencil((0,boundarySize-1), pad_tuple(stringToTuple(Float64, d["d1"][1]), boundarySize))
+
+    d2 = D2(
+        quadratureClosure,
+        innerStencil,
+        closureStencils,
+        eClosure,
+        dClosure,
+        even
+    )
+
+    return d2
+end
+
+
+function apply_e(op::D2, v::AbstractVector, ::Type{Lower})
+    apply(op.eClosure,v,1)
+end
+
+function apply_e(op::D2, v::AbstractVector, ::Type{Upper})
+    apply(flip(op.eClosure),v,length(v))
+end
+
+
+function apply_d(op::D2, h_inv::Real, v::AbstractVector, ::Type{Lower})
+    -h_inv*apply(op.dClosure,v,1)
+end
+
+function apply_d(op::D2, h_inv::Real, v::AbstractVector, ::Type{Upper})
+    -h_inv*apply(flip(op.dClosure),v,length(v))
+end
+
+function readSectionedFile(filename)::Dict{String, Vector{String}}
+    f = open(filename)
+    sections = Dict{String, Vector{String}}()
+    currentKey = ""
+
+    for ln ∈ eachline(f)
+        if ln == "" || ln[1] == '#' # Skip comments and empty lines
+            continue
+        end
+
+        if isletter(ln[1]) # Found start of new section
+            if ~haskey(sections, ln)
+                sections[ln] =  Vector{String}()
+            end
+            currentKey = ln
+            continue
+        end
+
+        push!(sections[currentKey], ln)
+    end
+
+    return sections
+end
+
+function stringToTuple(T::DataType, s::String)
+    return Tuple(stringToVector(T,s))
+end
+
+function stringToVector(T::DataType, s::String)
+    return T.(eval.(Meta.parse.(split(s))))
+end
+
+
+function pad_tuple(t::NTuple{N, T}, n::Integer) where {N,T}
+    if N >= n
+        return t
+    else
+        return pad_tuple((t..., zero(T)), n)
+    end
+end
+
+end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/stencil.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,38 @@
+struct Stencil{T<:Real,N}
+    range::Tuple{Int,Int}
+    weights::NTuple{N,T}
+
+    function Stencil(range::Tuple{Int,Int},weights::NTuple{N,T}) where {T <: Real, N}
+        @assert range[2]-range[1]+1 == N
+        new{T,N}(range,weights)
+    end
+end
+
+function flip(s::Stencil)
+    range = (-s.range[2], -s.range[1])
+    return Stencil(range, reverse(s.weights))
+end
+
+# Provides index into the Stencil based on offset for the root element
+@inline function Base.getindex(s::Stencil, i::Int)
+    @boundscheck if i < s.range[1] || s.range[2] < i
+        return eltype(s.weights)(0)
+    end
+    return s.weights[1 + i - s.range[1]]
+end
+
+Base.@propagate_inbounds @inline function apply(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N}
+    w = s.weights[1]*v[i + s.range[1]]
+    @simd for k ∈ 2:N
+        w += s.weights[k]*v[i + s.range[1] + k-1]
+    end
+    return w
+end
+
+Base.@propagate_inbounds @inline function apply_backwards(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N}
+    w = s.weights[N]*v[i - s.range[2]]
+    @simd for k ∈ N-1:-1:1
+        w += s.weights[k]*v[i - s.range[1] - k + 1]
+    end
+    return w
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -0,0 +1,4 @@
+using SbpOperators
+using Test
+
+@test_broken false
--- a/TODO.txt	Fri Feb 22 15:22:34 2019 +0100
+++ b/TODO.txt	Wed Jun 26 15:07:47 2019 +0200
@@ -10,3 +10,5 @@
 
 Konvertera till paket
 Skriv tester
+
+Specificera operatorer i TOML eller något liknande?
--- a/d2_2nd.txt	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,13 +0,0 @@
-# D2 order 2
-
-boundary_stencils
-1 -2 1
-
-inner_stencil
-1 -2 1
-
-e
-1
-
-d1
--3/2 2 -1/2
--- a/d2_4th.txt	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,16 +0,0 @@
-# D2 order 4
-
-boundary_stencils
-     2    -5       4      -1     0     0
-     1    -2       1       0     0     0
- -4/43 59/43 -110/43   59/43 -4/43     0
- -1/49     0   59/49 -118/49 64/49 -4/49
-
-inner_stencil
--1/12     4/3  -5/2   4/3 -1/12
-
-e
-1
-
-d1
--11/6 3 -3/2 1/3
\ No newline at end of file
--- a/diffOp.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-abstract type DiffOp end
-
-# TBD: The "error("not implemented")" thing seems to be hiding good error information. How to fix that? Different way of saying that these should be implemented?
-function apply(D::DiffOp, v::AbstractVector, i::Int)
-    error("not implemented")
-end
-
-function innerProduct(D::DiffOp, u::AbstractVector, v::AbstractVector)::Real
-    error("not implemented")
-end
-
-function matrixRepresentation(D::DiffOp)
-    error("not implemented")
-end
-
-function boundaryCondition(D::DiffOp,b::Grid.BoundaryId,type)::(Closure, Penalty)
-    error("not implemented")
-end
-
-function interface(Du::DiffOp, Dv::DiffOp, b::Grid.BoundaryId; type)
-    error("not implemented")
-end
-
-abstract type Closure end
-
-function apply(c::Closure, v::AbstractVector, i::Int)
-    error("not implemented")
-end
-
-abstract type Penalty end
-
-function apply(c::Penalty, g, i::Int)
-    error("not implemented")
-end
-
-abstract type DiffOpCartesian{Dim} <: DiffOp end
-
-# DiffOp must have a grid of dimension Dim!!!
-function apply!(D::DiffOpCartesian{Dim}, u::AbstractArray{T,Dim}, v::AbstractArray{T,Dim}) where {T,Dim}
-    for I ∈ eachindex(D.grid)
-        u[I] = apply(D, v, I)
-    end
-
-    return nothing
-end
-
-function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T
-    apply_region!(D, u, v, Lower, Lower)
-    apply_region!(D, u, v, Lower, Interior)
-    apply_region!(D, u, v, Lower, Upper)
-    apply_region!(D, u, v, Interior, Lower)
-    apply_region!(D, u, v, Interior, Interior)
-    apply_region!(D, u, v, Interior, Upper)
-    apply_region!(D, u, v, Upper, Lower)
-    apply_region!(D, u, v, Upper, Interior)
-    apply_region!(D, u, v, Upper, Upper)
-    return nothing
-end
-
-# Maybe this should be split according to b3fbef345810 after all?! Seems like it makes performance more predictable
-function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T
-    for I ∈ regionindices(D.grid.size, closureSize(D.op), (r1,r2))
-        @inbounds indextuple = (Index{r1}(I[1]), Index{r2}(I[2]))
-        @inbounds u[I] = apply(D, v, indextuple)
-    end
-    return nothing
-end
-
-function apply_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T
-    apply_region_tiled!(D, u, v, Lower, Lower)
-    apply_region_tiled!(D, u, v, Lower, Interior)
-    apply_region_tiled!(D, u, v, Lower, Upper)
-    apply_region_tiled!(D, u, v, Interior, Lower)
-    apply_region_tiled!(D, u, v, Interior, Interior)
-    apply_region_tiled!(D, u, v, Interior, Upper)
-    apply_region_tiled!(D, u, v, Upper, Lower)
-    apply_region_tiled!(D, u, v, Upper, Interior)
-    apply_region_tiled!(D, u, v, Upper, Upper)
-    return nothing
-end
-
-using TiledIteration
-function apply_region_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T
-    ri = regionindices(D.grid.size, closureSize(D.op), (r1,r2))
-    # TODO: Pass Tilesize to function
-    for tileaxs ∈ TileIterator(axes(ri), padded_tilesize(T, (5,5), 2))
-        for j ∈ tileaxs[2], i ∈ tileaxs[1]
-            I = ri[i,j]
-            u[I] = apply(D, v, (Index{r1}(I[1]), Index{r2}(I[2])))
-        end
-    end
-    return nothing
-end
-
-function apply(D::DiffOp, v::AbstractVector)::AbstractVector
-    u = zeros(eltype(v), size(v))
-    apply!(D,v,u)
-    return u
-end
-
-struct Laplace{Dim,T<:Real,N,M,K} <: DiffOpCartesian{Dim}
-    grid::Grid.EquidistantGrid{Dim,T}
-    a::T
-    op::D2{Float64,N,M,K}
-end
-
-function apply(L::Laplace{Dim}, v::AbstractArray{T,Dim} where T, I::CartesianIndex{Dim}) where Dim
-    error("not implemented")
-end
-
-# u = L*v
-function apply(L::Laplace{1}, v::AbstractVector, i::Int)
-    uᵢ = L.a * apply(L.op, L.grid.spacing[1], v, i)
-    return uᵢ
-end
-
-@inline function apply(L::Laplace{2}, v::AbstractArray{T,2} where T, I::Tuple{Index{R1}, Index{R2}}) where {R1, R2}
-    # 2nd x-derivative
-    @inbounds vx = view(v, :, Int(I[2]))
-    @inbounds uᵢ = L.a*apply(L.op, L.grid.inverse_spacing[1], vx , I[1])
-    # 2nd y-derivative
-    @inbounds vy = view(v, Int(I[1]), :)
-    @inbounds uᵢ += L.a*apply(L.op, L.grid.inverse_spacing[2], vy, I[2])
-    return uᵢ
-end
-
-# Slow but maybe convenient?
-function apply(L::Laplace{2}, v::AbstractArray{T,2} where T, i::CartesianIndex{2})
-    I = Index{Unknown}.(Tuple(i))
-    apply(L, v, I)
-end
--- a/grid.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-module Grid
-
-# TODO: Where is this used?
-abstract type BoundaryId end
-
-include("AbstractGrid.jl")
-include("EquidistantGrid.jl")
-
-end
--- a/h_2nd.txt	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-# H order 2
-
-closure
-1/2
-
-inner_stencil
-1
--- a/h_4th.txt	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-# H order 4
-
-closure
-17/48 59/48 43/48 49/48
-
-inner_stencil
-1
\ No newline at end of file
--- a/index.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-abstract type Region end
-struct Interior <: Region end
-struct Lower    <: Region end
-struct Upper    <: Region end
-struct Unknown  <: Region end
-
-struct Index{R<:Region, T<:Integer}
-    i::T
-
-    Index{R,T}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i)
-    Index{R}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i)
-    Index(i::T, ::Type{R}) where {R<:Region,T<:Integer} = Index{R,T}(i)
-    Index(t::Tuple{T, DataType}) where {R<:Region,T<:Integer} = Index{t[2],T}(t[1]) # TBD: This is not very specific in what types are allowed in t[2]. Can this be fixed?
-end
-
-# Index(R::Type{<:Region}) = Index{R}
-
-## Vill kunna skriva
-## IndexTupleType(Int, (Lower, Interior))
-Index(R::Type{<:Region}, T::Type{<:Integer}) = Index{R,T}
-IndexTupleType(T::Type{<:Integer},R::NTuple{N, DataType} where N) = Tuple{Index.(R, T)...}
-
-Base.convert(::Type{T}, i::Index{R,T} where R) where T = i.i
-Base.convert(::Type{CartesianIndex}, I::NTuple{N,Index} where N) = CartesianIndex(convert.(Int, I))
-
-Base.Int(I::Index) = I.i
-
-function Index(i::Integer, boundary_width::Integer, dim_size::Integer)
-    if 0 < i <= boundary_width
-        return Index{Lower}(i)
-    elseif boundary_width < i <= dim_size-boundary_width
-        return Index{Interior}(i)
-    elseif dim_size-boundary_width < i <= dim_size
-        return Index{Upper}(i)
-    else
-        error("Bounds error") # TODO: Make this more standard
-    end
-end
-
-IndexTuple(t::Vararg{Tuple{T, DataType}}) where T<:Integer = Index.(t)
-
-# TODO: Use the values of the region structs, e.g. Lower(), for the region parameter instead of the types.
-# For example the following works:
-#   (Lower(),Upper()) isa NTuple{2, Region} -> true
-#   typeof((Lower(),Upper()))               -> Tuple{Lower,Upper}
-function regionindices(gridsize::NTuple{Dim,Integer}, closuresize::Integer, region::NTuple{Dim,DataType}) where Dim
-    return regionindices(gridsize, ntuple(x->closuresize,Dim), region)
-end
-
-function regionindices(gridsize::NTuple{Dim,Integer}, closuresize::NTuple{Dim,Integer}, region::NTuple{Dim,DataType}) where Dim
-    regions = map(getrange,gridsize,closuresize,region)
-    return CartesianIndices(regions)
-end
-
-function getrange(gridsize::Integer, closuresize::Integer, region::DataType)
-    if region == Lower
-        r = 1:closuresize
-    elseif region == Interior
-        r = (closuresize+1):(gridsize - closuresize)
-    elseif region == Upper
-        r = (gridsize - closuresize + 1):gridsize
-    else
-        error("Unspecified region")
-    end
-    return r
-end
--- a/sbp.jl	Fri Feb 22 15:22:34 2019 +0100
+++ b/sbp.jl	Wed Jun 26 15:07:47 2019 +0200
@@ -1,8 +1,9 @@
 module sbp
-include("index.jl")
-include("grid.jl")
-include("stencil.jl")
-include("sbpD2.jl")
-include("diffOp.jl")
+
+using Grids
+using RegionIndices
+using SbpOperators
+using DiffOps
+
 include("TimeStepper.jl")
 end  # module
--- a/sbpD2.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-abstract type ConstantStencilOperator end
-
-# Apply for different regions Lower/Interior/Upper or Unknown region
-@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Lower})
-    return @inbounds h*h*apply(op.closureStencils[Int(i)], v, Int(i))
-end
-
-@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Interior})
-    return @inbounds h*h*apply(op.innerStencil, v, Int(i))
-end
-
-@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Index{Upper})
-    N = length(v)
-    return @inbounds h*h*Int(op.parity)*apply_backwards(op.closureStencils[N-Int(i)+1], v, Int(i))
-end
-
-@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, index::Index{Unknown})
-    cSize = closureSize(op)
-    N = length(v)
-
-    i = Int(index)
-
-    if 0 < i <= cSize
-        return apply(op, h, v, Index{Lower}(i))
-    elseif cSize < i <= N-cSize
-        return apply(op, h, v, Index{Interior}(i))
-    elseif N-cSize < i <= N
-        return apply(op, h, v, Index{Upper}(i))
-    else
-        error("Bounds error") # TODO: Make this more standard
-    end
-end
-
-
-# Wrapper functions for using regular indecies without specifying regions
-@inline function apply(op::ConstantStencilOperator, h::Real, v::AbstractVector, i::Int)
-    return apply(op, h, v, Index{Unknown}(i))
-end
-
-@enum Parity begin
-    odd = -1
-    even = 1
-end
-
-struct D2{T,N,M,K} <: ConstantStencilOperator
-    quadratureClosure::NTuple{M,T}
-    innerStencil::Stencil{T,N}
-    closureStencils::NTuple{M,Stencil{T,K}}
-    eClosure::Stencil{T,M}
-    dClosure::Stencil{T,M}
-    parity::Parity
-end
-
-function closureSize(D::D2)::Int
-    return length(D.quadratureClosure)
-end
-
-function readOperator(D2fn, Hfn)
-    d = readSectionedFile(D2fn)
-    h = readSectionedFile(Hfn)
-
-    # Create inner stencil
-    innerStencilWeights = stringToTuple(Float64, d["inner_stencil"][1])
-    width = length(innerStencilWeights)
-    r = (-div(width,2), div(width,2))
-
-    innerStencil = Stencil(r, innerStencilWeights)
-
-    # Create boundary stencils
-    boundarySize = length(d["boundary_stencils"])
-    closureStencils = Vector{typeof(innerStencil)}() # TBD: is the the right way to get the correct type?
-
-    for i ∈ 1:boundarySize
-        stencilWeights = stringToTuple(Float64, d["boundary_stencils"][i])
-        width = length(stencilWeights)
-        r = (1-i,width-i)
-        closureStencils = (closureStencils..., Stencil(r, stencilWeights))
-    end
-
-    quadratureClosure = pad_tuple(stringToTuple(Float64, h["closure"][1]), boundarySize)
-    eClosure = Stencil((0,boundarySize-1), pad_tuple(stringToTuple(Float64, d["e"][1]), boundarySize))
-    dClosure = Stencil((0,boundarySize-1), pad_tuple(stringToTuple(Float64, d["d1"][1]), boundarySize))
-
-    d2 = D2(
-        quadratureClosure,
-        innerStencil,
-        closureStencils,
-        eClosure,
-        dClosure,
-        even
-    )
-
-    return d2
-end
-
-
-function apply_e(op::D2, v::AbstractVector, ::Type{Lower})
-    apply(op.eClosure,v,1)
-end
-
-function apply_e(op::D2, v::AbstractVector, ::Type{Upper})
-    apply(flip(op.eClosure),v,length(v))
-end
-
-
-function apply_d(op::D2, h::Real, v::AbstractVector, ::Type{Lower})
-    -apply(op.dClosure,v,1)/h
-end
-
-function apply_d(op::D2, h::Real, v::AbstractVector, ::Type{Upper})
-    -apply(flip(op.dClosure),v,length(v))/h
-end
-
-function readSectionedFile(filename)::Dict{String, Vector{String}}
-    f = open(filename)
-    sections = Dict{String, Vector{String}}()
-    currentKey = ""
-
-    for ln ∈ eachline(f)
-        if ln == "" || ln[1] == '#' # Skip comments and empty lines
-            continue
-        end
-
-        if isletter(ln[1]) # Found start of new section
-            if ~haskey(sections, ln)
-                sections[ln] =  Vector{String}()
-            end
-            currentKey = ln
-            continue
-        end
-
-        push!(sections[currentKey], ln)
-    end
-
-    return sections
-end
-
-function stringToTuple(T::DataType, s::String)
-    return Tuple(stringToVector(T,s))
-end
-
-function stringToVector(T::DataType, s::String)
-    return T.(eval.(Meta.parse.(split(s))))
-end
-
-
-function pad_tuple(t::NTuple{N, T}, n::Integer) where {N,T}
-    if N >= n
-        return t
-    else
-        return pad_tuple((t..., zero(T)), n)
-    end
-end
--- a/stencil.jl	Fri Feb 22 15:22:34 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-struct Stencil{T<:Real,N}
-    range::Tuple{Int,Int}
-    weights::NTuple{N,T}
-
-    function Stencil(range::Tuple{Int,Int},weights::NTuple{N,T}) where {T <: Real, N}
-        @assert range[2]-range[1]+1 == N
-        new{T,N}(range,weights)
-    end
-end
-
-function flip(s::Stencil)
-    range = (-s.range[2], -s.range[1])
-    return Stencil(range, reverse(s.weights))
-end
-
-# Provides index into the Stencil based on offset for the root element
-@inline function Base.getindex(s::Stencil, i::Int)
-    @boundscheck if i < s.range[1] || s.range[2] < i
-        return eltype(s.weights)(0)
-    end
-    return s.weights[1 + i - s.range[1]]
-end
-
-Base.@propagate_inbounds @inline function apply(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N}
-    w = s.weights[1]*v[i + s.range[1]]
-    @simd for k ∈ 2:N
-        w += s.weights[k]*v[i + s.range[1] + k-1]
-    end
-    return w
-end
-
-Base.@propagate_inbounds @inline function apply_backwards(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N}
-    w = s.weights[N]*v[i - s.range[2]]
-    @simd for k ∈ N-1:-1:1
-        w += s.weights[k]*v[i - s.range[1] - k + 1]
-    end
-    return w
-end