changeset 291:0f94dc29c4bf

Merge in branch boundary_conditions
author Jonatan Werpers <jonatan@werpers.com>
date Mon, 22 Jun 2020 21:43:05 +0200
parents fbabfd4e8f20 (current diff) 22d7bd87c20e (diff)
children 3747e5636eef 9c12d9eb38fd
files
diffstat 25 files changed, 1160 insertions(+), 496 deletions(-) [+]
line wrap: on
line diff
--- a/DiffOps/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
+++ b/DiffOps/Manifest.toml	Mon Jun 22 21:43:05 2020 +0200
@@ -17,6 +17,11 @@
 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"
 
--- a/DiffOps/Project.toml	Wed Jun 26 15:07:47 2019 +0200
+++ b/DiffOps/Project.toml	Mon Jun 22 21:43:05 2020 +0200
@@ -5,6 +5,7 @@
 
 [deps]
 Grids = "960fdf28-97ed-11e9-2a74-bd90bf2fab5a"
+LazyTensors = "62fbed2c-918d-11e9-279b-eb3a325b37d3"
 RegionIndices = "5d527584-97f1-11e9-084c-4540c7ecf219"
 SbpOperators = "204357d8-97fd-11e9-05e7-010897a14cd0"
 TiledIteration = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
--- a/DiffOps/src/DiffOps.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/DiffOps/src/DiffOps.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -3,6 +3,7 @@
 using RegionIndices
 using SbpOperators
 using Grids
+using LazyTensors
 
 """
     DiffOp
@@ -46,7 +47,7 @@
 
 # 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))
+    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
@@ -69,7 +70,7 @@
 
 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))
+    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]
@@ -97,7 +98,5 @@
 
 
 include("laplace.jl")
-export Laplace
-
 
 end # module
--- a/DiffOps/src/laplace.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/DiffOps/src/laplace.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -1,111 +1,205 @@
-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
+#TODO: move to sbpoperators.jl
+"""
+    Laplace{Dim,T<:Real,N,M,K} <: TensorOperator{T,Dim}
 
