changeset 376:f65809a26a17

Merge performance/lazy_elementwise_operation
author Jonatan Werpers <jonatan@werpers.com>
date Wed, 30 Sep 2020 21:09:35 +0200
parents 99296cbb7bcd (diff) b5444ae443a0 (current diff)
children 8414c2334393 946516954c85
files src/LazyTensors/lazy_array.jl
diffstat 18 files changed, 422 insertions(+), 393 deletions(-) [+]
line wrap: on
line diff
--- a/README.md	Wed Sep 30 21:09:21 2020 +0200
+++ b/README.md	Wed Sep 30 21:09:35 2020 +0200
@@ -1,1 +1,15 @@
 # Sbplib
+
+## Running tests
+To run all tests simply run
+```
+(@v1.5) pkg> activate .
+(Sbplib) pkg> test
+```
+
+If you want to run tests from a specific file in `test/`, you can do
+```
+julia> using Pkg
+julia> Pkg.test(test_args=["testLazyTensors"])
+```
+This works by using the `@includetests` macro from the [TestSetExtensions](https://github.com/ssfrr/TestSetExtensions.jl) package. For more information, see their documentation.
--- a/src/Grids/EquidistantGrid.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/Grids/EquidistantGrid.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -66,6 +66,20 @@
     return broadcast(I -> grid.limit_lower .+ (I.-1).*h, indices)
 end
 
+"""
+    restrict(::EquidistantGrid, dim)
+
+Pick out given dimensions from the grid and return a grid for them
+"""
+function restrict(grid::EquidistantGrid, dim)
+    size = grid.size[dim]
+    limit_lower = grid.limit_lower[dim]
+    limit_upper = grid.limit_upper[dim]
+
+    return EquidistantGrid(size, limit_lower, limit_upper)
+end
+export restrict
+
 function pointsalongdim(grid::EquidistantGrid, dim::Integer)
     @assert dim<=dimension(grid)
     @assert dim>0
--- a/src/LazyTensors/lazy_array.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/LazyTensors/lazy_array.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -17,6 +17,30 @@
 Base.getindex(lca::LazyConstantArray{T,D}, I::Vararg{Int,D}) where {T,D} = lca.val
 
 """
+    LazyFunctionArray{F<:Function,T, D} <: LazyArray{T,D}
+
+A lazy array where each element is defined by a function f(i,j,...)
+"""
+struct LazyFunctionArray{F<:Function,T, D} <: LazyArray{T,D}
+    f::F
+    size::NTuple{D,Int}
+end
+export LazyFunctionArray
+
+function LazyFunctionArray(f::F, size::NTuple{D,Int}) where {F<:Function,D}
+    T = typeof(f(ones(D)...))
+    return LazyFunctionArray{F,T,D}(f,size)
+end
+
+Base.size(lfa::LazyFunctionArray) = lfa.size
+
+function Base.getindex(lfa::LazyFunctionArray{F,T,D}, I::Vararg{Int,D}) where {F,T,D}
+    @boundscheck checkbounds(lfa, I...)
+    return lfa.f(I...)
+end
+
+
+"""
     LazyElementwiseOperation{T,D,Op} <: LazyArray{T,D}
 Struct allowing for lazy evaluation of elementwise operations on AbstractArrays.
 
--- a/src/LazyTensors/lazy_tensor_operations.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/LazyTensors/lazy_tensor_operations.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -11,12 +11,13 @@
     t::TensorMapping{T,R,D}
     o::AbstractArray{T,D}
 end
+# TODO: Do boundschecking on creation!
 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))
+Base.size(ta::LazyTensorMappingApplication) = range_size(ta.t)
 # 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 '→'
@@ -41,16 +42,15 @@
 export LazyTensorMappingTranspose
 
 # # TBD: Should this be implemented on a type by type basis or through a trait to provide earlier errors?
+# Jonatan 2020-09-25: Is the problem that you can take the transpose of any TensorMapping even if it doesn't implement `apply_transpose`?
 Base.adjoint(tm::TensorMapping) = LazyTensorMappingTranspose(tm)
 Base.adjoint(tmt::LazyTensorMappingTranspose) = tmt.tm
 
 apply(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,R}, I::Vararg{Index,D}) where {T,R,D} = apply_transpose(tmt.tm, v, I...)
 apply_transpose(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,D}, I::Vararg{Index,R}) 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)
-
-
+range_size(tmt::LazyTensorMappingTranspose) = domain_size(tmt.tm)
+domain_size(tmt::LazyTensorMappingTranspose) = range_size(tmt.tm)
 
 
 struct LazyTensorMappingBinaryOperation{Op,T,R,D,T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}} <: TensorMapping{T,D,R}
@@ -61,12 +61,13 @@
         return new{Op,T,R,D,T1,T2}(tm1,tm2)
     end
 end
+# TODO: Boundschecking in constructor.
 
 apply(tmBinOp::LazyTensorMappingBinaryOperation{:+,T,R,D}, v::AbstractArray{T,D}, I::Vararg{Index,R}) 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::Vararg{Index,R}) 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)
+range_size(tmBinOp::LazyTensorMappingBinaryOperation{Op,T,R,D}) where {Op,T,R,D} = range_size(tmBinOp.tm1)
+domain_size(tmBinOp::LazyTensorMappingBinaryOperation{Op,T,R,D}) where {Op,T,R,D} = domain_size(tmBinOp.tm1)
 
 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)
--- a/src/LazyTensors/tensor_mapping.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/LazyTensors/tensor_mapping.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -6,11 +6,11 @@
 
     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
+The size of the range and domain that the operator works with should be returned by
+the functions
 
-    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}
+    range_size(::TensorMapping)
+    domain_size(::TensorMapping)
 
 to allow querying for one or the other.
 
@@ -49,35 +49,19 @@
 export range_dim, domain_dim
 
 """
-    range_size(M::TensorMapping, domain_size)
+    range_size(M::TensorMapping)
 
-Return the resulting range size for the mapping applied to a given domain_size
+Return the range size for the mapping.
 """
 function range_size end
 
 """
-    domain_size(M::TensorMapping, range_size)
+    domain_size(M::TensorMapping)
 
-Return the resulting domain size for the mapping applied to a given range_size
+Return the domain size for the mapping.
 """
 function domain_size end
 
-"""
-    Dummy type for representing dimensions of tensormappings when domain_size is unknown
-"""
-struct UnknownDim end
-export range_size, domain_size, TensorMappingDim, UnknownDim
+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
--- a/src/SbpOperators/SbpOperators.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/SbpOperators.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -2,6 +2,7 @@
 
 using Sbplib.RegionIndices
 using Sbplib.LazyTensors
+using Sbplib.Grids
 
 include("stencil.jl")
 include("constantstenciloperator.jl")
@@ -13,4 +14,5 @@
 include("quadrature/quadrature.jl")
 include("quadrature/inverse_diagonal_inner_product.jl")
 include("quadrature/inverse_quadrature.jl")
+
 end # module
--- a/src/SbpOperators/laplace/laplace.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/laplace/laplace.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -1,18 +1,27 @@
 export Laplace
 """
-    Laplace{Dim,T<:Real,N,M,K} <: TensorOperator{T,Dim}
+    Laplace{Dim,T<:Real,N,M,K} <: TensorMapping{T,Dim,Dim}
 
 Implements the Laplace operator `L` in Dim dimensions as a tensor operator
 The multi-dimensional tensor operator consists of a tuple of 1D SecondDerivative
 tensor operators.
 """
 #export quadrature, inverse_quadrature, boundary_quadrature, boundary_value, normal_derivative
-struct Laplace{Dim,T,N,M,K} <: TensorOperator{T,Dim}
+struct Laplace{Dim,T,N,M,K} <: TensorMapping{T,Dim,Dim}
     D2::NTuple{Dim,SecondDerivative{T,N,M,K}}
-    #TODO: Write a good constructor
 end
 
-LazyTensors.domain_size(L::Laplace{Dim}, range_size::NTuple{Dim,Integer}) where {Dim} = range_size
+function Laplace(g::EquidistantGrid{Dim}, innerStencil, closureStencils) where Dim
+    D2 = ()
+    for i ∈ 1:Dim
+        D2 = (D2..., SecondDerivative(restrict(g,i), innerStencil, closureStencils))
+    end
+
+    return Laplace(D2)
+end
+
+LazyTensors.range_size(L::Laplace) = getindex.(range_size.(L.D2),1)
+LazyTensors.domain_size(L::Laplace) = getindex.(domain_size.(L.D2),1)
 
 function LazyTensors.apply(L::Laplace{Dim,T}, v::AbstractArray{T,Dim}, I::Vararg{Index,Dim}) where {T,Dim}
     error("not implemented")
@@ -36,8 +45,6 @@
     return uᵢ
 end
 
-LazyTensors.apply_transpose(L::Laplace{Dim,T}, v::AbstractArray{T,Dim}, I::Vararg{Index,Dim}) where {T,Dim} = LazyTensors.apply(L, v, I...)
-
 # 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)
--- a/src/SbpOperators/laplace/secondderivative.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/laplace/secondderivative.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -4,16 +4,22 @@
 in 1D dimension
 """
 
-struct SecondDerivative{T,N,M,K} <: TensorOperator{T,1}
+struct SecondDerivative{T,N,M,K} <: TensorMapping{T,1,1}
     h_inv::T # The grid spacing could be included in the stencil already. Preferable?
     innerStencil::Stencil{T,N}
     closureStencils::NTuple{M,Stencil{T,K}}
     parity::Parity
-    #TODO: Write a nice constructor
+    size::NTuple{1,Int}
 end
 export SecondDerivative
 
-LazyTensors.domain_size(D2::SecondDerivative, range_size::NTuple{1,Integer}) = range_size
+function SecondDerivative(grid::EquidistantGrid{1}, innerStencil, closureStencils)
+    h_inv = grid.inverse_spacing[1]
+    return SecondDerivative(h_inv, innerStencil, closureStencils, even, size(grid))
+end
+
+LazyTensors.range_size(D2::SecondDerivative) = D2.size
+LazyTensors.domain_size(D2::SecondDerivative) = D2.size
 
 #TODO: The 1D tensor mappings should not have to dispatch on 1D tuples if we write LazyTensor.apply for vararg right?!?!
 #      Currently have to index the Tuple{Index} in each method in order to call the stencil methods which is ugly.
@@ -40,6 +46,4 @@
     return LazyTensors.apply(D2, v, I)
 end
 
-LazyTensors.apply_transpose(D2::SecondDerivative{T}, v::AbstractVector{T}, I::Index) where {T} = LazyTensors.apply(D2, v, I)
-
 closuresize(D2::SecondDerivative{T,N,M,K}) where {T<:Real,N,M,K} = M
--- a/src/SbpOperators/quadrature/diagonal_inner_product.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/quadrature/diagonal_inner_product.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -4,25 +4,30 @@
 
 Implements the diagnoal norm operator `H` of Dim dimension as a TensorMapping
 """
-struct DiagonalInnerProduct{T,M} <: TensorOperator{T,1}
-    h::T # The grid spacing could be included in the stencil already. Preferable?
-    closure::NTuple{M,T}
-    #TODO: Write a nice constructor
+struct DiagonalInnerProduct{T,M} <: TensorMapping{T,1,1}
+    h::T
+    quadratureClosure::NTuple{M,T}
+    size::Tuple{Int}
 end
 
-LazyTensors.domain_size(H::DiagonalInnerProduct, range_size::NTuple{1,Integer}) = range_size
+function DiagonalInnerProduct(g::EquidistantGrid{1}, quadratureClosure)
+    return DiagonalInnerProduct(spacing(g)[1], quadratureClosure, size(g))
+end
+
+LazyTensors.range_size(H::DiagonalInnerProduct) = H.size
+LazyTensors.domain_size(H::DiagonalInnerProduct) = H.size
 
 function LazyTensors.apply(H::DiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index) where T
     return @inbounds apply(H, v, I)
 end
 
 function LazyTensors.apply(H::DiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index{Lower}) where T
-    return @inbounds H.h*H.closure[Int(I)]*v[Int(I)]
+    return @inbounds H.h*H.quadratureClosure[Int(I)]*v[Int(I)]
 end
 
 function LazyTensors.apply(H::DiagonalInnerProduct{T},v::AbstractVector{T}, I::Index{Upper}) where T
     N = length(v);
-    return @inbounds H.h*H.closure[N-Int(I)+1]v[Int(I)]
+    return @inbounds H.h*H.quadratureClosure[N-Int(I)+1]*v[Int(I)]
 end
 
 function LazyTensors.apply(H::DiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index{Interior}) where T
--- a/src/SbpOperators/quadrature/inverse_diagonal_inner_product.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/quadrature/inverse_diagonal_inner_product.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -1,22 +1,30 @@
 export InverseDiagonalInnerProduct, closuresize
 """
-    InverseDiagonalInnerProduct{Dim,T<:Real,M} <: TensorOperator{T,1}
+    InverseDiagonalInnerProduct{Dim,T<:Real,M} <: TensorMapping{T,1,1}
 
 Implements the inverse diagonal inner product operator `Hi` of as a 1D TensorOperator
 """
-struct InverseDiagonalInnerProduct{T<:Real,M} <: TensorOperator{T,1}
-    h_inv::T # The reciprocl grid spacing could be included in the stencil already. Preferable?
-    closure::NTuple{M,T}
-    #TODO: Write a nice constructor
+struct InverseDiagonalInnerProduct{T<:Real,M} <: TensorMapping{T,1,1}
+    h_inv::T
+    inverseQuadratureClosure::NTuple{M,T}
+    size::Tuple{Int}
 end
 
+function InverseDiagonalInnerProduct(g::EquidistantGrid{1}, quadratureClosure)
+    return InverseDiagonalInnerProduct(inverse_spacing(g)[1], 1 ./ quadratureClosure, size(g))
+end
+
+LazyTensors.range_size(Hi::InverseDiagonalInnerProduct) = Hi.size
+LazyTensors.domain_size(Hi::InverseDiagonalInnerProduct) = Hi.size
+
+
 function LazyTensors.apply(Hi::InverseDiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index{Lower}) where T
-    return @inbounds Hi.h_inv*Hi.closure[Int(I)]*v[Int(I)]
+    return @inbounds Hi.h_inv*Hi.inverseQuadratureClosure[Int(I)]*v[Int(I)]
 end
 
 function LazyTensors.apply(Hi::InverseDiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index{Upper}) where T
     N = length(v);
-    return @inbounds Hi.h_inv*Hi.closure[N-Int(I)+1]v[Int(I)]
+    return @inbounds Hi.h_inv*Hi.inverseQuadratureClosure[N-Int(I)+1]*v[Int(I)]
 end
 
 function LazyTensors.apply(Hi::InverseDiagonalInnerProduct{T}, v::AbstractVector{T}, I::Index{Interior}) where T
--- a/src/SbpOperators/quadrature/inverse_quadrature.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/quadrature/inverse_quadrature.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -2,14 +2,27 @@
 """
     InverseQuadrature{Dim,T<:Real,M,K} <: TensorMapping{T,Dim,Dim}
 
-Implements the inverse quadrature operator `Qi` of Dim dimension as a TensorOperator
+Implements the inverse quadrature operator `Qi` of Dim dimension as a TensorMapping
 The multi-dimensional tensor operator consists of a tuple of 1D InverseDiagonalInnerProduct
 tensor operators.
 """
-struct InverseQuadrature{Dim,T<:Real,M} <: TensorOperator{T,Dim}
+struct InverseQuadrature{Dim,T<:Real,M} <: TensorMapping{T,Dim,Dim}
     Hi::NTuple{Dim,InverseDiagonalInnerProduct{T,M}}
 end
 
+
+function InverseQuadrature(g::EquidistantGrid{Dim}, quadratureClosure) where Dim
+    Hi = ()
+    for i ∈ 1:Dim
+        Hi = (Hi..., InverseDiagonalInnerProduct(restrict(g,i), quadratureClosure))
+    end
+
+    return InverseQuadrature(Hi)
+end
+
+LazyTensors.range_size(Hi::InverseQuadrature) = getindex.(range_size.(Hi.Hi),1)
+LazyTensors.domain_size(Hi::InverseQuadrature) = getindex.(domain_size.(Hi.Hi),1)
+
 LazyTensors.domain_size(Qi::InverseQuadrature{Dim}, range_size::NTuple{Dim,Integer}) where Dim = range_size
 
 function LazyTensors.apply(Qi::InverseQuadrature{Dim,T}, v::AbstractArray{T,Dim}, I::Vararg{Index,Dim}) where {T,Dim}
--- a/src/SbpOperators/quadrature/quadrature.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/src/SbpOperators/quadrature/quadrature.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -6,11 +6,21 @@
 The multi-dimensional tensor operator consists of a tuple of 1D DiagonalInnerProduct H
 tensor operators.
 """
-struct Quadrature{Dim,T<:Real,M} <: TensorOperator{T,Dim}
+struct Quadrature{Dim,T<:Real,M} <: TensorMapping{T,Dim,Dim}
     H::NTuple{Dim,DiagonalInnerProduct{T,M}}
 end
 
-LazyTensors.domain_size(Q::Quadrature{Dim}, range_size::NTuple{Dim,Integer}) where {Dim} = range_size
+function Quadrature(g::EquidistantGrid{Dim}, quadratureClosure) where Dim
+    H = ()
+    for i ∈ 1:Dim
+        H = (H..., DiagonalInnerProduct(restrict(g,i), quadratureClosure))
+    end
+
+    return Quadrature(H)
+end
+
+LazyTensors.range_size(H::Quadrature) = getindex.(range_size.(H.H),1)
+LazyTensors.domain_size(H::Quadrature) = getindex.(domain_size.(H.H),1)
 
 function LazyTensors.apply(Q::Quadrature{Dim,T}, v::AbstractArray{T,Dim}, I::Vararg{Index,Dim}) where {T,Dim}
     error("not implemented")
--- a/test/testDiffOps.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/test/testDiffOps.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -6,268 +6,193 @@
 using Sbplib.LazyTensors
 
 @testset "DiffOps" begin
-
-@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)
+#
+# @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)
+#
+#     # 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
 
-    # 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
-
-end
\ No newline at end of file
--- a/test/testGrids.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/test/testGrids.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -4,9 +4,23 @@
 @testset "Grids" begin
 
 @testset "EquidistantGrid" begin
-    @test EquidistantGrid(4,0,1) isa EquidistantGrid
-    @test dimension(EquidistantGrid(4,0,1)) == 1
-    @test EquidistantGrid(4,0,1) == EquidistantGrid((4,),(0,),(1,))
+    @test EquidistantGrid(4,0.0,1.0) isa EquidistantGrid
+    @test EquidistantGrid(4,0.0,8.0) isa EquidistantGrid
+    @test dimension(EquidistantGrid(4,0.0,1.0)) == 1
+    @test EquidistantGrid(4,0.0,1.0) == EquidistantGrid((4,),(0.0,),(1.0,))
+
+    g = EquidistantGrid((5,3), (0.0,0.0), (2.0,1.0))
+    @test restrict(g, 1) == EquidistantGrid(5,0.0,2.0)
+    @test restrict(g, 2) == EquidistantGrid(3,0.0,1.0)
+
+    g = EquidistantGrid((2,5,3), (0.0,0.0,0.0), (2.0,1.0,3.0))
+    @test restrict(g, 1) == EquidistantGrid(2,0.0,2.0)
+    @test restrict(g, 2) == EquidistantGrid(5,0.0,1.0)
+    @test restrict(g, 3) == EquidistantGrid(3,0.0,3.0)
+    @test restrict(g, 1:2) == EquidistantGrid((2,5),(0.0,0.0),(2.0,1.0))
+    @test restrict(g, 2:3) == EquidistantGrid((5,3),(0.0,0.0),(1.0,3.0))
+    @test restrict(g, [1,3]) == EquidistantGrid((2,3),(0.0,0.0),(2.0,3.0))
+    @test restrict(g, [2,1]) == EquidistantGrid((5,2),(0.0,0.0),(1.0,2.0))
 end
 
 end