-struct BoundaryValue{N,M,K}
-	op::D2{Float64,N,M,K}
-	grid::EquidistantGrid
-	bId::CartesianBoundary
+Implements the Laplace operator `L` in Dim dimensions as a tensor operator
+The multi-dimensional tensor operator simply consists of a tuple of the 1D
+Laplace tensor operator as defined by ConstantLaplaceOperator.
+"""
+struct Laplace{Dim,T<:Real,N,M,K} <: TensorOperator{T,Dim}
+    tensorOps::NTuple(Dim,ConstantLaplaceOperator{T,N,M,K})
+    #TODO: Write a good constructor
 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
+export Laplace
 
-	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
+LazyTensors.domain_size(H::Laplace{Dim}, range_size::NTuple{Dim,Integer}) = range_size
 
-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
+function LazyTensors.apply(L::Laplace{Dim,T}, v::AbstractArray{T,Dim}, I::NTuple{Dim,Index}) where {T,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)
+function LazyTensors.apply(L::Laplace{1,T}, v::AbstractVector{T}, I::NTuple{1,Index}) where T
+    return apply(L.tensorOps[1],v,I)
+end
+
+
+@inline function LazyTensors.apply(L::Laplace{2,T}, v::AbstractArray{T,2}, I::NTuple{2,Index}) where T
+    # 2nd x-derivative
+    @inbounds vx = view(v, :, Int(I[2]))
+    @inbounds uᵢ = apply(L.tensorOps[1], vx , (I[1],)) #Tuple conversion here is ugly. How to do it? Should we use indexing here?
+
+    # 2nd y-derivative
+    @inbounds vy = view(v, Int(I[1]), :)
+    @inbounds uᵢ += apply(L.tensorOps[2], vy , (I[2],)) #Tuple conversion here is ugly. How to do it?
+
     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ᵢ
+quadrature(L::Laplace) = Quadrature(L.op, L.grid)
+inverse_quadrature(L::Laplace) = InverseQuadrature(L.op, L.grid)
+boundary_value(L::Laplace, bId::CartesianBoundary) = BoundaryValue(L.op, L.grid, bId)
+normal_derivative(L::Laplace, bId::CartesianBoundary) = NormalDerivative(L.op, L.grid, bId)
+boundary_quadrature(L::Laplace, bId::CartesianBoundary) = BoundaryQuadrature(L.op, L.grid, bId)
+export quadrature
+
+# At the moment the grid property is used all over. It could possibly be removed if we implement all the 1D operators as TensorMappings
+"""
+    Quadrature{Dim,T<:Real,N,M,K} <: TensorMapping{T,Dim,Dim}
+
+Implements the quadrature operator `H` of Dim dimension as a TensorMapping
+"""
+struct Quadrature{Dim,T<:Real,N,M,K} <: TensorOperator{T,Dim}
+    op::D2{T,N,M,K}
+    grid::EquidistantGrid{Dim,T}
+end
+export Quadrature
+
+LazyTensors.domain_size(H::Quadrature{Dim}, range_size::NTuple{Dim,Integer}) where Dim = range_size
+
+@inline function LazyTensors.apply(H::Quadrature{2,T}, v::AbstractArray{T,2}, I::NTuple{2,Index}) where T
+    N = size(H.grid)
+    # Quadrature in x direction
+    @inbounds q = apply_quadrature(H.op, spacing(H.grid)[1], v[I] , I[1], N[1])
+    # Quadrature in y-direction
+    @inbounds q = apply_quadrature(H.op, spacing(H.grid)[2], q, I[2], N[2])
+    return q
+end
+
+LazyTensors.apply_transpose(H::Quadrature{2,T}, v::AbstractArray{T,2}, I::NTuple{2,Index}) where T = LazyTensors.apply(H,v,I)
+
+
+"""
+    InverseQuadrature{Dim,T<:Real,N,M,K} <: TensorMapping{T,Dim,Dim}
+
+Implements the inverse quadrature operator `inv(H)` of Dim dimension as a TensorMapping
+"""
+struct InverseQuadrature{Dim,T<:Real,N,M,K} <: TensorOperator{T,Dim}
+    op::D2{T,N,M,K}
+    grid::EquidistantGrid{Dim,T}
+end
+export InverseQuadrature
+
+LazyTensors.domain_size(H_inv::InverseQuadrature{Dim}, range_size::NTuple{Dim,Integer}) where Dim = range_size
+
+@inline function LazyTensors.apply(H_inv::InverseQuadrature{2,T}, v::AbstractArray{T,2}, I::NTuple{2,Index}) where T
+    N = size(H_inv.grid)
+    # Inverse quadrature in x direction
+    @inbounds q_inv = apply_inverse_quadrature(H_inv.op, inverse_spacing(H_inv.grid)[1], v[I] , I[1], N[1])
+    # Inverse quadrature in y-direction
+    @inbounds q_inv = apply_inverse_quadrature(H_inv.op, inverse_spacing(H_inv.grid)[2], q_inv, I[2], N[2])
+    return q_inv
 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)
+LazyTensors.apply_transpose(H_inv::InverseQuadrature{2,T}, v::AbstractArray{T,2}, I::NTuple{2,Index}) where T = LazyTensors.apply(H_inv,v,I)
+
+"""
+    BoundaryValue{T,N,M,K} <: TensorMapping{T,2,1}
+
+Implements the boundary operator `e` as a TensorMapping
+"""
+struct BoundaryValue{T,N,M,K} <: TensorMapping{T,2,1}
+    op::D2{T,N,M,K}
+    grid::EquidistantGrid{2}
+    bId::CartesianBoundary
+end
+export BoundaryValue
+
+# TODO: This is obviouly strange. Is domain_size just discarded? Is there a way to avoid storing grid in BoundaryValue?
+# Can we give special treatment to TensorMappings that go to a higher dim?
+function LazyTensors.range_size(e::BoundaryValue{T}, domain_size::NTuple{1,Integer}) where T
+    if dim(e.bId) == 1
+        return (UnknownDim, domain_size[1])
+    elseif dim(e.bId) == 2
+        return (domain_size[1], UnknownDim)
+    end
+end
+LazyTensors.domain_size(e::BoundaryValue{T}, range_size::NTuple{2,Integer}) where T = (range_size[3-dim(e.bId)],)
+# TODO: Make a nicer solution for 3-dim(e.bId)
+
+# TODO: Make this independent of dimension
+function LazyTensors.apply(e::BoundaryValue{T}, v::AbstractArray{T}, I::NTuple{2,Index}) where T
+    i = I[dim(e.bId)]
+    j = I[3-dim(e.bId)]
+    N_i = size(e.grid)[dim(e.bId)]
+    return apply_boundary_value(e.op, v[j], i, N_i, region(e.bId))
+end
+
+function LazyTensors.apply_transpose(e::BoundaryValue{T}, v::AbstractArray{T}, I::NTuple{1,Index}) where T
+    u = selectdim(v,3-dim(e.bId),Int(I[1]))
+    return apply_boundary_value_transpose(e.op, u, region(e.bId))
+end
+
+"""
+    NormalDerivative{T,N,M,K} <: TensorMapping{T,2,1}
+
+Implements the boundary operator `d` as a TensorMapping
+"""
+struct NormalDerivative{T,N,M,K} <: TensorMapping{T,2,1}
+    op::D2{T,N,M,K}
+    grid::EquidistantGrid{2}
+    bId::CartesianBoundary
 end
+export NormalDerivative
+
+# TODO: This is obviouly strange. Is domain_size just discarded? Is there a way to avoid storing grid in BoundaryValue?
+# Can we give special treatment to TensorMappings that go to a higher dim?
+function LazyTensors.range_size(e::NormalDerivative, domain_size::NTuple{1,Integer})
+    if dim(e.bId) == 1
+        return (UnknownDim, domain_size[1])
+    elseif dim(e.bId) == 2
+        return (domain_size[1], UnknownDim)
+    end
+end
+LazyTensors.domain_size(e::NormalDerivative, range_size::NTuple{2,Integer}) = (range_size[3-dim(e.bId)],)
+
+# TODO: Not type stable D:<
+# TODO: Make this independent of dimension
+function LazyTensors.apply(d::NormalDerivative{T}, v::AbstractArray{T}, I::NTuple{2,Index}) where T
+    i = I[dim(d.bId)]
+    j = I[3-dim(d.bId)]
+    N_i = size(d.grid)[dim(d.bId)]
+    h_inv = inverse_spacing(d.grid)[dim(d.bId)]
+    return apply_normal_derivative(d.op, h_inv, v[j], i, N_i, region(d.bId))
+end
+
+function LazyTensors.apply_transpose(d::NormalDerivative{T}, v::AbstractArray{T}, I::NTuple{1,Index}) where T
+    u = selectdim(v,3-dim(d.bId),Int(I[1]))
+    return apply_normal_derivative_transpose(d.op, inverse_spacing(d.grid)[dim(d.bId)], u, region(d.bId))
+end
+
+"""
+    BoundaryQuadrature{T,N,M,K} <: TensorOperator{T,1}
+
+Implements the boundary operator `q` as a TensorOperator
+"""
+struct BoundaryQuadrature{T,N,M,K} <: TensorOperator{T,1}
+    op::D2{T,N,M,K}
+    grid::EquidistantGrid{2}
+    bId::CartesianBoundary
+end
+export BoundaryQuadrature
+
+# TODO: Make this independent of dimension
+function LazyTensors.apply(q::BoundaryQuadrature{T}, v::AbstractArray{T,1}, I::NTuple{1,Index}) where T
+    h = spacing(q.grid)[3-dim(q.bId)]
+    N = size(v)
+    return apply_quadrature(q.op, h, v[I[1]], I[1], N[1])
+end
+
+LazyTensors.apply_transpose(q::BoundaryQuadrature{T}, v::AbstractArray{T,1}, I::NTuple{1,Index}) where T = LazyTensors.apply(q,v,I)
+
+
 
 
 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
+    e = boundary_value(L, Bid())
+    d = normal_derivative(L, Bid())
+    Hᵧ = boundary_quadrature(L, Bid())
+    H⁻¹ = inverse_quadrature(L)
+    return (-H⁻¹*e*Hᵧ*(d'*v - g))[I]
 end
 
 struct Dirichlet{Bid<:BoundaryIdentifier} <: BoundaryCondition
@@ -113,17 +207,16 @@
 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)
+    e = boundary_value(L, Bid())
+    d = normal_derivative(L, Bid())
+    Hᵧ = boundary_quadrature(L, Bid())
+    H⁻¹ = inverse_quadrature(L)
+    return (-H⁻¹*(tau/h*e + d)*Hᵧ*(e'*v - g))[I]
     # 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) +
+    #   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) +
--- a/DiffOps/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/DiffOps/test/runtests.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -1,4 +1,269 @@
+using Test
 using DiffOps
-using Test
+using Grids
+using SbpOperators
+using RegionIndices
+using LazyTensors
+
+@testset "Laplace2D" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    Lx = 3.5
+    Ly = 7.2
+    g = EquidistantGrid((42,41), (0.0, 0.0), (Lx,Ly))
+    L = Laplace(g, 1., op)
+    H = quadrature(L)
+
+    f0(x::Float64,y::Float64) = 2.
+    f1(x::Float64,y::Float64) = x+y
+    f2(x::Float64,y::Float64) = 1/2*x^2 + 1/2*y^2
+    f3(x::Float64,y::Float64) = 1/6*x^3 + 1/6*y^3
+    f4(x::Float64,y::Float64) = 1/24*x^4 + 1/24*y^4
+    f5(x::Float64,y::Float64) = sin(x) + cos(y)
+    f5ₓₓ(x::Float64,y::Float64) = -f5(x,y)
+
+    v0 = evalOn(g,f0)
+    v1 = evalOn(g,f1)
+    v2 = evalOn(g,f2)
+    v3 = evalOn(g,f3)
+    v4 = evalOn(g,f4)
+    v5 = evalOn(g,f5)
+    v5ₓₓ = evalOn(g,f5ₓₓ)
+
+    @test L isa TensorOperator{T,2} where T
+    @test L' isa TensorMapping{T,2,2} where T
+
+    # TODO: Should perhaps set tolerance level for isapporx instead?
+    #       Are these tolerance levels resonable or should tests be constructed
+    #       differently?
+    equalitytol = 0.5*1e-10
+    accuracytol = 0.5*1e-3
+    # 4th order interior stencil, 2nd order boundary stencil,
+    # implies that L*v should be exact for v - monomial up to order 3.
+    # Exact differentiation is measured point-wise. For other grid functions
+    # the error is measured in the H-norm.
+    @test all(abs.(collect(L*v0)) .<= equalitytol)
+    @test all(abs.(collect(L*v1)) .<= equalitytol)
+    @test all(collect(L*v2) .≈ v0) # Seems to be more accurate
+    @test all(abs.((collect(L*v3) - v1)) .<= equalitytol)
+    e4 = collect(L*v4) - v2
+    e5 = collect(L*v5) - v5ₓₓ
+    @test sum(collect(H*e4.^2)) <= accuracytol
+    @test sum(collect(H*e5.^2)) <= accuracytol
+end
+
+@testset "Quadrature" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    Lx = 2.3
+    Ly = 5.2
+    g = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly))
+    H = Quadrature(op,g)
+    v = ones(Float64, size(g))
+
+    @test H isa TensorOperator{T,2} where T
+    @test H' isa TensorMapping{T,2,2} where T
+    @test sum(collect(H*v)) ≈ (Lx*Ly)
+    @test collect(H*v) == collect(H'*v)
+end
+
+@testset "InverseQuadrature" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    Lx = 7.3
+    Ly = 8.2
+    g = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly))
+    H = Quadrature(op,g)
+    Hinv = InverseQuadrature(op,g)
+    v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y)
+
+    @test Hinv isa TensorOperator{T,2} where T
+    @test Hinv' isa TensorMapping{T,2,2} where T
+    @test collect(Hinv*H*v)  ≈ v
+    @test collect(Hinv*v) == collect(Hinv'*v)
+end
+
+@testset "BoundaryValue" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    g = EquidistantGrid((4,5), (0.0, 0.0), (1.0,1.0))
+
+    e_w = BoundaryValue(op, g, CartesianBoundary{1,Lower}())
+    e_e = BoundaryValue(op, g, CartesianBoundary{1,Upper}())
+    e_s = BoundaryValue(op, g, CartesianBoundary{2,Lower}())
+    e_n = BoundaryValue(op, g, CartesianBoundary{2,Upper}())
+
+    v = zeros(Float64, 4, 5)
+    v[:,5] = [1, 2, 3,4]
+    v[:,4] = [1, 2, 3,4]
+    v[:,3] = [4, 5, 6, 7]
+    v[:,2] = [7, 8, 9, 10]
+    v[:,1] = [10, 11, 12, 13]
+
+    @test e_w  isa TensorMapping{T,2,1} where T
+    @test e_w' isa TensorMapping{T,1,2} where T
+
+    @test domain_size(e_w, (3,2)) == (2,)
+    @test domain_size(e_e, (3,2)) == (2,)
+    @test domain_size(e_s, (3,2)) == (3,)
+    @test domain_size(e_n, (3,2)) == (3,)
+
+    @test size(e_w'*v) == (5,)
+    @test size(e_e'*v) == (5,)
+    @test size(e_s'*v) == (4,)
+    @test size(e_n'*v) == (4,)
+
+    @test collect(e_w'*v) == [10,7,4,1.0,1]
+    @test collect(e_e'*v) == [13,10,7,4,4.0]
+    @test collect(e_s'*v) == [10,11,12,13.0]
+    @test collect(e_n'*v) == [1,2,3,4.0]
+
+    g_x = [1,2,3,4.0]
+    g_y = [5,4,3,2,1.0]
+
+    G_w = zeros(Float64, (4,5))
+    G_w[1,:] = g_y
+
+    G_e = zeros(Float64, (4,5))
+    G_e[4,:] = g_y
+
+    G_s = zeros(Float64, (4,5))
+    G_s[:,1] = g_x
+
+    G_n = zeros(Float64, (4,5))
+    G_n[:,5] = g_x
+
+    @test size(e_w*g_y) == (UnknownDim,5)
+    @test size(e_e*g_y) == (UnknownDim,5)
+    @test size(e_s*g_x) == (4,UnknownDim)
+    @test size(e_n*g_x) == (4,UnknownDim)
 
-@test_broken false
+    # These tests should be moved to where they are possible (i.e we know what the grid should be)
+    @test_broken collect(e_w*g_y) == G_w
+    @test_broken collect(e_e*g_y) == G_e
+    @test_broken collect(e_s*g_x) == G_s
+    @test_broken collect(e_n*g_x) == G_n
+end
+
+@testset "NormalDerivative" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    g = EquidistantGrid((5,6), (0.0, 0.0), (4.0,5.0))
+
+    d_w = NormalDerivative(op, g, CartesianBoundary{1,Lower}())
+    d_e = NormalDerivative(op, g, CartesianBoundary{1,Upper}())
+    d_s = NormalDerivative(op, g, CartesianBoundary{2,Lower}())
+    d_n = NormalDerivative(op, g, CartesianBoundary{2,Upper}())
+
+
+    v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y)
+    v∂x = evalOn(g, (x,y)-> 2*x + y)
+    v∂y = evalOn(g, (x,y)-> 2*(y-1) + x)
+
+    @test d_w  isa TensorMapping{T,2,1} where T
+    @test d_w' isa TensorMapping{T,1,2} where T
+
+    @test domain_size(d_w, (3,2)) == (2,)
+    @test domain_size(d_e, (3,2)) == (2,)
+    @test domain_size(d_s, (3,2)) == (3,)
+    @test domain_size(d_n, (3,2)) == (3,)
+
+    @test size(d_w'*v) == (6,)
+    @test size(d_e'*v) == (6,)
+    @test size(d_s'*v) == (5,)
+    @test size(d_n'*v) == (5,)
+
+    @test collect(d_w'*v) ≈ v∂x[1,:]
+    @test collect(d_e'*v) ≈ v∂x[5,:]
+    @test collect(d_s'*v) ≈ v∂y[:,1]
+    @test collect(d_n'*v) ≈ v∂y[:,6]
+
+
+    d_x_l = zeros(Float64, 5)
+    d_x_u = zeros(Float64, 5)
+    for i ∈ eachindex(d_x_l)
+        d_x_l[i] = op.dClosure[i-1]
+        d_x_u[i] = -op.dClosure[length(d_x_u)-i]
+    end
+
+    d_y_l = zeros(Float64, 6)
+    d_y_u = zeros(Float64, 6)
+    for i ∈ eachindex(d_y_l)
+        d_y_l[i] = op.dClosure[i-1]
+        d_y_u[i] = -op.dClosure[length(d_y_u)-i]
+    end
+
+    function prod_matrix(x,y)
+        G = zeros(Float64, length(x), length(y))
+        for I ∈ CartesianIndices(G)
+            G[I] = x[I[1]]*y[I[2]]
+        end
+
+        return G
+    end
+
+    g_x = [1,2,3,4.0,5]
+    g_y = [5,4,3,2,1.0,11]
+
+    G_w = prod_matrix(d_x_l, g_y)
+    G_e = prod_matrix(d_x_u, g_y)
+    G_s = prod_matrix(g_x, d_y_l)
+    G_n = prod_matrix(g_x, d_y_u)
+
+
+    @test size(d_w*g_y) == (UnknownDim,6)
+    @test size(d_e*g_y) == (UnknownDim,6)
+    @test size(d_s*g_x) == (5,UnknownDim)
+    @test size(d_n*g_x) == (5,UnknownDim)
+
+    # These tests should be moved to where they are possible (i.e we know what the grid should be)
+    @test_broken collect(d_w*g_y) ≈ G_w
+    @test_broken collect(d_e*g_y) ≈ G_e
+    @test_broken collect(d_s*g_x) ≈ G_s
+    @test_broken collect(d_n*g_x) ≈ G_n
+end
+
+@testset "BoundaryQuadrature" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    g = EquidistantGrid((10,11), (0.0, 0.0), (1.0,1.0))
+
+    H_w = BoundaryQuadrature(op, g, CartesianBoundary{1,Lower}())
+    H_e = BoundaryQuadrature(op, g, CartesianBoundary{1,Upper}())
+    H_s = BoundaryQuadrature(op, g, CartesianBoundary{2,Lower}())
+    H_n = BoundaryQuadrature(op, g, CartesianBoundary{2,Upper}())
+
+    v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y)
+
+    function get_quadrature(N)
+        qc = op.quadratureClosure
+        q = (qc..., ones(N-2*closuresize(op))..., reverse(qc)...)
+        @assert length(q) == N
+        return q
+    end
+
+    v_w = v[1,:]
+    v_e = v[10,:]
+    v_s = v[:,1]
+    v_n = v[:,11]
+
+    q_x = spacing(g)[1].*get_quadrature(10)
+    q_y = spacing(g)[2].*get_quadrature(11)
+
+    @test H_w isa TensorOperator{T,1} where T
+
+    @test domain_size(H_w, (3,)) == (3,)
+    @test domain_size(H_n, (3,)) == (3,)
+
+    @test range_size(H_w, (3,)) == (3,)
+    @test range_size(H_n, (3,)) == (3,)
+
+    @test size(H_w*v_w) == (11,)
+    @test size(H_e*v_e) == (11,)
+    @test size(H_s*v_s) == (10,)
+    @test size(H_n*v_n) == (10,)
+
+    @test collect(H_w*v_w) ≈ q_y.*v_w
+    @test collect(H_e*v_e) ≈ q_y.*v_e
+    @test collect(H_s*v_s) ≈ q_x.*v_s
+    @test collect(H_n*v_n) ≈ q_x.*v_n
+
+    @test collect(H_w'*v_w) == collect(H_w'*v_w)
+    @test collect(H_e'*v_e) == collect(H_e'*v_e)
+    @test collect(H_s'*v_s) == collect(H_s'*v_s)
+    @test collect(H_n'*v_n) == collect(H_n'*v_n)
+end
--- a/Grids/src/EquidistantGrid.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/Grids/src/EquidistantGrid.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -10,13 +10,13 @@
     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
+    inverse_spacing::NTuple{Dim, T} # Reciprocal of 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)
+        inverse_spacing = (size.-1)./ abs.(limit_upper.-limit_lower)
         return new{Dim,T}(size, limit_lower, limit_upper, inverse_spacing)
     end
 end
@@ -25,6 +25,8 @@
     CartesianIndices(grid.size)
 end
 
+Base.size(g::EquidistantGrid) = g.size
+
 # Returns the number of dimensions of an EquidistantGrid.
 #
 # @Input: grid - an EquidistantGrid
@@ -33,11 +35,20 @@
     return length(grid.size)
 end
 
-# Returns the spacing of the grid
+# Returns the reciprocal of the spacing of the grid
 #
+function inverse_spacing(grid::EquidistantGrid)
+    return grid.inverse_spacing
+end
+export inverse_spacing
+
+# Returns the reciprocal of the spacing of the grid
+#
+# TODO: Evaluate if divisions affect performance
 function spacing(grid::EquidistantGrid)
     return 1.0./grid.inverse_spacing
 end
+export spacing
 
 # Computes the points of an EquidistantGrid as an array of tuples with
 # the same dimension as the grid.
--- a/Grids/src/Grids.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/Grids/src/Grids.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -9,6 +9,8 @@
 dim(::CartesianBoundary{Dim, R}) where {Dim, R} = Dim
 region(::CartesianBoundary{Dim, R}) where {Dim, R} = R
 
+export dim, region
+
 include("AbstractGrid.jl")
 include("EquidistantGrid.jl")
 
--- a/LazyTensors/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
+++ b/LazyTensors/Manifest.toml	Mon Jun 22 21:43:05 2020 +0200
@@ -1,2 +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"
--- a/LazyTensors/Project.toml	Wed Jun 26 15:07:47 2019 +0200
+++ b/LazyTensors/Project.toml	Mon Jun 22 21:43:05 2020 +0200
@@ -3,6 +3,9 @@
 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"
 
--- a/LazyTensors/src/LazyTensors.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/LazyTensors/src/LazyTensors.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -1,6 +1,7 @@
 module LazyTensors
-
+using RegionIndices
 include("tensor_mapping.jl")
-include("lazy_operations.jl")
+include("lazy_array.jl")
+include("lazy_tensor_operations.jl")
 
 end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LazyTensors/src/lazy_array.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,163 @@
+"""
+    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
+
+"""
+    LazyElementwiseOperation{T,D,Op,T1,T2} <: LazyArray{T,D}
+Struct allowing for lazy evaluation of elementwise operations on AbstractArrays.
+
+A LazyElementwiseOperation contains two datatypes T1, and T2, together with an operation,
+where at least one of T1 and T2 is an AbstractArray, and one may be a Real.
+The operations are carried out when the LazyElementwiseOperation is indexed.
+"""
+struct LazyElementwiseOperation{T,D,Op,T1,T2} <: 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
+
+    @inline function LazyElementwiseOperation{T,D,Op}(a::T1,b::T2) where {T,D,Op,T1<:AbstractArray{T,D},T2<:Real}
+        return new{T,D,Op,T1,T2}(a,b)
+    end
+
+    @inline function LazyElementwiseOperation{T,D,Op}(a::T1,b::T2) where {T,D,Op,T1<:Real,T2<:AbstractArray{T,D}}
+        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,:+,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:AbstractArray{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,:-,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:AbstractArray{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,:*,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:AbstractArray{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,:/,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:AbstractArray{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,:+,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:Real}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a[I...] + leo.b
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:-,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:Real}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a[I...] - leo.b
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:*,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:Real}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a[I...] * leo.b
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:/,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:AbstractArray{T,D},T2<:Real}
+    @boundscheck if !checkbounds(Bool,leo.a,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a[I...] / leo.b
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:+,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:Real,T2<:AbstractArray{T,D}}
+    @boundscheck if !checkbounds(Bool,leo.b,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a + leo.b[I...]
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:-,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:Real,T2<:AbstractArray{T,D}}
+    @boundscheck if !checkbounds(Bool,leo.b,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a - leo.b[I...]
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:*,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:Real,T2<:AbstractArray{T,D}}
+    @boundscheck if !checkbounds(Bool,leo.b,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a * leo.b[I...]
+end
+
+Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D,:/,T1,T2}, I::Vararg{Int,D}) where {T,D,T1<:Real,T2<:AbstractArray{T,D}}
+    @boundscheck if !checkbounds(Bool,leo.b,I...)
+        throw(BoundsError([leo],I...))
+    end
+    return leo.a / 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)
+
+Base.@propagate_inbounds +̃(a::AbstractArray{T,D}, b::Real) where {T,D} = LazyElementwiseOperation{T,D,:+}(a,b)
+Base.@propagate_inbounds -̃(a::AbstractArray{T,D}, b::Real) where {T,D} = LazyElementwiseOperation{T,D,:-}(a,b)
+Base.@propagate_inbounds *̃(a::AbstractArray{T,D}, b::Real) where {T,D} = LazyElementwiseOperation{T,D,:*}(a,b)
+Base.@propagate_inbounds /̃(a::AbstractArray{T,D}, b::Real) where {T,D} = LazyElementwiseOperation{T,D,:/}(a,b)
+
+Base.@propagate_inbounds +̃(a::Real, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:+}(a,b)
+Base.@propagate_inbounds -̃(a::Real, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:-}(a,b)
+Base.@propagate_inbounds *̃(a::Real, b::AbstractArray{T,D}) where {T,D} = LazyElementwiseOperation{T,D,:*}(a,b)
+Base.@propagate_inbounds /̃(a::Real, 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 +̃, -̃, *̃, /̃
--- a/LazyTensors/src/lazy_operations.jl	Wed Jun 26 15:07:47 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-"""
-    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/lazy_tensor_operations.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,101 @@
+"""
+    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{Index,R}) where {T,R,D} = apply(ta.t, ta.o, I)
+Base.getindex(ta::LazyTensorMappingApplication{T,R,D}, I::Vararg{Int,R}) where {T,R,D} = apply(ta.t, ta.o, Index{Unknown}.(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.:*(a::TensorMapping{T,R,D}, b::TensorMapping{T,D,K}, args::Union{TensorMapping{T}, AbstractArray{T}}...) where {T,R,D,K} = foldr(*,(a,b,args...))
+# # Should we overload some other infix binary opesrator?
+# →(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)?
+
+"""
+    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(tm::TensorMapping) = LazyTensorMappingTranspose(tm)
+Base.adjoint(tmt::LazyTensorMappingTranspose) = tmt.tm
+
+apply(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,R}, I::NTuple{D,Index}) where {T,R,D} = apply_transpose(tmt.tm, v, I)
+apply_transpose(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,D}, I::NTuple{R,Index}) where {T,R,D} = apply(tmt.tm, v, I)
+
+range_size(tmt::LazyTensorMappingTranspose{T,R,D}, d_size::NTuple{R,Integer}) where {T,R,D} = domain_size(tmt.tm, d_size)
+domain_size(tmt::LazyTensorMappingTranspose{T,R,D}, r_size::NTuple{D,Integer}) where {T,R,D} = range_size(tmt.tm, r_size)
+
+
+
+
+struct LazyTensorMappingBinaryOperation{Op,T,R,D,T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}} <: TensorMapping{T,D,R}
+    tm1::T1
+    tm2::T2
+
+    @inline function LazyTensorMappingBinaryOperation{Op,T,R,D}(tm1::T1,tm2::T2) where {Op,T,R,D, T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}}
+        return new{Op,T,R,D,T1,T2}(tm1,tm2)
+    end
+end
+
+apply(tmBinOp::LazyTensorMappingBinaryOperation{:+,T,R,D}, v::AbstractArray{T,D}, I::NTuple{R,Index}) where {T,R,D} = apply(tmBinOp.tm1, v, I) + apply(tmBinOp.tm2, v, I)
+apply(tmBinOp::LazyTensorMappingBinaryOperation{:-,T,R,D}, v::AbstractArray{T,D}, I::NTuple{R,Index}) where {T,R,D} = apply(tmBinOp.tm1, v, I) - apply(tmBinOp.tm2, v, I)
+
+range_size(tmBinOp::LazyTensorMappingBinaryOperation{Op,T,R,D}, domain_size::NTuple{D,Integer}) where {Op,T,R,D} = range_size(tmBinOp.tm1, domain_size)
+domain_size(tmBinOp::LazyTensorMappingBinaryOperation{Op,T,R,D}, range_size::NTuple{R,Integer}) where {Op,T,R,D} = domain_size(tmBinOp.tm2, range_size)
+
+Base.:+(tm1::TensorMapping{T,R,D}, tm2::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:+,T,R,D}(tm1,tm2)
+Base.:-(tm1::TensorMapping{T,R,D}, tm2::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:-,T,R,D}(tm1,tm2)
+
+
+# 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::NTuple{R,Int}) 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::NTuple{D,Int}) 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 →
--- a/LazyTensors/src/tensor_mapping.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/LazyTensors/src/tensor_mapping.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -62,7 +62,12 @@
 """
 function domain_size end
 
-export range_size, domain_size
+"""
+    Dummy type for representing dimensions of tensormappings when domain_size is unknown
+"""
+struct UnknownDim end
+export range_size, domain_size, TensorMappingDim, UnknownDim
+
 # TODO: Think about boundschecking!
 
 
--- a/LazyTensors/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/LazyTensors/test/runtests.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -1,12 +1,13 @@
 using Test
 using LazyTensors
+using RegionIndices
 
 @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
+    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i::NTuple{R,Index{<:Region}}) 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
+    @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),(Index{Unknown}(0),Index{Unknown}(0))) == :apply
 end
 
 @testset "Generic Operator methods" begin