--- a/test/testLazyTensors.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/test/testLazyTensors.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -12,20 +12,14 @@
     @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),(Index{Unknown}(0),Index{Unknown}(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::Vararg{Index{<:Region},R}) where {T,R,D} = :apply
     LazyTensors.apply_transpose(m::DummyMapping{T,R,D}, v, I::Vararg{Index{<:Region},D}) where {T,R,D} = :apply_transpose
 
-    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
+    LazyTensors.range_size(m::DummyMapping{T,R,D}) where {T,R,D} = :range_size
+    LazyTensors.domain_size(m::DummyMapping{T,R,D}) where {T,R,D} = :domain_size
 
     m = DummyMapping{Float64,2,3}()
     I = Index{Unknown}(0)
@@ -35,19 +29,21 @@
     @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
+    @test range_size(m') == :domain_size
+    @test domain_size(m') == :range_size
 end
 
 @testset "TensorApplication" begin
-    struct DummyMapping{T,R,D} <: TensorMapping{T,R,D} end
+    struct SizeDoublingMapping{T,R,D} <: TensorMapping{T,R,D}
+        domain_size::NTuple{D,Int}
+    end
 
-    LazyTensors.apply(m::DummyMapping{T,R,D}, v, i::Vararg{Index{<:Region},R}) 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
+    LazyTensors.apply(m::SizeDoublingMapping{T,R,D}, v, i::Vararg{Index{<:Region},R}) where {T,R,D} = (:apply,v,i)
+    LazyTensors.range_size(m::SizeDoublingMapping) = 2 .* m.domain_size
+    LazyTensors.domain_size(m::SizeDoublingMapping) = m.domain_size
 
 
-    m = DummyMapping{Int, 1, 1}()
+    m = SizeDoublingMapping{Int, 1, 1}((3,))
     v = [0,1,2]
     @test m*v isa AbstractVector{Int}
     @test size(m*v) == 2 .*size(v)
@@ -63,28 +59,31 @@
     @test_broken BoundsError == (m*m*v)[0]
     @test_broken BoundsError == (m*m*v)[7]
 
-    m = DummyMapping{Int, 2, 1}()
+    m = SizeDoublingMapping{Int, 2, 1}((3,))
     @test_throws MethodError m*ones(Int,2,2)
     @test_throws MethodError m*m*v
 
-    m = DummyMapping{Float64, 2, 2}()
+    m = SizeDoublingMapping{Float64, 2, 2}((3,3))
     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}
+    struct ScalingOperator{T,D} <: TensorMapping{T,D,D}
         λ::T
+        size::NTuple{D,Int}
     end
 
     LazyTensors.apply(m::ScalingOperator{T,D}, v, I::Vararg{Index,D}) where {T,D} = m.λ*v[I]
+    LazyTensors.range_size(m::ScalingOperator) = m.size
+    LazyTensors.domain_size(m::ScalingOperator) = m.size
 
-    m = ScalingOperator{Int,1}(2)
+    m = ScalingOperator{Int,1}(2,(3,))
     v = [1,2,3]
     @test m*v isa AbstractVector
     @test m*v == [2,4,6]
 
-    m = ScalingOperator{Int,2}(2)
+    m = ScalingOperator{Int,2}(2,(2,2))
     v = [[1 2];[3 4]]
     @test m*v == [[2 4];[6 8]]
     I = (Index{Upper}(2),Index{Lower}(1))
@@ -94,14 +93,16 @@
 @testset "TensorMapping binary operations" begin
     struct ScalarMapping{T,R,D} <: TensorMapping{T,R,D}
         λ::T
+        range_size::NTuple{R,Int}
+        domain_size::NTuple{D,Int}
     end
 
     LazyTensors.apply(m::ScalarMapping{T,R,D}, v, I::Vararg{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
+    LazyTensors.range_size(m::ScalarMapping) = m.domain_size
+    LazyTensors.domain_size(m::ScalarMapping) = m.range_size
 
-    A = ScalarMapping{Float64,1,1}(2.0)
-    B = ScalarMapping{Float64,1,1}(3.0)
+    A = ScalarMapping{Float64,1,1}(2.0, (3,), (3,))
+    B = ScalarMapping{Float64,1,1}(3.0, (3,), (3,))
 
     v = [1.1,1.2,1.3]
     for i ∈ eachindex(v)
@@ -112,8 +113,8 @@
         @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,))
+    @test range_size(A+B) == range_size(A) == range_size(B)
+    @test domain_size(A+B) == domain_size(A) == domain_size(B)
 end
 
 @testset "LazyArray" begin
@@ -192,4 +193,23 @@
     @test_throws DimensionMismatch v1 + v2
 end
 
-end
\ No newline at end of file
+@testset "LazyFunctionArray" begin
+    @test LazyFunctionArray(i->i^2, (3,)) == [1,4,9]
+    @test LazyFunctionArray((i,j)->i*j, (3,2)) == [
+        1 2;
+        2 4;
+        3 6;
+    ]
+
+    @test size(LazyFunctionArray(i->i^2, (3,))) == (3,)
+    @test size(LazyFunctionArray((i,j)->i*j, (3,2))) == (3,2)
+
+    @inferred LazyFunctionArray(i->i^2, (3,))[2]
+
+    @test_throws BoundsError LazyFunctionArray(i->i^2, (3,))[4]
+    @test_throws BoundsError LazyFunctionArray((i,j)->i*j, (3,2))[4,2]
+    @test_throws BoundsError LazyFunctionArray((i,j)->i*j, (3,2))[2,3]
+
+end
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/testRegionIndices.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -0,0 +1,6 @@
+using Sbplib.RegionIndices
+using Test
+
+@testset "RegionIndices" begin
+	@test_broken false
+end
--- a/test/testRegionInidices.jl	Wed Sep 30 21:09:21 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-using Sbplib.RegionIndices
-using Test
-
-@testset "RegionIndices" begin
-	@test_broken false
-end
--- a/test/testSbpOperators.jl	Wed Sep 30 21:09:21 2020 +0200
+++ b/test/testSbpOperators.jl	Wed Sep 30 21:09:35 2020 +0200
@@ -33,7 +33,7 @@
     g = EquidistantGrid((101,), (0.0,), (L,))
     h_inv = inverse_spacing(g)
     h = 1/h_inv[1];
-    Dₓₓ = SecondDerivative(h_inv[1],op.innerStencil,op.closureStencils,op.parity)
+    Dₓₓ = SecondDerivative(g,op.innerStencil,op.closureStencils)
 
     f0(x::Float64) = 1.
     f1(x::Float64) = x
@@ -50,7 +50,7 @@
     v4 = evalOn(g,f4)
     v5 = evalOn(g,f5)
 
-    @test Dₓₓ isa TensorOperator{T,1} where T
+    @test Dₓₓ isa TensorMapping{T,1,1} where T
     @test Dₓₓ' isa TensorMapping{T,1,1} where T
 
     # TODO: Should perhaps set tolerance level for isapporx instead?
@@ -77,12 +77,8 @@
     Lx = 1.5
     Ly = 3.2
     g = EquidistantGrid((102,131), (0.0, 0.0), (Lx,Ly))
+    L = Laplace(g, op.innerStencil, op.closureStencils)
 
-    h_inv = inverse_spacing(g)
-    h = spacing(g)
-    D_xx = SecondDerivative(h_inv[1],op.innerStencil,op.closureStencils,op.parity)
-    D_yy = SecondDerivative(h_inv[2],op.innerStencil,op.closureStencils,op.parity)
-    L = Laplace((D_xx,D_yy))
 
     f0(x::Float64,y::Float64) = 2.
     f1(x::Float64,y::Float64) = x+y
@@ -100,7 +96,7 @@
     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
     @test L' isa TensorMapping{T,2,2} where T
 
     # TODO: Should perhaps set tolerance level for isapporx instead?
@@ -118,6 +114,8 @@
     @test all(abs.((collect(L*v3) - v1)) .<= equalitytol)
     e4 = collect(L*v4) - v2
     e5 = collect(L*v5) - v5ₓₓ
+
+    h = spacing(g)
     @test sqrt(prod(h)*sum(collect(e4.^2))) <= accuracytol
     @test sqrt(prod(h)*sum(collect(e5.^2))) <= accuracytol
 end
@@ -126,11 +124,10 @@
     op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
     L = 2.3
     g = EquidistantGrid((77,), (0.0,), (L,))
-    h = spacing(g)
-    H = DiagonalInnerProduct(h[1],op.quadratureClosure)
+    H = DiagonalInnerProduct(g,op.quadratureClosure)
     v = ones(Float64, size(g))
 
-    @test H isa TensorOperator{T,1} where T
+    @test H isa TensorMapping{T,1,1} where T
     @test H' isa TensorMapping{T,1,1} where T
     @test sum(collect(H*v)) ≈ L
     @test collect(H*v) == collect(H'*v)
@@ -142,14 +139,11 @@
     Ly = 5.2
     g = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly))
 
-    h = spacing(g)
-    Hx = DiagonalInnerProduct(h[1],op.quadratureClosure);
-    Hy = DiagonalInnerProduct(h[2],op.quadratureClosure);
-    Q = Quadrature((Hx,Hy))
+    Q = Quadrature(g, op.quadratureClosure)
 
     v = ones(Float64, size(g))
 
-    @test Q isa TensorOperator{T,2} where T
+    @test Q isa TensorMapping{T,2,2} where T
     @test Q' isa TensorMapping{T,2,2} where T
     @test sum(collect(Q*v)) ≈ (Lx*Ly)
     @test collect(Q*v) == collect(Q'*v)
@@ -159,14 +153,11 @@
     op = readOperator(sbp_operators_path()*"d2_4th.txt",sbp_operators_path()*"h_4th.txt")
     L = 2.3
     g = EquidistantGrid((77,), (0.0,), (L,))
-    h = spacing(g)
-    H = DiagonalInnerProduct(h[1],op.quadratureClosure)
-
-    h_i = inverse_spacing(g)
-    Hi = InverseDiagonalInnerProduct(h_i[1],1 ./ op.quadratureClosure)
+    H = DiagonalInnerProduct(g, op.quadratureClosure)
+    Hi = InverseDiagonalInnerProduct(g,op.quadratureClosure)
     v = evalOn(g, x->sin(x))
 
-    @test Hi isa TensorOperator{T,1} where T
+    @test Hi isa TensorMapping{T,1,1} where T
     @test Hi' isa TensorMapping{T,1,1} where T
     @test collect(Hi*H*v)  ≈ v
     @test collect(Hi*v) == collect(Hi'*v)
@@ -178,20 +169,13 @@
     Ly = 8.2
     g = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly))
 
-    h = spacing(g)
-    Hx = DiagonalInnerProduct(h[1], op.quadratureClosure);
-    Hy = DiagonalInnerProduct(h[2], op.quadratureClosure);
-    Q = Quadrature((Hx,Hy))
-
-    hi = inverse_spacing(g)
-    Hix = InverseDiagonalInnerProduct(hi[1], 1 ./ op.quadratureClosure);
-    Hiy = InverseDiagonalInnerProduct(hi[2], 1 ./ op.quadratureClosure);
-    Qinv = InverseQuadrature((Hix,Hiy))
+    Q = Quadrature(g, op.quadratureClosure)
+    Qinv = InverseQuadrature(g, op.quadratureClosure)
     v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y)
 
-    @test Qinv isa TensorOperator{T,2} where T
+    @test Qinv isa TensorMapping{T,2,2} where T
     @test Qinv' isa TensorMapping{T,2,2} where T
-    @test collect(Qinv*Q*v)  ≈ v
+    @test collect(Qinv*(Q*v)) ≈ v
     @test collect(Qinv*v) == collect(Qinv'*v)
 end
 #