@@ -18,17 +19,19 @@
 @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.apply(m::DummyMapping{T,R,D}, v, I::NTuple{R,Index{<:Region}}) where {T,R,D} = :apply
+    LazyTensors.apply_transpose(m::DummyMapping{T,R,D}, v, I::NTuple{D,Index{<:Region}}) 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
+    LazyTensors.range_size(m::DummyMapping{T,R,D}, domain_size::NTuple{D,Integer}) where {T,R,D} = :range_size
+    LazyTensors.domain_size(m::DummyMapping{T,R,D}, range_size::NTuple{R,Integer}) where {T,R,D} = :domain_size
 
     m = DummyMapping{Float64,2,3}()
+    I = Index{Unknown}(0)
+    @test m' isa TensorMapping{Float64, 3,2}
     @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 apply(m',zeros(Float64,(0,0)), (I,I,I)) == :apply_transpose
+    @test apply(m'',zeros(Float64,(0,0,0)),(I,I)) == :apply
+    @test apply_transpose(m', zeros(Float64,(0,0,0)),(I,I)) == :apply
 
     @test range_size(m', (0,0)) == :domain_size
     @test domain_size(m', (0,0,0)) == :range_size
@@ -37,24 +40,53 @@
 @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
+    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i::NTuple{R,Index{<:Region}}) where {T,R,D} = (:apply,v,i)
+    LazyTensors.range_size(m::DummyMapping{T,R,D}, domain_size::NTuple{D,Integer}) where {T,R,D} = 2 .* domain_size
+    LazyTensors.domain_size(m::DummyMapping{T,R,D}, range_size::NTuple{R,Integer}) 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*v)[Index{Upper}(0)] == (:apply,v,(Index{Upper}(0),))
+    @test (m*v)[0] == (:apply,v,(Index{Unknown}(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 (m*m*v)[Index{Upper}(1)] == (:apply,m*v,(Index{Upper}(1),))
+    @test (m*m*v)[1] == (:apply,m*v,(Index{Unknown}(1),))
+    @test (m*m*v)[Index{Interior}(3)] == (:apply,m*v,(Index{Interior}(3),))
+    @test (m*m*v)[3] == (:apply,m*v,(Index{Unknown}(3),))
+    @test (m*m*v)[Index{Lower}(6)] == (:apply,m*v,(Index{Lower}(6),))
+    @test (m*m*v)[6] == (:apply,m*v,(Index{Unknown}(6),))
     @test_broken BoundsError == (m*m*v)[0]
     @test_broken BoundsError == (m*m*v)[7]
+
+    m = DummyMapping{Int, 2, 1}()
+    @test_throws MethodError m*ones(Int,2,2)
+    @test_throws MethodError m*m*v
+
+    m = DummyMapping{Float64, 2, 2}()
+    v = ones(3,3)
+    I = (Index{Lower}(1),Index{Interior}(2));
+    @test size(m*v) == 2 .*size(v)
+    @test (m*v)[I] == (:apply,v,I)
+
+    struct ScalingOperator{T,D} <: TensorOperator{T,D}
+        λ::T
+    end
+
+    LazyTensors.apply(m::ScalingOperator{T,D}, v, I::NTuple{D, Index}) where {T,D} = m.λ*v[I]
+
+    m = ScalingOperator{Int,1}(2)
+    v = [1,2,3]
+    @test m*v isa AbstractVector
+    @test m*v == [2,4,6]
+
+    m = ScalingOperator{Int,2}(2)
+    v = [[1 2];[3 4]]
+    @test m*v == [[2 4];[6 8]]
+    I = (Index{Upper}(2),Index{Lower}(1))
+    @test (m*v)[I] == 6
 end
 
 @testset "TensorMapping binary operations" begin
@@ -62,7 +94,7 @@
         λ::T
     end
 
-    LazyTensors.apply(m::ScalarMapping{T,R,D}, v, i) where {T,R,D} = m.λ*v[i]
+    LazyTensors.apply(m::ScalarMapping{T,R,D}, v, I::Tuple{Index{<:Region}}) 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
 
@@ -70,7 +102,6 @@
     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
@@ -88,24 +119,45 @@
         data::T1
     end
     Base.size(v::DummyArray) = size(v.data)
-    Base.getindex(v::DummyArray, I...) = v.data[I...]
+    Base.getindex(v::DummyArray{T,D}, I::Vararg{Int,D}) where {T,D} = 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
+    s = 3.4
+    r_add_v = v1 .+ v2
+    r_sub_v = v1 .- v2
+    r_times_v = v1 .* v2
+    r_div_v = v1 ./ v2
+    r_add_s = v1 .+ s
+    r_sub_s = v1 .- s
+    r_times_s = v1 .* s
+    r_div_s = v1 ./ s
     @test isa(v1 +̃ v2, LazyArray)
     @test isa(v1 -̃ v2, LazyArray)
     @test isa(v1 *̃ v2, LazyArray)
     @test isa(v1 /̃ v2, LazyArray)
+    @test isa(v1 +̃ s, LazyArray)
+    @test isa(v1 -̃ s, LazyArray)
+    @test isa(v1 *̃ s, LazyArray)
+    @test isa(v1 /̃ s, LazyArray)
+    @test isa(s +̃ v1, LazyArray)
+    @test isa(s -̃ v1, LazyArray)
+    @test isa(s *̃ v1, LazyArray)
+    @test isa(s /̃ v1, 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]
+        @test (v1 +̃ v2)[i] == r_add_v[i]
+        @test (v1 -̃ v2)[i] == r_sub_v[i]
+        @test (v1 *̃ v2)[i] == r_times_v[i]
+        @test (v1 /̃ v2)[i] == r_div_v[i]
+        @test (v1 +̃ s)[i] == r_add_s[i]
+        @test (v1 -̃ s)[i] == r_sub_s[i]
+        @test (v1 *̃ s)[i] == r_times_s[i]
+        @test (v1 /̃ s)[i] == r_div_s[i]
+        @test (s +̃ v1)[i] == r_add_s[i]
+        @test (s -̃ v1)[i] == -r_sub_s[i]
+        @test (s *̃ v1)[i] == r_times_s[i]
+        @test (s /̃ v1)[i] == 1/r_div_s[i]
     end
     @test_throws BoundsError (v1 +̃  v2)[4]
     v2 = [1., 2, 3, 4]
@@ -120,8 +172,8 @@
     @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]
+        @test (v1 + v2)[i] == (v2 + v1)[i] == r_add_v[i]
+        @test (v1 - v2)[i] == -(v2 - v1)[i] == r_sub_v[i]
     end
     @test_throws BoundsError (v1 + v2)[4]
     v2 = [1., 2, 3, 4]
--- a/Manifest.toml	Wed Jun 26 15:07:47 2019 +0200
+++ b/Manifest.toml	Mon Jun 22 21:43:05 2020 +0200
@@ -4,7 +4,7 @@
 uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
 
 [[DiffOps]]
-deps = ["Grids", "RegionIndices", "SbpOperators", "TiledIteration"]
+deps = ["Grids", "LazyTensors", "RegionIndices", "SbpOperators", "TiledIteration"]
 path = "DiffOps"
 uuid = "39474f48-97ec-11e9-01fc-6ddcbe5918df"
 version = "0.1.0"
@@ -24,6 +24,7 @@
 uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
 
 [[LazyTensors]]
+deps = ["RegionIndices"]
 path = "LazyTensors"
 uuid = "62fbed2c-918d-11e9-279b-eb3a325b37d3"
 version = "0.1.0"
@@ -36,16 +37,16 @@
 uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
 
 [[OffsetArrays]]
-git-tree-sha1 = "1af2f79c7eaac3e019a0de41ef63335ff26a0a57"
+git-tree-sha1 = "87d0a91efe29352d5caaa271ae3927083c096e33"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "0.11.1"
+version = "0.11.4"
 
 [[Random]]
 deps = ["Serialization"]
 uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 
 [[RegionIndices]]
-path = "RegionIndices"
+path = "/Users/vidar/dev/hg/sbpteam/sbplib_julia/RegionIndices"
 uuid = "5d527584-97f1-11e9-084c-4540c7ecf219"
 version = "0.1.0"
 
--- a/RegionIndices/src/RegionIndices.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/RegionIndices/src/RegionIndices.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -30,6 +30,8 @@
 Base.convert(::Type{CartesianIndex}, I::NTuple{N,Index} where N) = CartesianIndex(convert.(Int, I))
 
 Base.Int(I::Index) = I.i
+Base.to_index(I::Index) = Int(I) #How to get this to work for all cases??
+Base.getindex(A::AbstractArray{T,N}, I::NTuple{N,Index}) where {T,N} = A[I...] #Is this ok??
 
 function Index(i::Integer, boundary_width::Integer, dim_size::Integer)
     return Index{getregion(i,boundary_width,dim_size)}(i)
@@ -65,6 +67,8 @@
     end
 end
 
+export getregion
+
 function getrange(gridsize::Integer, closuresize::Integer, region::DataType)
     if region == Lower
         r = 1:closuresize
--- a/SbpOperators/src/SbpOperators.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/SbpOperators/src/SbpOperators.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -3,161 +3,8 @@
 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
+include("constantstenciloperator.jl")
+include("d2.jl")
+include("readoperator.jl")
 
 end # module
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/constantlaplace.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,54 @@
+#TODO: Naming?! What is this? It is a 1D tensor operator but what is then the
+# potentially multi-D laplace tensor mapping then?
+# Ideally I would like the below to be the laplace operator in 1D, while the
+# multi-D operator is a a tuple of the 1D-operator. Possible via recursive
+# definitions? Or just bad design?
+"""
+    ConstantLaplaceOperator{T<:Real,N,M,K} <: TensorOperator{T,1}
+Implements the Laplace tensor operator `L` with constant grid spacing and coefficients
+in 1D dimension
+"""
+struct ConstantLaplaceOperator{T<:Real,N,M,K} <: TensorOperator{T,1}
+    h_inv::T # The grid spacing could be included in the stencil already. Preferable?
+    a::T # TODO: Better name?
+    innerStencil::Stencil{T,N}
+    closureStencils::NTuple{M,Stencil{T,K}}
+    parity::Parity
+    #TODO: Write a nice constructor
+end
+
+@enum Parity begin
+    odd = -1
+    even = 1
+end
+
+LazyTensors.domain_size(L::ConstantLaplaceOperator, range_size::NTuple{1,Integer}) = range_size
+
+function LazyTensors.apply(L::ConstantLaplaceOperator{T}, v::AbstractVector{T}, I::NTuple{1,Index}) where T
+    return apply(L, v, I[1])
+end
+
+# Apply for different regions Lower/Interior/Upper or Unknown region
+@inline function LazyTensors.apply(L::ConstantLaplaceOperator, v::AbstractVector, i::Index{Lower})
+    return @inbounds L.a*L.h_inv*L.h_inv*apply_stencil(L.closureStencils[Int(i)], v, Int(i))
+end
+
+@inline function LazyTensors.apply(L::ConstantLaplaceOperator, v::AbstractVector, i::Index{Interior})
+    return @inbounds L.a*L.h_inv*L.h_inv*apply_stencil(L.innerStencil, v, Int(i))
+end
+
+@inline function LazyTensors.apply(L::ConstantLaplaceOperator, v::AbstractVector, i::Index{Upper})
+    N = length(v) # TODO: Use domain_size here instead?
+    return @inbounds L.a*L.h_inv*L.h_inv*Int(L.parity)*apply_stencil_backwards(L.closureStencils[N-Int(i)+1], v, Int(i))
+end
+
+@inline function LazyTensors.apply(L::ConstantLaplaceOperator, v::AbstractVector, index::Index{Unknown})
+    N = length(v)  # TODO: Use domain_size here instead?
+    r = getregion(Int(index), closuresize(L), N)
+    i = Index(Int(index), r)
+    return apply(L, v, i)
+end
+
+function closuresize(L::ConstantLaplaceOperator{T<:Real,N,M,K}) where T,N,M,K
+    return M
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/constantstenciloperator.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,109 @@
+abstract type ConstantStencilOperator end
+
+# Apply for different regions Lower/Interior/Upper or Unknown region
+@inline function apply_2nd_derivative(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, i::Index{Lower})
+    return @inbounds h_inv*h_inv*apply_stencil(op.closureStencils[Int(i)], v, Int(i))
+end
+
+@inline function apply_2nd_derivative(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, i::Index{Interior})
+    return @inbounds h_inv*h_inv*apply_stencil(op.innerStencil, v, Int(i))
+end
+
+@inline function apply_2nd_derivative(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, i::Index{Upper})
+    N = length(v)
+    return @inbounds h_inv*h_inv*Int(op.parity)*apply_stencil_backwards(op.closureStencils[N-Int(i)+1], v, Int(i))
+end
+
+@inline function apply_2nd_derivative(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, index::Index{Unknown})
+    N = length(v)
+    r = getregion(Int(index), closuresize(op), N)
+    i = Index(Int(index), r)
+    return apply_2nd_derivative(op, h_inv, v, i)
+end
+export apply_2nd_derivative
+
+apply_quadrature(op::ConstantStencilOperator, h::Real, v::T, i::Index{Lower}, N::Integer) where T = v*h*op.quadratureClosure[Int(i)]
+apply_quadrature(op::ConstantStencilOperator, h::Real, v::T, i::Index{Upper}, N::Integer) where T = v*h*op.quadratureClosure[N-Int(i)+1]
+apply_quadrature(op::ConstantStencilOperator, h::Real, v::T, i::Index{Interior}, N::Integer) where T = v*h
+
+function apply_quadrature(op::ConstantStencilOperator, h::Real, v::T, index::Index{Unknown}, N::Integer) where T
+    r = getregion(Int(index), closuresize(op), N)
+    i = Index(Int(index), r)
+    return apply_quadrature(op, h, v, i, N)
+end
+export apply_quadrature
+
+# TODO: Evaluate if divisions affect performance
+apply_inverse_quadrature(op::ConstantStencilOperator, h_inv::Real, v::T, i::Index{Lower}, N::Integer) where T = h_inv*v/op.quadratureClosure[Int(i)]
+apply_inverse_quadrature(op::ConstantStencilOperator, h_inv::Real, v::T, i::Index{Upper}, N::Integer) where T = h_inv*v/op.quadratureClosure[N-Int(i)+1]
+apply_inverse_quadrature(op::ConstantStencilOperator, h_inv::Real, v::T, i::Index{Interior}, N::Integer) where T = v*h_inv
+
+function apply_inverse_quadrature(op::ConstantStencilOperator, h_inv::Real, v::T, index::Index{Unknown}, N::Integer) where T
+    r = getregion(Int(index), closuresize(op), N)
+    i = Index(Int(index), r)
+    return apply_inverse_quadrature(op, h_inv, v, i, N)
+end
+
+export apply_inverse_quadrature
+
+function apply_boundary_value_transpose(op::ConstantStencilOperator, v::AbstractVector, ::Type{Lower})
+    @boundscheck if length(v) < closuresize(op)
+        throw(BoundsError())
+    end
+    apply_stencil(op.eClosure,v,1)
+end
+
+function apply_boundary_value_transpose(op::ConstantStencilOperator, v::AbstractVector, ::Type{Upper})
+    @boundscheck if length(v) < closuresize(op)
+        throw(BoundsError())
+    end
+    apply_stencil_backwards(op.eClosure,v,length(v))
+end
+export apply_boundary_value_transpose
+
+function apply_boundary_value(op::ConstantStencilOperator, v::Number, i::Index, N::Integer, ::Type{Lower})
+    @boundscheck if !(0<length(Int(i)) <= N)
+        throw(BoundsError())
+    end
+    op.eClosure[Int(i)-1]*v
+end
+
+function apply_boundary_value(op::ConstantStencilOperator, v::Number, i::Index,  N::Integer, ::Type{Upper})
+    @boundscheck if !(0<length(Int(i)) <= N)
+        throw(BoundsError())
+    end
+    op.eClosure[N-Int(i)]*v
+end
+export apply_boundary_value
+
+function apply_normal_derivative_transpose(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, ::Type{Lower})
+    @boundscheck if length(v) < closuresize(op)
+        throw(BoundsError())
+    end
+    h_inv*apply_stencil(op.dClosure,v,1)
+end
+
+function apply_normal_derivative_transpose(op::ConstantStencilOperator, h_inv::Real, v::AbstractVector, ::Type{Upper})
+    @boundscheck if length(v) < closuresize(op)
+        throw(BoundsError())
+    end
+    -h_inv*apply_stencil_backwards(op.dClosure,v,length(v))
+end
+
+export apply_normal_derivative_transpose
+
+function apply_normal_derivative(op::ConstantStencilOperator, h_inv::Real, v::Number, i::Index, N::Integer, ::Type{Lower})
+    @boundscheck if !(0<length(Int(i)) <= N)
+        throw(BoundsError())
+    end
+    h_inv*op.dClosure[Int(i)-1]*v
+end
+
+function apply_normal_derivative(op::ConstantStencilOperator, h_inv::Real, v::Number, i::Index, N::Integer, ::Type{Upper})
+    @boundscheck if !(0<length(Int(i)) <= N)
+        throw(BoundsError())
+    end
+    -h_inv*op.dClosure[N-Int(i)]*v
+end
+
+export apply_normal_derivative
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/d2.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,19 @@
+export D2, closuresize, readOperator
+
+@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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SbpOperators/src/readoperator.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -0,0 +1,80 @@
+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 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
+
+sbp_operators_path() = (@__DIR__) * "/../operators/"
+export sbp_operators_path
--- a/SbpOperators/src/stencil.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/SbpOperators/src/stencil.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -21,7 +21,7 @@
     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}
+Base.@propagate_inbounds @inline function apply_stencil(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]
@@ -29,7 +29,7 @@
     return w
 end
 
-Base.@propagate_inbounds @inline function apply_backwards(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N}
+Base.@propagate_inbounds @inline function apply_stencil_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]
--- a/SbpOperators/test/runtests.jl	Wed Jun 26 15:07:47 2019 +0200
+++ b/SbpOperators/test/runtests.jl	Mon Jun 22 21:43:05 2020 +0200
@@ -1,4 +1,23 @@
 using SbpOperators
 using Test
 
-@test_broken false
+@testset "apply_quadrature" begin
+    op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
+    h = 0.5
+
+    @test apply_quadrature(op, h, 1.0, 10, 100) == h
+
+    N = 10
+    qc = op.quadratureClosure
+    q = h.*(qc..., ones(N-2*closuresize(op))..., reverse(qc)...)
+    @assert length(q) == N
+
+    for i ∈ 1:N
+        @test apply_quadrature(op, h, 1.0, i, N) == q[i]
+    end
+
+    v = [2.,3.,2.,4.,5.,4.,3.,4.,5.,4.5]
+    for i ∈ 1:N
+        @test apply_quadrature(op, h, v[i], i, N) == q[i]*v[i]
+    end
+end
--- a/TODO.txt	Wed Jun 26 15:07:47 2019 +0200
+++ b/TODO.txt	Mon Jun 22 21:43:05 2020 +0200
@@ -8,7 +8,26 @@
 
 Profilera
 
-Konvertera till paket
 Skriv tester
 
 Specificera operatorer i TOML eller något liknande?
+
+Borde det finns motsvarande apply_stencil för apply_quadrature,
+apply_boundary_value och apply_normal_derivative?
+
+Borde man alltid skicka in N som parameter i apply_2nd_derivative, t.ex som i
+apply_quadrature?
+
+Just nu agerar apply_normal_derivative, apply_boundary_value på inte på v som
+en vektor, utan randvärdet plockas ut utanför. Känns inte konsistent med övrig
+design
+
+## TODO 2020-06-18
+ * Remove grid as a property of the Laplace tensor operator
+ * Add 1D operators (D1, D2, e, d ... ) as TensorOperators
+ * Move Laplace tensor operator to different package
+ * Add new Laplace opertor to DiffOps, probably named WaveEqOp(?!!?)
+ * Decide: Should there be some kind of collection struct for SBP operators (as TensorOperators), providing easy access to all parts (D2, e, d ,
+ H.. H_gamma etc.)
+ * Is "missing" a good value for unknown dimension sizes (of e*g for example)
+ * Formalize how range_size() and domain_size() are supposed to work in TensorMappings where dim(domain) != dim(range) (add tests or document)