diff --git a/src/discretization.jl b/src/discretization.jl
new file mode 100644
index 0000000000000000000000000000000000000000..bb81ed76d280a396cf4f68e42127233a91d5920b
--- /dev/null
+++ b/src/discretization.jl
@@ -0,0 +1,146 @@
+Base.@kwdef struct Discretization{
+                      T_Grid<:AbstractGrid,
+                      T_Cache<:AbstractCache,
+                      T_Equation<:AbstractEquation,
+                      T_HRSC<:Maybe{AbstractHRSC}}
+
+  grid::T_Grid
+  cache::T_Cache
+  equation::T_Equation
+  hrsc::T_HRSC = nothing
+end
+
+
+struct ParameterDict <: AbstractDict
+  provided::FlatDict{String,Any}
+  consumed::FlatDict{String,Any}
+  defaulted::FlatDict{String,Any}
+
+  function ParameterDict(d::AbstractDict)
+    flat_d = FlatDict(d)
+    consumed = FlatDict()
+    return new(flat_d, consumed)
+  end
+end
+
+
+function Base.getindex(pdb::ParameterDict, key)
+  val = get(pdb.provided, key, nothing)
+  if !isnothing(val)
+    if !haskey(pdb.consumed, val)
+      pdb.consumed[key] = val
+    end
+    return val
+  end
+  throw()
+end
+
+
+function Base.get(pdb::ParameterDict, key, default)
+end
+
+
+function summarize(pdb::ParameterDict)
+  # compare pdb.provided, pdb.consumed, pdb.defaulted and display which
+  # parameters were unused, which ones were consumed and which ones
+  # falled back to defaults
+  # the output should also be logable to a file
+end
+
+
+# check if d.provided has key.
+# if it has one, copy it over to d.consumed and return it, if not copy the default
+# over to d.consumed and return it
+Base.get(d::ParameterDict, key, default) = get(d.provided, key, default)
+Base.length(d::ParameterDict) = length(d.provided)
+# similar to get but throw if key is not found in d.provided
+Base.getindex(d::ParameterDict, elements...) = getindex(d.dict, elements...)
+# disable this?
+Base.setindex!(d::ParameterDict, value, elements...) = setindex!(d.dict, value, elements...)
+# iteration interface 
+Base.iterate(d::ParameterDict) = iterate(d.dict)
+Base.iterate(d::ParameterDict, index) = iterate(d.dict, index)
+# can this work with arbitrary dicts?
+Base.merge(d::ParameterDict, others::AbstractDict...) = ParameterDict(merge(d.dict, others...))
+# disable if we disable setindex!
+Base.pop!(d::ParameterDict, key, default) = pop!(d.dict, key, default)
+
+
+
+
+
+
+#######################################################################
+#                                main                                 #
+#######################################################################
+
+
+parameters_db = ParameterDict(parameters)
+
+# factories, provided by dg1d, except equation should be provided by a project
+grid     = make_Grid(parameters_db)
+cache    = make_Cache(grid)
+hrsc     = make_HRSC(parameters_db)
+equation = make_Equation(parameters_db)
+
+# we need an aggregation type
+disc = Discretization(grid, cache, hrsc, equation)
+
+# above might be combined into
+# and a project must then only implement make_Equation
+disc = Discretization(parameters_db)
+
+# prepare initial data, boundary conditions
+# this should also be implemented by a project
+# and just like in Makie, this should be merely are elaborated expression of what
+# problem we want to solve, e.g. which type of initial data we want and what boundary conditions
+# no actual computations should occur here
+problem = Problem(disc, parameters_db)
+
+# evolution
+runner = make_runner(disc, problem, parameters_db)
+
+summarize(runner, parameters_db)
+run(runner)
+
+function make_Runner(disc, problem, parameters_db)
+  # prepare evolution
+  rhs       = make_rhs(disc, problem, parameters_db)
+  timestep  = make_timestep(disc, problem, parameters_db)
+  callbacks = make_callbacks(disc, problem, parameters_db)
+  # () -> dg1d.evolve(rhs,
+end
+
+
+function make_rhs(disc, problem, parameters_db)
+  (u, t) -> dg1d.rhs(u, t, disc, problem, disc.equation)
+end
+
+
+function make_timestep(disc, problem, parameters_db)
+  (u, t) -> dg1d.timestep(u, t, disc, problem, disc.equation)
+end
+
+
+function dg1d.rhs(u, t, disc, problem, equation::ScalarEquation)
+  rhs(u, t, disc, problem, disc.hrsc)
+end
+
+
+function make_callbacks(disc, problem, parameters_db)
+  # setup rhs_callbacks, plot_callback, save_callback,
+  # id_smoother_callback, aligned_savecallback, restep_callback
+end
+
+
+# what needs to be implemented by a project will be
+# (I think we can't use factories because we cannot dispatch on any parameters then, or?)
+# - dg1d.make_equation
+# - dg1d.make_probelm
+# - dg1d.make_rhs
+# - dg1d.make_timestep
+# - dg1d.make_callback
+
+# things to figure out
+# - how to register variables consistently and in such a way that different
+# components can depend on one another? do we need another accepts/returns mechanism?
diff --git a/src/grid.jl b/src/grid.jl
new file mode 100644
index 0000000000000000000000000000000000000000..c621ef08fddd21b7bb117eb03d09206cc5b8af95
--- /dev/null
+++ b/src/grid.jl
@@ -0,0 +1,281 @@
+#######################################################################
+#                           Type definition                           #
+#######################################################################
+
+
+struct Grid{N_Dim}
+	tree::Tree{N_Dim}
+	tensorbasis_cache::ObjectCache{Int64}
+	# interpolation_cache::ObjectCache{Int64}
+	bases::Vector{TensorBasis{N_Dim}}
+	ioffset::Vector{Int64}
+	ioffset_boundary::Vector{Int64}
+	boxes::Vector{Box{N_Dim}}
+	# parameters::Dict{String,Any}
+
+	function Grid{N_Dim}(ns, ps, extends, periodic, blueprint_basis) where N_Dim
+
+		tree = Tree{N_Dim}(ns..., periodic=periodic)
+		boxes = place_boxes(tree, extends...)
+
+		tensorbasis_cache = ObjectCache{Int64,SpectralElement}(blueprint_basis)
+		bases = [ TensorBasis([ tensorbasis_cache[p] for p in ps]...) for _ in 1:n_cells(tree) ]
+		ioffset, ioffset_boundary = Int64[1], Int64[1]
+		for b in bases
+			push!(ioffset,					ioffset[end]+n_points(b))
+			push!(ioffset_boundary, ioffset_boundary[end]+n_points_boundary(b))
+		end
+
+		grid = new{N_Dim}(tree, tensorbasis_cache, bases, ioffset, ioffset_boundary, boxes)
+
+		return grid
+	end
+end
+const Grid1d = Grid{Cell1d}
+const Grid2d = Grid{Cell2d}
+const Grid3d = Grid{Cell3d}
+
+
+#######################################################################
+#                             Constructor                             #
+#######################################################################
+
+
+function Grid(parameters=Dict())
+	ns = query(Vector{Int64}, parameters, "ns", default=[5,5,5])
+	ps = query(Vector{Int64}, parameters, "ps", default=[ 5 for _ = 1:length(ns) ])
+	@assert all(ns .> 0)
+	@assert all(ps .== ps[1]) # equal polynomial orders in each direction for now
+	N_Dim, n_ps = length(ns), length(ps)
+	@assert N_Dim == n_ps """
+		Unable to determine dimension of grid. Please provide the same number of parameters for
+		- cells per dimension 'ns'
+		- polynomial orders per dimensions 'ps'
+	"""
+
+	extends = query(Vector{Float64}, parameters, "extends", default=repeat([-1.0,1.0], N_Dim))
+	@assert length(extends) == 2*N_Dim """
+		Cannot determine domain size from 'extends' = $(extends).
+		Require one pair of extrema per dimension.
+	"""
+	periodic = query(Vector{Bool}, parameters, "periodic", default=[ true for _ in 1:N_Dim ])
+	quadrature = query(String, parameters, "quadrature", default="LGL")
+
+	coordinates = query(String, parameters, "domaintype", default="cartesian")
+
+	blueprint(n) = SpectralElement(n, Symbol(quadrature))
+
+	return Grid{N_Dim}(ns, ps, (extends[2*(i-1)+1:2*(i-1)+2] for i = 1:N_Dim), tuple(periodic...), blueprint)
+end
+
+
+#######################################################################
+#                              Utilities                              #
+#######################################################################
+
+
+n_points(g::Grid) = sum(n_points.(g.tensorbasis))
+
+
+function Base.show(io::IO, grid::Grid)
+	ncs = ncells(grid)
+	npts = npoints(grid)
+	nbdrypts = npoints_boundary(grid)
+	print("""
+				$(typeof(grid))
+					# cells                $(ncells(grid))
+					# points               $(npoints(grid))
+					# boundary points      $(npoints_boundary(grid))
+				""")
+end
+
+
+
+function sample_coordinates(grid::Grid{1})
+	xs   = Float64[]
+	Js   = Float64[]
+	detJ = Float64[]
+	npts = n_points(grid)
+	sizehint!(xs, npts)
+	sizehint!(Js, npts)
+	sizehint!(detJ, npts)
+	for (box, basis) in zip(grid.boxes, grid.tensorbasis)
+		(xmin, xmax), = extrema(basis)
+		map_x(z) = (xmax-xmin)/2 * z + (xmax+xmin)/2
+		box_xs = [ map_x(zi) for z in basis[Cart1d.X].z ]
+		box_Js = (xmax-xmin)/2 * ones(n_points(basis))
+		box_detJ = (xmax-xmin)/2
+		append!(xs, box_xs)
+		append!(Js, box_Js)
+		append!(detJ, box_detJ)
+	end
+	return [ xs ], [ Js ], detJ
+end
+
+
+function sample_coordinates(grid::Grid{2})
+	xs, ys = Float64[], Float64[]
+	Js_Xx, Js_Xy, Js_Yx, Js_Yy = [ Float64[] for _ = 1:4 ]
+	detJ = Float64
+	npts = n_points(grid)
+	sizehint!(xs, npts)
+	sizehint!(ys, npts)
+	sizehint!(Js_Xx, npts)
+	sizehint!(Js_Xy, npts)
+	sizehint!(Js_Yx, npts)
+	sizehint!(Js_Yy, npts)
+	sizehint!(detJ, npts)
+	for (box, basis) in zip(grid.boxes, grid.tensorbasis)
+		npts = n_points(basis)
+		(xmin, xmax), (ymin, ymax) = extrema(basis)
+		map_x(z) = (xmax-xmin)/2 * z + (xmax+xmin)/2
+		map_y(z) = (ymax-ymin)/2 * z + (ymax+ymin)/2
+		box_xs = [ map_x(zi) for z in basis[Cart2d.X].z ]
+		box_ys = [ map_y(zi) for z in basis[Cart2d.Y].z ]
+		box_xs, box_ys = mesh_coordinates(box_xs, box_ys)
+		box_Js_Xx = (xmax-xmin)/2 * ones(npts)
+		box_Js_Xy = zeros(npts)
+		box_Js_Yx = zeros(npts)
+		box_Js_Yy = (ymax-ymin)/2 * ones(npts)
+		box_detJ = @. box_Js_Xx * box_Js_Yy - box_Js_Xy * box_Js_Yx
+		push!(xs, box_xs)
+		push!(ys, box_ys)
+		push!(Js_Xx, box_Js_Xx)
+		push!(Js_Xy, box_Js_Xy)
+		push!(Js_Yx, box_Js_Yx)
+		push!(Js_Yy, box_Js_Yy)
+		push!(detJ, box_detJ)
+	end
+	return [ xs, ys ], [ Js_Xx, Js_Xy, Js_Yx, Js_Yy ], detJ
+end
+
+
+function sample_coordinates(grid::Grid{3})
+	xs, ys, zs = Float64[], Float64[], Float64[]
+	Js_Xx, Js_Xy, Js_Xz, Js_Yx, Js_Yy, Js_Yz, Js_Zx, Js_Zy, Js_Zz = [ Float64[] for _ = 1:9 ]
+	detJ = Float64[]
+	npts = n_points(grid)
+	sizehint!(xs, npts)
+	sizehint!(ys, npts)
+	sizehint!(zs, npts)
+	sizehint!(Js_Xx, npts)
+	sizehint!(Js_Xy, npts)
+	sizehint!(Js_Xz, npts)
+	sizehint!(Js_Yx, npts)
+	sizehint!(Js_Yy, npts)
+	sizehint!(Js_Yz, npts)
+	sizehint!(Js_Zx, npts)
+	sizehint!(Js_Zy, npts)
+	sizehint!(Js_Zz, npts)
+	sizehint!(detJ, npts)
+	for (box, basis) in zip(grid.boxes, grid.tensorbasis)
+		npts = n_points(basis)
+		(xmin, xmax), (ymin, ymax), (zmin, zmax) = extrema(basis)
+		map_x(z) = (xmax-xmin)/2 * z + (xmax+xmin)/2
+		map_y(z) = (ymax-ymin)/2 * z + (ymax+ymin)/2
+		map_z(z) = (zmax-zmin)/2 * z + (zmax+zmin)/2
+		box_xs = [ map_x(zi) for z in basis[Cart3d.X].z ]
+		box_ys = [ map_y(zi) for z in basis[Cart3d.Y].z ]
+		box_zs = [ map_z(zi) for z in basis[Cart3d.Z].z ]
+		box_xs, box_ys, box_zs = mesh(box_xs, box_ys, box_zs)
+		box_Js_Xx = (xmax-xmin)/2 * ones(npts)
+		box_Js_Xy = zeros(npts)
+		box_Js_Xz = zeros(npts)
+		box_Js_Yx = zeros(npts)
+		box_Js_Yy = (ymax-ymin)/2 * ones(npts)
+		box_Js_Yz = zeros(npts)
+		box_Js_Zx = zeros(npts)
+		box_Js_Zy = zeros(npts)
+		box_Js_Zz = (zmax-zmin)/2 * ones(npts)
+		box_detJ = ( @. box_Js_Xx * box_Js_Yy * box_Zz + box_Js_Xy * box_Js_Yz * box_Js_Zx +
+									  box_Js_Xz * box_Js_Yx * box_Zy - box_Js_Xz * box_Js_Yy * box_Js_Zx -
+										box_Js_Xy * box_Js_Yx * box_Zz - box_Js_Xx * box_Js_Yz * box_Js_Zy )
+		append!(xs, box_xs)
+		append!(ys, box_ys)
+		append!(zs, box_zs)
+		push!(Js_Xx, box_Js_Xx)
+		push!(Js_Xy, box_Js_Xy)
+		push!(Js_Xz, box_Js_Xz)
+		push!(Js_Yx, box_Js_Yx)
+		push!(Js_Yy, box_Js_Yy)
+		push!(Js_Yz, box_Js_Yz)
+		push!(Js_Zx, box_Js_Zx)
+		push!(Js_Zy, box_Js_Zy)
+		push!(Js_Zz, box_Js_Zz)
+		push!(detJ, box_detJ)
+	end
+	return [ xs, ys, zs ], [ Js_Xx, Js_Xy, Js_Xz, Js_Yx, Js_Yy, Js_Yz, Js_Zx, Js_Zy, Js_Zz ], detJ
+end
+
+
+function mesh_coordinates(x, y)
+	nx, ny = length(x), length(y)
+	xx, yy = [ zeros(nx*ny) for _ in 1:2 ]
+	for i in 1:nx, j in 1:ny
+		ij = i + (j-1) * nx
+		xx[ij] = x[i]
+		yy[ij] = y[j]
+	end
+	return xx, yy
+end
+
+
+function mesh_coordinates(x,y,z)
+	nx, ny, nz = length(x), length(y), length(z)
+	xx, yy, zz = [ zeros(nx*ny*nz) for _ in 1:3 ]
+	for i in 1:nx, j in 1:ny, k in 1:nz
+		ijk = i + (j-1) * nx + (k-1) * nx * ny
+		xx[ijk] = x[i]
+		yy[ijk] = y[j]
+		zz[ijk] = z[k]
+	end
+	return xx, yy, zz
+end
+
+
+function register_cartesian_coordinates!(grid::Grid)
+	xys, components_Js, detJ = sample_coordinates(grid)
+	add_variables!(grid.cache, static_variables = [ xs..., Js..., detJ ])
+end
+
+
+function regrid!(grid::Grid)
+	# duplicate tree, bases and boxes and update them
+	# regrid cached variables inplace using the above copies
+	# e.g. perform various interpolation coarsening steps in different types of variables
+end
+
+
+function eachcell(grid::Grid, vars::RefBlockVector...)
+	((block(v, i) for i in 1:n_cells(grid)) for v in vars) 
+end
+
+
+# function range(grid::Grid, i)
+#   range(grid.ioffset[i], length=n_points(grid.bases[i]))
+# end
+
+
+# struct Boundary{T<:Grid{N_Dim}}
+#   grid::T
+#   dir::Cart{N_Dim}.Direction
+# end
+#
+#
+# function eachcell(g::Boundary{Grid}, vars...)
+#   ((view(v, range(grid, i, dir)) for v in vars) for i in 1:n_cells(grid))
+# end
+#
+#
+# function range(cell::Grid, i, dir)
+#   range(bdry.grid.ioffset[i]+offset_boundary(grid.bases[i], dir),
+#         length=n_points_boundary(grid.bases[i], dir))
+# end
+
+
+# for (ai,bi,ci) in eachcell(grid,a,b,c)
+#   ci .= ai .+ bi
+# end
+# for (ai,bi,ci) in eachcell(Boundary(grid,Cart2d.Xmin),a,b,c)
+#   ci .= ai .+ bi
+# end
diff --git a/src/grid.jl.old b/src/grid.jl.old
new file mode 100644
index 0000000000000000000000000000000000000000..5a79761715d3f36ed259138a6e76b4e4644e635a
--- /dev/null
+++ b/src/grid.jl.old
@@ -0,0 +1,293 @@
+@enum CellType::UInt8 VolumeCell BoundaryCell NoCell
+
+@enum Directions1d::UInt8 Left=1 Right
+@enum Directions2d::UInt8 Left=1 Right Top Bottom
+@enum Directions3d::UInt8 Left=1 Right Top Bottom Front Back
+
+Base.to_index(d::Directions1d) = Int(d)
+Base.to_index(d::Directions2d) = Int(d)
+Base.to_index(d::Directions3d) = Int(d)
+
+struct Cell{N_Dim,T_Neighbours}
+	type::CellType
+	index::Int64
+	level::Int64
+	neighbours::T_Neighbours
+	# hlevel::Int64
+	# plevel::Int64
+	# tlevel::Int64
+end
+
+const Cell1d = Cell{1,Neighbours1d}
+const Cell2d = Cell{2,Neighbours2d}
+const Cell3d = Cell{3,Neighbours3d}
+
+# struct CellId
+#   index::Int64
+#   type::CellType
+# end
+
+
+struct Tile{N_Dim,CellId}
+  cellids::NTuple{N_Dim,CellId}
+end
+const Tile1d{CellId} = Tile{1,CellId}
+const Tile2d{CellId} = Tile{2,CellId}
+
+
+is_boundary_cell(cid::CellId)          = cid.type == BoundaryCell
+is_volume_cell(cid::CellId)            = cid.type == VolumeCell
+is_no_cell(cid::CellId)                = cid.type == NoCell
+is_boundary_cell(tcid::Tile{CellId}) = any(Tuple(cid.type == BoundaryCell for cid in tcid.cellids))
+is_volume_cell(tcid::Tile{CellId})   = any(Tuple(cid.type == VolumeCell for cid in tcid.cellids))
+is_no_cell(tcid::Tile{CellId})       = any(Tuple(cid.type == NoCell for cid in tcid.cellids))
+
+
+Base.length(cid::CellId) = 1
+Base.length(tcid::Tile1d) = 2
+Base.length(tcid::Tile2d) = 4
+
+
+const Neighbours1d = MVector{2,CellId}
+const Neighbours2d = MVector{4,Union{CellId,Tile1d}}
+const Neighbours3d = MVector{6,Union{CellId,Tile2d}}
+
+
+# # TODO Why not FieldVector? That would also allow indexing it.
+# mutable struct Neighbours1d
+#   left::CellId
+#   right::CellId
+# end
+#
+#
+# mutable struct Neighbours2d
+#   left::Union{CellId,Tile{CellId}}
+#   right::Union{CellId,Tile{CellId}}
+#   top::Union{CellId,Tile{CellId}}
+#   bottom::Union{CellId,Tile{CellId}}
+# end
+#
+#
+# mutable struct Neighbours3d
+#   left::Union{CellId,Tile{CellId}}
+#   right::Union{CellId,Tile{CellId}}
+#   top::Union{CellId,Tile{CellId}}
+#   bottom::Union{CellId,Tile{CellId}}
+#   front::Union{CellId,Tile{CellId}}
+#   back::Union{CellId,Tile{CellId}}
+# end
+
+
+# h-refinement
+has_tiles(nghbr::Neighbours1d) = false
+# has_tilled(nghbr::Neighbours) = any( #= member isa Tile for memeber in nghbr =#)
+find_tiles(nghbr::Neighbours1d) = nothing
+# findtilled(nghbr::Neighbours) = [ (side, id) for (side, id) in nghbr ]
+
+
+mutable struct Basis{N_Dim}
+  # TODO MVector instead of NTuple avoids making Basis mutable
+  elements::NTuple{N_Dim,SpectralElement}
+end
+Base.ndims(::Type{Basis{N_Dim}}) where N_Dim = N_Dim
+Base.ndims(b::Basis) = ndims(typeof(b))
+Base.size(basis::Basis) = Tuple( el.Npts for el in basis.elements )
+npoints(basis::Basis) = prod(size(basis))
+npoints_boundary(basis::Basis{1}) = 2
+function npoints_boundary(basis::Basis{2})
+  sz = size(basis)
+  return sum(2 .* sz) - 4 # remove double counted corners
+end
+function npoints_boundary(basis::Basis{3})
+  sz = size(basis)
+  return sum(2 .* (sz[1]*sz[2], sz[1]*sz[3], sz[2]*sz[3])) - 16 # remove double counted corners
+end
+
+
+mutable struct Cell{N_Dim,T_Neighbours}
+  # TODO replace (this,offset,isboundary) with CellId?
+  this::Int64
+  offset::Int64
+  neighbours::T_Neighbours
+  isboundary::Bool
+  basis::Basis{N_Dim}
+	
+	# TODO incomplete constructor really needed?
+  Cell{N_Dim,T_Neighbours}() where {N_Dim,T_Neighbours} = new{N_Dim,T_Neighbours}()
+end
+Base.ndims(::Type{Cell{N_Dim,T_Neighbours}}) where {N_Dim,T_Neighbours} = N_Dim
+npoints(cell::Cell) = npoints(cell.basis)
+npoints_boundary(cell::Cell) = npoints_boundary(cell.basis)
+nneighbours(::Cell{N_Dim}) where N_Dim = 2*N_Dim
+
+
+const Cell1d = Cell{1,Neighbours1d}
+const Cell2d = Cell{2,Neighbours2d}
+const Cell3d = Cell{3,Neighbours3d}
+
+
+indices(cell::Cell) = range(cell.offset; length=npoints(cell))
+
+
+struct BoundaryIndices{N_Dim,T_Cell}
+  cell::T_Cell
+end
+const BoundaryIndices1d = BoundaryIndices{ndims(Cell1d),Cell1d}
+const BoundaryIndices2d = BoundaryIndices{ndims(Cell2d),Cell2d}
+const BoundaryIndices3d = BoundaryIndices{ndims(Cell3d),Cell3d}
+function boundary_indices(cell::Cell1d, side::Symbol)
+  @unpack offset = cell
+  if side === :left
+    return range(offset; length=1)
+  elseif side === :right
+    return range(offset+npoints(cell); length=1)
+  else
+    error()
+  end
+end
+function boundary_indices(cell::Cell2d, side::Symbol)
+  @unpack offset = cell
+  sz = size(cell)
+  if side === :left
+    return range(offset; length=sz[1])
+  elseif side === :right
+    return range(offset+npoints(cell)-sz[1]; length=sz[1])
+  elseif side === :top
+    return range(offset; length=sz[2], step=sz[1])
+  elseif side === :bottom
+    return range(offset+sz[1]; length=sz[2], step=sz[1])
+  else
+    error()
+  end
+end
+function boundary_indices(cell::Cell3d, side::Symbol)
+  @unpack offset = cell
+  sz = size(cell)
+  if side === :left
+    return range(offset; length=sz[1]*sz[2], step=1)
+  elseif side === :right
+    return range(offset+npoints(cell)-sz[1]*sz[2]; length=sz[1]*sz[2])
+  elseif side === :top
+    return range(offset; length=sz[1]*sz[2], step=sz[1])
+  elseif side === :bottom
+    return range(offset+sz[1]; length=sz[1]*sz[2], step=sz[1])
+  elseif side === :front
+    return range(offset+sz[1]*sz[2]; length=sz[2]*sz[3], step=sz[1]*sz[2])
+  elseif side === :back
+    return range(offset+npoints(cell)-sz[1]*sz[2]; length=sz[2]*sz[3], step=sz[1]*sz[2])
+  else
+    error()
+  end
+end
+
+
+struct Grid{T_Cell}
+  cells::Vector{T_Cell}
+end
+
+
+const Grid1d = Grid{Cell1d}
+const Grid2d = Grid{Cell2d}
+const Grid3d = Grid{Cell3d}
+
+
+function Grid(ncells)
+  grid = Grid1d([ Cell1d() for _ in 1:ncells ])
+  @unpack cells = grid
+  # connect cells
+  for (i, cell) in enumerate(cells)
+    i_left, i_right = periodize(i-1, ncells), periodize(i+1, ncells)
+    neighbours = Neighbours1d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+function Grid(ncells_x, ncells_y)
+  ncells = ncells_x * ncells_y
+  grid = Grid2d([ Cell2d() for _ in 1:ncells ])
+  @unpack cells = grid
+  CI = CartesianIndices((ncells_x, ncells_y))
+  for (i, cell) in enumerate(cells)
+    ix, iy = Tuple(CI[i])
+    i_left, i_right = periodize(ix-1, ncells_x), periodize(ix+1, ncells_x)
+    i_top, i_bottom = periodize(iy-1, ncells_y), periodize(iy+1, ncells_y)
+    neighbours = Neighbours2d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell),
+                               CellId(i_top, VolumeCell), CellId(i_bottom, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+function Grid(ncells_x, ncells_y, ncells_z)
+  ncells = ncells_x * ncells_y * ncells_z
+  grid = Grid3d([ Cell3d() for _ in 1:ncells ])
+  @unpack cells = grid
+  CI = CartesianIndices((ncells_x, ncells_y, ncells_z))
+  for (i, cell) in enumerate(cells)
+    ix, iy, iz = Tuple(CI[i])
+    i_left, i_right = periodize(ix-1, ncells_x), periodize(ix+1, ncells_x)
+    i_top, i_bottom = periodize(iy-1, ncells_y), periodize(iy+1, ncells_y)
+    i_front, i_back = periodize(iz-1, ncells_z), periodize(iz+1, ncells_z)
+    neighbours = Neighbours3d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell),
+                               CellId(i_top, VolumeCell), CellId(i_bottom, VolumeCell),
+                               CellId(i_front, VolumeCell), CellId(i_back, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+cells(grid::Grid) = grid.cells
+Base.ndims(grid::Grid) = ndims(eltype(cells(grid)))
+ncells(grid::Grid) = length(cells(grid))
+npoints(grid::Grid) = sum(npoints(c) for c in cells(grid))
+npoints_boundary(grid::Grid1d) = 2
+npoints_boundary(grid::Grid) = sum(npoints_boundary(c) for c in cells(grid))
+nneighbours(grid::Grid) = sum(nneighbours(c) for c in cells(grid))
+
+
+const _CACHE_SpectralElements = Dict{Int64,SpectralElement}()
+
+
+function make_SpectralElement(; args...)
+  N = args[:N]
+  if haskey(_CACHE_SpectralElements, N)
+    return _CACHE_SpectralElements[N]
+  end
+  el = SpectralElement(values(args)...)
+  _CACHE_SpectralElements[N] = el
+  return el
+end
+
+
+function Base.fill!(grid::Grid, basis::Basis)
+  @assert ndims(basis) == ndims(grid)
+  npts = npoints(basis)
+  offset = 1
+  for cell in cells(grid)
+    setproperty!(cell, :basis, basis)
+    setproperty!(cell, :offset, offset)
+    offset += npts
+  end
+end
+
+
+function Base.show(io::IO, grid::Grid)
+  ncs = ncells(grid)
+  npts = npoints(grid)
+  nbdrypts = npoints_boundary(grid)
+  print("""
+        $(typeof(grid))
+          # cells                $(ncells(grid))
+          # points               $(npoints(grid))
+          # boundary points      $(npoints_boundary(grid))
+        """)
+end
+
+# TODO Show methods for cell and basis.
diff --git a/src/ideas/cache.jl b/src/ideas/cache.jl
new file mode 100644
index 0000000000000000000000000000000000000000..d8ba1dbb307dafe1cec94a9e8c36a7f7717b7137
--- /dev/null
+++ b/src/ideas/cache.jl
@@ -0,0 +1,144 @@
+export VariableCache, register_variables!, add_variables!, wrap_variables!
+
+
+#######################################################################
+#                                Types                                #
+#######################################################################
+
+
+"""
+    VariableVariableCache
+
+Caching object to bundle variables of different kinds.
+"""
+struct VariableCache{T_Grid<:AbstractGrid}
+  grid::T_Grid
+  varlist::Dict{Symbol,Symbol}
+  dynamic::Variables
+  static::Variables
+  cell::Variables
+  rhs::Variables
+  # interface::Variables # should we added this to separate inferface and boundary values
+  # would allow to easily distinguish boundary values that are computed from boundary conditions
+  # and/or neighboring grids (e.g. sphered cubes etc).
+  boundary::Variables
+  domain::Variables
+end
+
+
+function VariableCache(grid::Grid)
+  verbose = false
+  varlist        = Dict{Symbol, Symbol}()
+  npts, npts_bdry     = n_points(grid), n_points_boundary(grid)
+  ncells              = n_cells(grid)
+  dynamic             = Variables(npts, verbose=verbose)
+  static              = Variables(npts, verbose=verbose)
+  cell                = Variables(ncells, verbose=verbose)
+  rhs                 = Variables(npts, verbose=verbose)
+  boundary            = Variables(npts_bdry, verbose=verbose)
+  domain              = Variables(1, verbose=verbose)
+  return VariableCache(grid, varlist, dynamic, static, cell, rhs, boundary, domain)
+end
+
+
+#######################################################################
+#                               Methods                               #
+#######################################################################
+
+
+@inline UnPack.unpack(x::VariableCache, ::Val{f}) where {f} = get_variable(x, Val(f))
+
+
+function get_variable(varcache::VariableCache, ::Val{f}) where {f}
+  field = v.varlist[f]
+  return getproperty(varcache, field)
+end
+
+
+
+
+# TODO Can we avoid this and instead overload Unpack.@unpack or so?
+function Base.propertynames(::VariableCache)
+  fnames = fieldnames(VariableCache)
+  filter(f -> !in(f, [ :varlist ]), fnames)
+end
+
+
+"""
+    wrap_variables!(C::VariableCache, u=nothing, du=nothing)
+
+Wrap fields `dynamic, rhs` around data `u, du` that is not owned by `C`.
+"""
+function wrap_variables!(C::VariableCache; u=nothing, du=nothing)
+  !isnothing(u) && set!(C.dynamic, u)
+  !isnothing(du) && set!(C.rhs, du)
+  return
+end
+
+
+
+function register_variables!(C::VariableCache;
+    dynamic = [], rhs = [], static = [], cell = [], bdry = [], domain = [])
+
+  register!(C.dynamic,  Symbol.(dynamic)...)
+  register!(C.rhs,      Symbol.(rhs)...)
+  register!(C.static,   Symbol.(static)...)
+  register!(C.cell,     Symbol.(cell)...)
+  register!(C.boundary, Symbol.(boundary)...)
+  register!(C.domain,   Symbol.(domain)...)
+
+  empty!(cache.varlist)
+  push_names_field!(cache.varlist, dg1d.names(cache.dynamic), :dynamic)
+  push_names_field!(cache.varlist, dg1d.names(cache.rhs), :rhs)
+  push_names_field!(cache.varlist, dg1d.names(cache.static), :static)
+  push_names_field!(cache.varlist, dg1d.names(cache.cell), :cell)
+  push_names_field!(cache.varlist, dg1d.names(cache.boundary), :boundary)
+  push_names_field!(cache.varlist, dg1d.names(cache.domain), :domain)
+
+  return
+end
+
+
+push_names_field!(dict, names, field) = for name in names; push!(dict, name => field); end
+
+
+function Base.show(io::IO, C::VariableCache)
+  variables_list = collect(keys(C.fields_to_variables))
+  N_variables = length(variables_list)
+  if N_variables == 0 
+    @warn "No variables registered!"
+    return
+  end
+  names_list = collect(values(C.fields_to_variables))
+  permute_idx = sortperm(variables_list)
+  variables_list, names_list = variables_list[permute_idx], names_list[permute_idx]
+  max_rows = maximum(length.(names_list))
+  data = Matrix{String}(undef, max_rows, N_variables)
+  fill!(data, "")
+  for (idx, names) in enumerate(names_list)
+    N_names = length(names)
+    data[1:N_names,idx] .= string.(names)
+  end
+  # sort!(data, dims=1)
+  pretty_table(data, header=variables_list, crop=:none)
+end
+
+
+function resize!(varcache::VariableCache)
+  npts = n_points(varcache)
+  npts_bdry = n_points_boundary(varcache)
+  ncells = n_cells(varcache)
+  resize!(varcache.dynamic)
+  resize!(varcache.rhs, npts)
+  resize!(varcache.static, npts)
+  resize!(varcache.cell, ncells)
+  resize!(varcache.boundary, npts_bdry)
+  # domain unchanged, because it only contains single values
+end
+
+
+function regrid_dynamic!(varcache::VariableCache)
+  for name in varcache.dynamic
+    
+  end
+end
diff --git a/src/ideas/grhd_rhs.jl b/src/ideas/grhd_rhs.jl
new file mode 100644
index 0000000000000000000000000000000000000000..3ddf94219967e676fd78753e0f05e6531bb26c57
--- /dev/null
+++ b/src/ideas/grhd_rhs.jl
@@ -0,0 +1,136 @@
+struct Grid{N_Dim,T_SpectralElement}
+  mesh::TreeMesh{N_Dim}
+  bases_cache::ObjectCache{Int64,T_SpectralElement}
+  bases::Vector{TensorBasis{N_Dim}}
+  cache::Cache
+end
+
+
+@unpack variable"u, v, w, fu, fv, fw" = cache
+@unpack dynamic"u, v, w" = cache
+@unpack static"u, v, w" = cache
+@unpack boundary"u, v, w" = cache
+@unpack cell"u, v, w" = cache
+# register_variable.(cache, dynamic"u, v, w")
+->
+getproperty(getproperty(cache, :dynamic), :u)
+(alternative) ->
+getproperty(cache, Dynamic(:u))
+(intermediate) ->
+@unpack Var(:u), Var(:v), Var(:w), ... = cache
+
+
+function Grid(tm::TreeMesh{N_Dim},
+    grid_extends::Vararg{Tuple{Float64,Float64},N_Dim}) where N_Dim
+
+  ncells = n_cells(tm)
+  bases = [ TensorBasis(plevels(c)) for c in cells(tm) ]
+end
+
+
+
+const Grid1d = Grid{Cell1d}
+const Grid2d = Grid{Cell2d}
+const Grid3d = Grid{Cell3d}
+
+
+function Grid(ncells)
+  grid = Grid1d([ Cell1d() for _ in 1:ncells ])
+  @unpack cells = grid
+  # connect cells
+  for (i, cell) in enumerate(cells)
+    i_left, i_right = periodize(i-1, ncells), periodize(i+1, ncells)
+    neighbours = Neighbours1d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+function Grid(ncells_x, ncells_y)
+  ncells = ncells_x * ncells_y
+  grid = Grid2d([ Cell2d() for _ in 1:ncells ])
+  @unpack cells = grid
+  CI = CartesianIndices((ncells_x, ncells_y))
+  for (i, cell) in enumerate(cells)
+    ix, iy = Tuple(CI[i])
+    i_left, i_right = periodize(ix-1, ncells_x), periodize(ix+1, ncells_x)
+    i_top, i_bottom = periodize(iy-1, ncells_y), periodize(iy+1, ncells_y)
+    neighbours = Neighbours2d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell),
+                               CellId(i_top, VolumeCell), CellId(i_bottom, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+function Grid(ncells_x, ncells_y, ncells_z)
+  ncells = ncells_x * ncells_y * ncells_z
+  grid = Grid3d([ Cell3d() for _ in 1:ncells ])
+  @unpack cells = grid
+  CI = CartesianIndices((ncells_x, ncells_y, ncells_z))
+  for (i, cell) in enumerate(cells)
+    ix, iy, iz = Tuple(CI[i])
+    i_left, i_right = periodize(ix-1, ncells_x), periodize(ix+1, ncells_x)
+    i_top, i_bottom = periodize(iy-1, ncells_y), periodize(iy+1, ncells_y)
+    i_front, i_back = periodize(iz-1, ncells_z), periodize(iz+1, ncells_z)
+    neighbours = Neighbours3d( CellId(i_left, VolumeCell), CellId(i_right, VolumeCell),
+                               CellId(i_top, VolumeCell), CellId(i_bottom, VolumeCell),
+                               CellId(i_front, VolumeCell), CellId(i_back, VolumeCell) )
+    setproperty!(cell, :neighbours, neighbours)
+    setproperty!(cell, :this, i)
+  end
+  return grid
+end
+
+
+cells(grid::Grid) = grid.cells
+Base.ndims(grid::Grid) = ndims(eltype(cells(grid)))
+ncells(grid::Grid) = length(cells(grid))
+npoints(grid::Grid) = sum(npoints(c) for c in cells(grid))
+npoints_boundary(grid::Grid1d) = 2
+npoints_boundary(grid::Grid) = sum(npoints_boundary(c) for c in cells(grid))
+nneighbours(grid::Grid) = sum(nneighbours(c) for c in cells(grid))
+
+
+const _CACHE_SpectralElements = Dict{Int64,SpectralElement}()
+
+
+function make_SpectralElement(; args...)
+  N = args[:N]
+  if haskey(_CACHE_SpectralElements, N)
+    return _CACHE_SpectralElements[N]
+  end
+  el = SpectralElement(values(args)...)
+  _CACHE_SpectralElements[N] = el
+  return el
+end
+
+
+function Base.fill!(grid::Grid, basis::Basis)
+  @assert ndims(basis) == ndims(grid)
+  npts = npoints(basis)
+  offset = 1
+  for cell in cells(grid)
+    setproperty!(cell, :basis, basis)
+    setproperty!(cell, :offset, offset)
+    offset += npts
+  end
+end
+
+
+function Base.show(io::IO, grid::Grid)
+  ncs = ncells(grid)
+  npts = npoints(grid)
+  nbdrypts = npoints_boundary(grid)
+  print("""
+        $(typeof(grid))
+          # cells                $(ncells(grid))
+          # points               $(npoints(grid))
+          # boundary points      $(npoints_boundary(grid))
+        """)
+end
+
+# TODO Show methods for cell and basis.
diff --git a/src/ideas/mesh2d.jl b/src/ideas/mesh2d.jl
new file mode 100644
index 0000000000000000000000000000000000000000..9773e0fd8e45bf708bec998b61b6ed0915b00b65
--- /dev/null
+++ b/src/ideas/mesh2d.jl
@@ -0,0 +1,372 @@
+export Mesh, make_Mesh
+
+
+#######################################################################
+#                                Types                                #
+#######################################################################
+
+
+abstract type AbstractMesh{Ndim} end
+
+@doc """
+    Mesh{N}
+
+Object that bundles grid and operators needed for modeling a Discontinuous Galerkin 
+scheme in N dimensions.
+"""
+struct Mesh{Ndim} <: AbstractMesh{Ndim}
+  elements::NTuple{Ndim,SpectralElement} # collection of spectral element operators
+  Ks::NTuple{Ndim,Int64}                 # number of cells
+  bounds::NTuple{Ndim,NTuple{Ndim,Float64}}
+  CFL::Float64                           # Courant Friedrichs Lewy factor
+  Ls::NTuple{Ndim,Float64}               # domain length
+  dls::NTuple{Ndim,Float64}              # equidistant subcell length
+  xs::NTuple{Ndim,Matrix{Float64}}       # grid points
+  invjacs::NTuple{Ndim,Matrix{Float64}}  # inverse jacobian of coordinate transformation
+                                         # from computational [-1,1] onto physical grid [xmin,xmax]
+
+  function Mesh{Ndim}(; Ns, Ks, bounds, CFL, quadrature_method)
+    elements = [ SpectralElement(N, Symbol(quadrature_method)) for N in Ns ]
+    zs       = [ el.z for el in elements ]
+    bounds   = [ extrema(el.z) for el in elements ]
+    Ls       = [ diff(b) for b in bounds ]
+    dls      = [ L/K for (L,K) in zip(Ls, Ks) ]
+    as       = [ [ b[1] + dl * idx for idx = 0 : K-1 ] for (b,dl,K) in zip(bounds, Ls, Ks) ]
+    bs       = [ a.+dl for (a,dl) in zip(as,dls) ]
+    Npts     = [ length(z) for z in zs ]
+    xs       = [ Matrix{Float64}(undef, Np, K) for (Np,K) in zip(Npts, Ks) ]
+    invjacs  = [ similar(x) for x in xs ]
+    for (Np, K, a, b, z, x, invjac) in zip (Npts, Ks, as, bs, zs, xs, invjacs)
+      for i=1:Np, j=1:K
+        aj = a[j]
+        bj = b[j]
+        zi = z[i]
+        x[i,j] = 0.5 * ((bj - aj) * zi + (aj + bj))
+        invjac[i,j] = 2.0 / (bj - aj)
+      end
+      # sanitize cell end points to have same x coordinate at common interfaces
+      for j=1:K-1
+        xl = x[end,j]
+        xr = x[1,j+1]
+        xmean = 0.5 * (xl + xr)
+        x[end,j] = xmean
+        x[1,j+1] = xmean
+      end
+    end
+    return new{Ndim}(elements, Tuple(Ks), bounds, CFL, Ls, dls, xs, invjacs)
+  end
+end
+
+
+#######################################################################
+#                              Factories                              #
+#######################################################################
+
+
+make_Mesh(prms)      = Mesh(; parse_parameters(Mesh, prms)...)
+make_Mesh(; args...) = make_Mesh(FlatDict(args))
+
+
+######################################################################
+#                            Parameters                              #
+######################################################################
+
+
+const Mesh1d = Mesh{1}
+const Mesh2d = Mesh{2}
+const Mesh3d = Mesh{3}
+
+
+function parse_parameters(::Type{Mesh{Ndim}}, prms)
+  N = query(Vector{Int64}, prms, "N", default=repeat([5],Ndim))
+  K = query(Vector{Int64}, prms, "K", default=repeat([10],Ndim))
+  CFL = query(Float64, prms, "CFL")
+  quadrature_method = query(String, prms, "quadrature_method", default="LGL",
+                            options=["LGL", "GLGL", "LGL_lumped"])
+  range = query(Vector{Vector{Float64}}, prms, "range", default=repeat([[-1.0,1.0]], Ndim))
+  @assert all(N .> 0) "'N > 0' required"
+  @assert all(K .> 0) "'K > 0' required"
+  @assert CFL > 0 "'CFL > 0' required"
+  @assert all(length.(range) .== 2) "'range' must be a tuple"
+  # @assert all(range[1] < xrange[2] "'xrange' limits must be ordered"
+  @assert quadrature_method in ["LGL", "GLGL", "LGL_lumped"] "'quadrature_method' invalid"
+  return N, K, CFL, quadrature_method, range
+end
+
+
+function parse_parameters(::Type{Mesh1d}, prms)
+  N, K, CFL, quadrature_method, range = parse_parameters(Mesh, prms)
+
+
+end
+
+
+function parse_parameters(::Type{Mesh1d}, prms)
+  Ns = query(Int64, prms, "N", default=[5])
+  Ks = query(Int64, prms, "K", default=[20])
+  CFL = query(Float64, prms, "CFL", default=0.5)
+  quadrature_method = query(String, prms, "quadrature_method", default="LGL",
+                            options=["LGL", "GLGL", "LGL_lumped"])
+  xrange = query(Vector{Float64}, prms, "xrange", default = [-1.0,1.0])
+  # sanitize
+  @assert Ns > 0 "'N > 0' required"
+  @assert Ks > 0 "'K > 0' required"
+  @assert CFL > 0 "'CFL > 0' required"
+  @assert length(xrange) == 2 "'xrange' must be a tuple"
+  @assert xrange[1] < xrange[2] "'xrange' limits must be ordered"
+  @assert quadrature_method in ["LGL", "GLGL", "LGL_lumped"] "'quadrature_method' invalid"
+  bounds = (xrange,)
+  return (; Ns, Ks, CFL, quadrature_method, bounds)
+end
+
+
+function parse_parameters(::Type{Mesh2d}, prms)
+  Ns = query(NTuple{2,Int64}, prms, "N", default=(5,5))
+  Ks = query(NTuple{2,Int64}, prms, "K", default=(20,20))
+  CFL = query(Float64, prms, "CFL", default=0.5)
+  quadrature_method = query(String, prms, "quadrature_method", default="LGL",
+                            options=["LGL", "GLGL", "LGL_lumped"])
+  xrange = query(Vector{Float64}, prms, "xrange", default = [-1.0,1.0])
+  yrange = query(Vector{Float64}, prms, "yrange", default = [-1.0,1.0])
+  # sanitize
+  @assert all(Ns .> 0) "'N > 0' required"
+  @assert all(Ks .> 0) "'K > 0' required"
+  @assert CFL > 0 "'CFL > 0' required"
+  @assert length(xrange) == 2 "'xrange' must be a tuple"
+  @assert length(yrange) == 2 "'yrange' must be a tuple"
+  @assert xrange[1] < xrange[2] "'xrange' limits must be ordered"
+  @assert yrange[1] < yrange[2] "'yrange' limits must be ordered"
+  @assert quadrature_method in ["LGL", "GLGL", "LGL_lumped"] "'quadrature_method' invalid"
+  bounds = (xrange, yrange)
+  return (; Ns, Ks, CFL, quadrature_method, bounds)
+end
+
+
+function parse_parameters(::Type{Mesh3d}, prms)
+  Ns = query(NTuple{3,Int64}, prms, "N", default=(5,5,5))
+  Ks = query(NTuple{3,Int64}, prms, "K", default=(20,20,20))
+  CFL = query(Float64, prms, "CFL", default=0.5)
+  quadrature_method = query(String, prms, "quadrature_method", default="LGL",
+                            options=["LGL", "GLGL", "LGL_lumped"])
+  xrange = query(Vector{Float64}, prms, "xrange", default = [-1.0,1.0])
+  yrange = query(Vector{Float64}, prms, "yrange", default = [-1.0,1.0])
+  zrange = query(Vector{Float64}, prms, "zrange", default = [-1.0,1.0])
+  # sanitize
+  @assert all(Ns .> 0) "'N > 0' required"
+  @assert all(Ks .> 0) "'K > 0' required"
+  @assert CFL > 0 "'CFL > 0' required"
+  @assert length(xrange) == 2 "'xrange' must be a tuple"
+  @assert length(yrange) == 2 "'yrange' must be a tuple"
+  @assert length(zrange) == 2 "'zrange' must be a tuple"
+  @assert xrange[1] < xrange[2] "'xrange' limits must be ordered"
+  @assert yrange[1] < yrange[2] "'yrange' limits must be ordered"
+  @assert zrange[1] < zrange[2] "'zrange' limits must be ordered"
+  @assert quadrature_method in ["LGL", "GLGL", "LGL_lumped"] "'quadrature_method' invalid"
+  bounds = (xrange, yrange, zrange)
+  return (; Ns, Ks, CFL, quadrature_method, bounds)
+end
+
+
+#######################################################################
+#                               Methods                               #
+#######################################################################
+
+
+Base.broadcastable(mesh::AbstractMesh)                  = Ref(mesh)
+Base.ndims(mesh::AbstractMesh{N})                       = N
+coordinates(mesh::AbstractMesh)                         = mesh.xs
+bounds(mesh::AbstractMesh)                              = mesh.bounds
+Base.length(mesh::AbstractMesh{N})::NTuple{N,Int64}     = prod(length.(coordinates(mesh)))
+n_points(mesh::AbstractMesh{N})                         = mesh.element.Npts .+ 1
+n_cells(mesh::AbstractMesh{N})                          = mesh.Ks
+layout(mesh::AbstractMesh{N})::NTuple{N,Tuple{Int,Int}} = (n_points(mesh), n_cells(mesh))
+
+
+"""
+    cell_bdrys(mesh::AbstarctMesh)
+
+Return coordinates of cell boundaries from a `mesh`.
+"""
+function cell_bdrys(mesh::Mesh{N}) where N
+  bdrys = NTuple{N,Vector{Float64}}([ Vector{Float64}(undef, K+1) for K in Ks ])
+  for (idx,xi) in enumerate(coordinates(mesh))
+    bdrys[idx] .= vcat(x[1,:], [x[end,end]])
+  end
+  return bdrys
+end
+
+@deprecate extract_cell_bdrys(mesh) cell_bdrys(mesh)
+
+@doc """
+    grid_average(u, mesh::Mesh)
+
+Return the grid average of all cell averages 
+"""
+function grid_average(u, mesh::Mesh1d)
+  @unpack element, dl, L = mesh
+  cellavg = cellwise_average(u, mesh) .* dl
+  avg = sum(cellavg) / L
+  return avg
+end
+
+
+@doc """
+  `cellwise_average(u, mesh::Mesh)`
+
+Return the cellwise average of `u` over `mesh.x`.
+"""
+function cellwise_average(u, mesh::Mesh)
+  @unpack element, invjac, K, dl = mesh
+  @unpack Npts = element
+  v = ones(Npts)
+  avg = zeros(K)
+  for i in 1:K
+    avg[i] = element.iproduct(u[:,i], v)
+  end
+  avg ./= invjac[1,:] * dl # constant Jacobian, constant cell width
+  return avg
+end
+
+
+@doc """
+  `cellwise_inner_product(u, v, mesh::Mesh)`
+
+Return vector of inner products between `u, v` on every cell in `mesh`.
+"""
+function cellwise_inner_product(u, v, mesh::Mesh)
+  @unpack element, K, invjac = mesh
+  @unpack Npts = element
+  L = layout(mesh)
+  mat_u = reshape(view(u,:), L)
+  mat_v = reshape(view(v,:), L)
+  inner_products = [ element.iproduct(mat_u[:,k], mat_v[:,k]) for k = 1:K ]
+  inner_products ./= invjac[1,:] # constant Jacobian
+  return inner_products
+end
+
+
+@doc """
+  `broken_inner_product(u, v, mesh::Mesh)`
+  
+Return sum of all inner products between `u,v` on every cell in `mesh`.
+"""
+function broken_inner_product(u, v, mesh::Mesh)
+  inner_products = cellwise_inner_product(u, v, mesh)
+  total = sum(inner_products[:])
+  return total
+end
+
+
+# function LinearAlgebra.norm(u, mesh::Mesh, order)
+#   @match order begin
+#     1 => norm_L1(u, mesh)
+#     2 => norm_L2(u, mesh)
+#     _ => error("Norm for order = $order not implemented.")
+#   end
+# end
+# norm_L2(u, mesh::Mesh) = sqrt(broken_inner_product(u, u, mesh))
+# norm_L1(u, mesh::Mesh) = broken_inner_product(abs.(u), ones(Float64, size(u)), mesh)
+
+
+@doc """
+Compute local maximum absolute value.
+"""
+function local_maxabs(v::AbstractMatrix, mesh::Mesh) 
+  return vec(maximum(abs.(v), dims=1))
+end
+
+
+@doc """
+Compute local minimum absolute value.
+"""
+function local_maxabs(v::AbstractVector, mesh::Mesh) 
+  return local_maxabs(reshape(v, (mesh.element.Npts, mesh.K)))
+end
+
+
+"""
+    find_nearest(xi, mesh::Mesh1d)
+    find_nearest(xi, mesh::Mesh2d)
+    find_nearest(xi, mesh::Mesh3d)
+
+Find index of node in `mesh` that lies closest to `xi`.
+Returns the index of the outermost point if `xi` falls outside `mesh`.
+"""
+function find_nearest(pt_x, mesh::Mesh1d)
+  px = pt_x
+  x, = coordinates(mesh)
+  min_ix = 1
+  min_distance = abs(x[min_ix] - px)
+  for ix = 2:length(x)
+    distance = abs(x[ix] - px)
+    if distance < min_distance
+      min_distance = distance
+      min_ix = ix
+    end
+  end
+  return (min_ix,)
+end
+function find_nearest(pt_xy, mesh::Mesh2d)
+  px, py = pt_xy
+  xs, ys = coordinates(mesh)
+  min_ix, min_iy = 1, 1
+  min_distance = sqrt( (xs[min_ix]-px)^2 + (ys[min_iy]-py)^2 )
+  for ix = 2:length(x), iy = 2:length(y)
+    distance = sqrt( (xs[ix]-px)^2 + (ys[iy]-py)^2 )
+    if distance < min_distance
+      min_distance = distance
+      min_ix, min_iy = ix, iy
+    end
+  end
+  return (min_ix, min_iy)
+end
+function find_nearest(pt_xyz, mesh::Mesh2d)
+  px, py, pz = pt_xyz
+  xs, ys, zs = coordinates(mesh)
+  min_ix, min_iy, min_iz = 1, 1, 1
+  min_distance = sqrt( (xs[min_ix]-px)^2 + (ys[min_iy]-py)^2 + (zs[min_iz]-pz)^2 )
+  for ix = 2:length(x), iy = 2:length(y), iz = 2:length(iz)
+    distance = sqrt( (xs[ix]-px)^2 + (ys[iy]-py)^2 + (zs[min_iz]-pz)^2 )
+    if distance < min_distance
+      min_distance = distance
+      min_ix, min_iy, min_iz = ix, iy, iz
+    end
+  end
+  return (min_ix, min_iy, min_iz)
+end
+
+
+@deprecate locate_point(xi, mesh::Mesh) find_nearest(xi, mesh::Mesh)
+@deprecate locate_point(xi, mesh::AbstractMesh) find_nearest(xi, mesh::AbstractMesh)
+
+
+"""
+    find_cell_index(xi, mesh::Mesh)
+
+Find the cell index into which coordinate `xi` falls into the `mesh`.
+Note that it returns the first index found in case `xi` falls onto an interface boundary.
+Returns nothing if `xi` lies outside the `mesh`.
+"""
+function find_cell_index(pt_x, mesh::Mesh1d)
+  px = pt_x
+  xbdrys, = cell_bdrys(mesh)
+  return findfirst(1:length(xbdrys)-1) do ix
+    xbdrys[ix] <= px <= xbdrys[ix+1]
+  end
+end
+function find_cell_index(pt_xy, mesh::Mesh2d)
+  px, py = pt_xy
+  xbdrys, ybdrys = cell_bdrys(mesh)
+  return findfirst((1:length(xbdrys)-1,1:length(ybdrys)-1)) do ix, iy
+    xbdrys[ix] <= px <= xbdrys[ix+1] && ybdrys[iy] <= py <= ybdrys[iy+1]
+  end
+end
+function find_cell_index(pt_xyz, mesh::Mesh3d)
+  px, py, pz = pt_xyz
+  xbdrys, ybdrys, zbdrys = cell_bdrys(mesh)
+  return findfirst((1:length(xbdrys)-1,1:length(ybdrys)-1,1:length(zbdrys)-1)) do ix, iy, iz
+    xbdrys[ix] <= px <= xbdrys[ix+1] && 
+    ybdrys[iy] <= py <= ybdrys[iy+1] && 
+    zbdrys[iz] <= pz <= zbdrys[iz+1]
+  end
+end
diff --git a/src/ideas/srhd_rhs.jl b/src/ideas/srhd_rhs.jl
new file mode 100644
index 0000000000000000000000000000000000000000..a928f52de88952a2b1aa9e48b378d0666e8ee0a7
--- /dev/null
+++ b/src/ideas/srhd_rhs.jl
@@ -0,0 +1,321 @@
+Base.@kwdef struct RHS{T_Equation <:SREulerEquation, 
+                       T_RSolver  <:AbstractRiemannSolver,
+                       T_HRSC     <:Maybe{HRSC.AbstractHRSC},
+                       T_TCI      <:Maybe{TCI.AbstractTCI},
+                       T_LDG_RSolver<:Maybe{AbstractRiemannSolver},
+                       T_AV_RSolver<:Maybe{AbstractRiemannSolver}}
+
+  cache::Cache
+  mesh::Mesh
+  equation::T_Equation
+  rsolver::T_RSolver
+  hrsc::T_HRSC               = nothing
+  tci::T_TCI                 = nothing
+  ldg_rsolver::T_LDG_RSolver = nothing
+  av_rsolver::T_AV_RSolver   = nothing
+end
+
+
+#######################################################################
+#                         Register variables                          #
+#######################################################################
+
+
+# Julia limitation/bug: Overloading imported functions without their Module prefix shadows their
+# definition in this module without a warning or error. This does not happen for methods
+# defined in Base.
+# Because of that we prefix with _ below.
+# Cf. https://github.com/JuliaLang/julia/issues/15510
+function _register_variables!(rhs::RHS)
+  register_variables!(rhs.cache,
+    dynamic_variablenames = (:D, :S, :tau,),            # conservatives
+    rhs_variablenames     = (:rhs_D, :rhs_S, :rhs_tau), # rhs of conservatives
+    static_variablenames  = (:flx_D, :flx_S, :flx_tau,  # conservatives' fluxes
+                             :rho, :v, :p, :eps,        # primitives
+                             :max_v, :x,                # maximum charactersitic speed, grid
+                             :E, :Em1,                  # entropy
+                             :flx_E, :flx_Em1),         # entropy flux
+    cell_variablenames    = (:cellmax_v,),
+    global_variablenames  = (:t, :tm1) # time steps
+  )
+  _register_variables!(rhs, rhs.hrsc)
+  _register_variables!(rhs, rhs.tci)
+  @unpack x = get_static_variables(rhs.cache)
+  @. x = rhs.mesh.x[:]
+end
+
+
+function _register_variables!(rhs, tci_or_hrsc::Nothing) end
+
+
+_register_variables!(rhs, hrsc::AbstractHRSC) = 
+  error("_register_variables: Not implemented for hrsc of type '$(typeof(hrsc))'")
+
+_register_variables!(rhs, hrsc::HRSC.AbstractReconstruction) = nothing
+
+
+function _register_variables!(rhs, hrsc::HRSC.AbstractArtificialViscosity)
+  register_variables!(rhs.cache,
+    static_variablenames  = (:ldg_D, :ldg_S, :ldg_tau,              # local DG variable
+                             :flx_ldg_D, :flx_ldg_S, :flx_ldg_tau), # local DG flux
+    cell_variablenames    = (:mu,), # 'one viscosity to rule them all'
+    )
+end
+
+
+function _register_variables!(rhs, hrsc::HRSC.SmoothedArtificialViscosity)
+  _register_variables!(rhs, hrsc.av)
+  register_variables!(rhs.cache,
+    static_variablenames  = (:smoothed_mu,)
+  )
+end
+
+
+function _register_variables!(rhs, tci::TCI.AbstractTCI)
+  register_variables!(rhs.cache, cell_variablenames = (:flag,:D_flag,:S_flag,:tau_flag))
+end
+
+
+function _register_variables!(rhs, tci::TCI.EntropyProduction)
+  register_variables!(rhs.cache, 
+                      cell_variablenames = (:flag,),
+                      static_variablenames = (:EP,), # entropy production
+                      bdry_variablenames   = (:EPjump_lhs, :EPjump_rhs)
+                                              # entropy production due to jumps at interfaces
+  )
+end
+
+
+#######################################################################
+#                              Timestep                               #
+#######################################################################
+
+
+function timestep(state_u, t, rhs::RHS)
+  wrap_dynamic_variables!(rhs.cache, state_u)
+  return timestep(rhs, rhs.hrsc)
+end
+
+
+function timestep(rhs::RHS, hrsc::Maybe{HRSC.AbstractHRSC})
+  @unpack cache, mesh, equation = rhs
+  @unpack max_v = get_static_variables(cache)
+
+  broadcast_volume!(cons2prim, equation, cache)
+  broadcast_volume!(speed, equation, cache)
+  vmax = dg1d.absolute_maximum(max_v)
+  # vmax > 0.99 && error("Unreasonable maximum speed encountered: $vmax")
+  vmax_limit = 0.9999
+  if vmax > vmax_limit
+    @warn "Limiting timestep due to maximum speed exceeding $vmax_limit"
+    vmax = vmax_limit
+  end
+
+  @unpack dl, x, CFL, element = mesh
+  @unpack N = element
+
+  dt = CFL * dl / (N^2 * vmax)
+  return dt
+end
+
+
+function timestep(rhs::RHS, hrsc::HRSC.AbstractArtificialViscosity)
+  @unpack cache, mesh, equation = rhs
+
+  broadcast_volume!(cons2prim, equation, cache)
+  broadcast_volume!(speed, equation, cache)
+  max_v = get_variable(cache, :max_v)
+  vmax = dg1d.absolute_maximum(view(max_v,:))
+  vmax_limit = 0.99
+  if vmax > vmax_limit
+    @warn "Limiting timestep due to maximum speed exceeding $vmax_limit"
+    vmax = vmax_limit
+  end
+  smoothed_mu = get_variable(cache, :smoothed_mu)
+  mumax = dg1d.absolute_maximum(view(smoothed_mu, :))
+
+  @unpack dl, x, CFL, element = mesh
+  @unpack N = element
+
+  dt = CFL / (vmax * N / dl + mumax * N^3 / dl^2)
+  dt_limit = 1e-7
+  if dt < dt_limit
+    @warn "Limiting timestep due to dt being smaller than $dt_limit"
+    dt = dt_limit
+  end
+  return dt
+end
+
+
+#######################################################################
+#                           RHS computation                           #
+#######################################################################
+
+
+function rhs!(state_du, state_u, t, rhs::RHS, bdryconds::BoundaryConditions,
+    ldg_bdryconds::Maybe{BoundaryConditions}, av_bdryconds::Maybe{BoundaryConditions})
+
+  @unpack cache = rhs
+  wrap_dynamic_variables!(cache, state_u)
+  wrap_rhs_variables!(cache, state_du)
+  rhs!(rhs, t, rhs.hrsc, bdryconds, ldg_bdryconds, av_bdryconds)
+end
+
+
+function rhs!(rhs::RHS, t, hrsc::Nothing, bdryconds, ldg_bdryconds, av_bdryconds)
+
+  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack D, S, tau             = get_dynamic_variables(cache)
+  @unpack flx_D, flx_S, flx_tau = get_static_variables(cache)
+  @unpack rhs_D, rhs_S, rhs_tau = get_rhs_variables(cache)
+  @unpack lhs_numflx_D,   rhs_numflx_D,
+          lhs_numflx_S,   rhs_numflx_S,
+          lhs_numflx_tau, rhs_numflx_tau = get_bdry_variables(cache)
+
+  broadcast_volume!(cons2prim, equation, cache)
+  broadcast_volume!(flux, equation, cache)
+  broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+
+  compute_rhs_weak_form!(rhs_D,   flx_D,    lhs_numflx_D,   rhs_numflx_D,   mesh)
+  compute_rhs_weak_form!(rhs_S,   flx_S,    lhs_numflx_S,   rhs_numflx_S,   mesh)
+  compute_rhs_weak_form!(rhs_tau, flx_tau,  lhs_numflx_tau, rhs_numflx_tau, mesh)
+
+  return
+end
+
+
+function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractReconstruction,
+    bdryconds, ldg_bdryconds, av_bdryconds)
+
+  @unpack cache, mesh, equation, rsolver = rhs
+  @unpack D, S, tau                      = get_dynamic_variables(cache)
+  @unpack flx_D, flx_S, flx_tau          = get_static_variables(cache)
+  @unpack rhs_D, rhs_S, rhs_tau          = get_rhs_variables(cache)
+  @unpack lhs_numflx_D,   rhs_numflx_D,
+          lhs_numflx_S,   rhs_numflx_S,
+          lhs_numflx_tau, rhs_numflx_tau = get_bdry_variables(cache)
+  @unpack flag                           = get_cell_variables(cache)
+
+  HRSC.reconstruct!(D,    flag, hrsc, isperiodic=isperiodic(bdryconds), m=1e-10, limit_with_boundaries=false)
+  HRSC.reconstruct!(S,    flag, hrsc, isperiodic=isperiodic(bdryconds), limit_with_boundaries=false)
+  HRSC.reconstruct!(tau,  flag, hrsc, isperiodic=isperiodic(bdryconds), m=1e-10, limit_with_boundaries=false)
+
+  broadcast_volume!(cons2prim, equation, cache)
+  broadcast_volume!(flux, equation, cache)
+  broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
+
+  compute_rhs_weak_form!(rhs_D,   flx_D,    lhs_numflx_D,   rhs_numflx_D,   mesh)
+  compute_rhs_weak_form!(rhs_S,   flx_S,    lhs_numflx_S,   rhs_numflx_S,   mesh)
+  compute_rhs_weak_form!(rhs_tau, flx_tau,  lhs_numflx_tau, rhs_numflx_tau, mesh)
+
+  return
+end
+
+
+function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+    bdryconds, ldg_bdryconds, av_bdryconds)
+
+  @unpack cache, mesh, equation, rsolver,
+          ldg_rsolver, av_rsolver                = rhs
+  @unpack D, S, tau                              = get_dynamic_variables(cache)
+  @unpack flx_D, flx_S, flx_tau,
+          ldg_D, ldg_S, ldg_tau,
+          flx_ldg_D, flx_ldg_S, flx_ldg_tau      = get_static_variables(cache)
+  @unpack rhs_D, rhs_S, rhs_tau                  = get_rhs_variables(cache)
+  # TODO dispose of lhs/rhs numflx since we only need one value per interface
+  # Does a penalty flux violate this assumption?
+  @unpack lhs_numflx_D,   rhs_numflx_D,
+          lhs_numflx_S,   rhs_numflx_S,
+          lhs_numflx_tau, rhs_numflx_tau,
+          lhs_numflx_ldg_D,   rhs_numflx_ldg_D,
+          lhs_numflx_ldg_S,   rhs_numflx_ldg_S,
+          lhs_numflx_ldg_tau, rhs_numflx_ldg_tau = get_bdry_variables(cache)
+
+  ## solve auxiliary equation: q + ∂x u = 0
+  # improved syntax
+  # broadcast_volume!(ldg_flux,               equation,         grid)
+  # broadcast_faces!(mean_flux,               ldg_rsolver,      grid)
+  # broadcast_boundaryconditions!(mean_flux,  ldg_bdryconds(t), grid)
+  broadcast_volume!(ldg_flux, equation, cache)
+  broadcast_faces!(mean_flux, ldg_rsolver, cache, mesh)
+  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds, cache, mesh, t)
+  compute_rhs_weak_form!(ldg_D,   flx_ldg_D,   lhs_numflx_ldg_D,   rhs_numflx_ldg_D,   mesh)
+  compute_rhs_weak_form!(ldg_S,   flx_ldg_S,   lhs_numflx_ldg_S,   rhs_numflx_ldg_S,   mesh)
+  compute_rhs_weak_form!(ldg_tau, flx_ldg_tau, lhs_numflx_ldg_tau, rhs_numflx_ldg_tau, mesh)
+
+  ## compute rhs of regularized equation: ∂t u + ∂x f + ∂x μ q = 0
+  broadcast_volume!(av_flux, equation, cache)
+  broadcast_faces!(mean_flux, av_rsolver, cache, mesh)
+  broadcast_boundaryconditions!(mean_flux, av_bdryconds, cache, mesh, t)
+
+  broadcast_volume!(cons2prim, equation, cache)
+  broadcast_volume!(flux, equation, cache)
+  broadcast_faces!(lax_friedrich_flux, rsolver, cache, mesh)
+  broadcast_boundaryconditions!(lax_friedrich_flux, bdryconds, cache, mesh, t)
+
+  # TODO Remove doubled memeory and calls below by allowing to broadcast with accumalative operations
+  # e.g. broadcast_volume!([av_flux, flux], equation, cache)
+  # which only works if av_flux and flux use the same outputs
+  @. flx_D          += flx_ldg_D
+  @. flx_S          += flx_ldg_S
+  @. flx_tau        += flx_ldg_tau
+  @. lhs_numflx_D   += lhs_numflx_ldg_D
+  @. lhs_numflx_S   += lhs_numflx_ldg_S
+  @. lhs_numflx_tau += lhs_numflx_ldg_tau
+  @. rhs_numflx_D   += rhs_numflx_ldg_D
+  @. rhs_numflx_S   += rhs_numflx_ldg_S
+  @. rhs_numflx_tau += rhs_numflx_ldg_tau
+
+  compute_rhs_weak_form!(rhs_D,   flx_D,   lhs_numflx_D,   rhs_numflx_D,   mesh)
+  compute_rhs_weak_form!(rhs_S,   flx_S,   lhs_numflx_S,   rhs_numflx_S,   mesh)
+  compute_rhs_weak_form!(rhs_tau, flx_tau, lhs_numflx_tau, rhs_numflx_tau, mesh)
+
+  return
+end
+
+
+
+### Desired new interface
+function rhs!(rhs::RHS, t, hrsc::HRSC.AbstractArtificialViscosity,
+    bdryconds, ldg_bdryconds, av_bdryconds)
+
+  @unpack grid, equation, rsolver, ldg_rsolver, av_rsolver = rhs
+
+  @unpack D, S, tau                      = dynamic_variables(cache)
+  @unpack rhs_D, rhs_S, rhs_tau          = rhs_variables(cache)
+  @unpack flx_D, flx_S, flx_tau,
+          ldg_D, ldg_S, ldg_tau,         = static_variables(cache)
+  @unpack numflx_D, numflx_S, numflx_tau = bdry_variables(cache)
+
+  success = broadcast_volume!(cons2prim, equation, grid)
+
+  ## solve auxiliary equation: q + ∂x u = 0
+  # improved syntax
+  broadcast_volume!(ldg_flux, equation, grid)
+  broadcast_faces!(mean_flux, ldg_rsolver, grid)
+  broadcast_boundaryconditions!(mean_flux, ldg_bdryconds(t), grid)
+
+  compute_rhs_weak_form!(ldg_D,   flx_D,   numflx_D,   mesh)
+  compute_rhs_weak_form!(ldg_S,   flx_S,   numflx_S,   mesh)
+  compute_rhs_weak_form!(ldg_tau, flx_tau, numflx_tau, mesh)
+
+  ## compute rhs of regularized equation: ∂t u + ∂x (f + μ q) = 0
+  broadcast_volume!([av_flux, flux], equation, grid)
+  broadcast_faces!([mean_flux, lax_friedrich_flux], [av_rsolver, rsolver], grid)
+  broadcast_boundaryconditions!([mean_flux, lax_friedrich_flux],
+                                [av_bdryconds(t), bdryconds(t)],
+                                grid)
+
+  compute_rhs_weak_form!(rhs_D,   flx_D,   numflx_D,   grid)
+  compute_rhs_weak_form!(rhs_S,   flx_S,   numflx_S,   grid)
+  compute_rhs_weak_form!(rhs_tau, flx_tau, numflx_tau, grid)
+
+  return
+end
+
+### Other considerations
+# - @with_signature: What is the maximum tuple size one can pass around?
+#   If reached should be swap over to SVec?
+#   Or are normal vectors good enough?
+# - @with_signature: Think about @inline addition to macro.
diff --git a/src/refvector.jl b/src/refvector.jl
new file mode 100644
index 0000000000000000000000000000000000000000..11ce1e0fed0eb7229a6299e447204b59d6cf2633
--- /dev/null
+++ b/src/refvector.jl
@@ -0,0 +1,216 @@
+export RefVector, RefBlockVector, block, update!, eachblock
+
+
+#######################################################################
+#                              RefVector                              #
+#######################################################################
+
+
+"""
+    RefVector{T} <: DenseVector{T}
+
+A vector referencing a contiguous subrange of a `Vector{T}`.
+
+---
+
+    RefVector(v::Vector{T}[, offset, n])
+
+Construct a `RefVector` tracking `v` starting at `offset` and length `n`.
+Use `update!(refvec, vec[, offset, n])` to update the reference and/or subrange.
+
+---
+
+# Examples
+
+```julia
+julia> v = randn(100);
+
+julia> refvec = RefVector(v, 10, 50);
+
+julia> v[10] == refvec[1]
+true
+
+julia> length(refvec)
+50
+
+julia> update!(refvec, v, 20, 50);
+
+julia> v[10] == refvec[1]
+false
+```
+"""
+mutable struct RefVector{T} <: DenseVector{T}
+  ref::Base.RefValue{Vector{T}}
+  # so far, vec is actually optional, but it is included for testing to see whether
+  # the ref above can have any influence on performance
+  vec::Vector{T}
+  offset::Int64
+  n::Int64
+
+  function RefVector{T}(v::Vector{T}, offset::Integer, n::Integer) where T
+    checkrange(length(v), offset, n)
+    vec = unsafe_wrap(Vector{T}, pointer(v, offset), n)
+    return new{T}(Ref(v), vec, offset, n)
+  end
+end
+
+RefVector(v::Vector{T}, offset::Integer=1, n::Integer=length(v)) where T =
+  RefVector{T}(v, offset, n)
+RefVector(ref_v::Base.RefValue{Vector{T}}, offset::Integer=1, n::Integer=length(ref[])) where T =
+  RefVector(ref_v[], offset, n)
+
+# array interface
+@inline Base.size(rv::RefVector) = (rv.n,)
+@propagate_inbounds Base.getindex(rv::RefVector, i) = rv.vec[i]
+@propagate_inbounds Base.setindex!(rv::RefVector, val, i) = rv.vec[i] = val
+@inline Base.IndexStyle(A::RefVector) = IndexLinear()
+
+Base.unsafe_convert(::Type{Ptr{T}}, rv::RefVector{T}) where T = pointer(rv.ref[], rv.offset)
+# Base.unsafe_convert(::Type{Ptr{T}}, rv::RefVector{T}) where T = Base.unsafe_convert(Ptr{T}, rv.vec)
+
+# @propagate_inbounds Base.getindex(rv::RefVector, i::Integer) = rv.ref[][i+rv.offset-1]
+# @propagate_inbounds Base.setindex!(rv::RefVector, val, i::Integer) = rv.ref[][i+rv.offset-1] = val
+# Base.iterate(rv::RefVector) = iterate(rv.vec)
+# Base.iterate(rv::RefVector, state) = iterate(rv.vec, state)
+# Base.BroadcastStyle(::Type{RefVector{T}}) where T = Base.BroadcastStyle(Vector{T})
+# Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType})
+# Base.copyto!(dest, bc::Broadcasted{DestStyle}) =
+# function Base.show(io::IOBuffer, rv::RefVector)
+#   println(io, "$(typeof(rv)):")
+#   for v in rv
+#       println(io, v)
+#   end
+#   return
+# end
+
+
+"""
+    update!(rv::RefVector{T}, v::Vector{T} [, offset, n]) where T
+
+Update the reference and/or subrange of `rv` to track `v`.
+"""
+function update!(rv::RefVector{T}, v::Vector{T}, offset=rv.offset, n=rv.n) where T
+  checkrange(length(v), offset, n)
+  rv.ref[] = v
+  rv.vec = unsafe_wrap(Vector{T}, pointer(v, offset), n)
+  rv.offset = offset
+  rv.n = n
+  return
+end
+
+
+function checkrange(N, offset, n)
+  if !(1 <= offset <= N-n+1) || !(0 <= n <= N)
+    error("Cannot reference vector of length $N starting from offset $offset with length $n")
+  end
+end
+
+
+#######################################################################
+#                           RefBlockVector                            #
+#######################################################################
+
+
+"""
+		RefBlockVector{T,T_Vector<:DenseVector{T}} <: DenseVector{T}
+
+A block vector referencing a `T_Vector<:DenseVector{T}`.
+
+---
+
+		RefBlockVector(v::Vector{T}, block_sizes[, offset])
+
+Construct a `RefBlockVector` tracking `v` by blocks of length `block_sizes`,
+optionally starting at `offset`.
+Use `update!(refvec, vec[, block_sizes, offset])` to update the reference and/or blocks.
+
+---
+
+# Examples
+
+```julia
+julia> v = randn(100);
+
+julia> refbvec = RefBlockVector(v, [10, 20, 30], 5)
+
+julia> v[15:25] == block(refbvec, 1)
+true
+
+julia> length(refbvec)
+60
+
+julia> update!(refvec, v, [10, 20], 1);
+
+julia> v[1:10] == block(refvec, 2)
+true
+```
+"""
+mutable struct RefBlockVector{T,T_Vector<:DenseVector{T}} <: DenseVector{T}
+	ref::Base.RefValue{T_Vector}
+	block_sizes::Vector{Int64}
+	blocks::Vector{RefVector{T}}
+	offset::Int64
+  n::Int64
+
+	function RefBlockVector{T,T_Vector}(v::T_Vector, block_sizes::Vector{Int64}, offset::Int64) where {T,T_Vector<:DenseVector{T}}
+		blocks = RefVector{T}[]
+		sizehint!(blocks, length(block_sizes))
+		refvec = new{T,T_Vector}(Ref(v), block_sizes, blocks)
+		update!(refvec, v, block_sizes, offset)
+		return refvec
+	end
+end
+
+
+RefBlockVector(v::T_Vector, block_sizes::Vector{Int64}, offset::Int64=1) where {T,T_Vector<:DenseVector{T}} =
+	RefBlockVector{T,T_Vector}(v, block_sizes, offset)
+
+
+"""
+	update!(refvec::RefBlockVector{T}, v::T_Vector, [block_sizes, offset]) where {T,T_Vector<:DenseVector{T}}
+
+Update the reference and/or blocks of `refvec` to track `v`.
+"""
+function update!(refvec::RefBlockVector{T}, v::T_Vector,
+		block_sizes::Vector{Int64}=refvec.block_sizes, offset::Int64=refvec.offset) where {T,T_Vector<:DenseVector{T}}
+
+	n = sum(block_sizes)
+	check_block_sizes(length(v), n, block_sizes, offset)
+	refvec.ref[]			 = v
+	refvec.block_sizes .= block_sizes
+	refvec.n           = n
+	refvec.offset			 = offset
+	block_offset       = offset
+	resize!(refvec.blocks, length(block_sizes))
+	for (i, bsz) in enumerate(block_sizes)
+		if isassigned(refvec.blocks, i)
+			update!(refvec.blocks[i], v, block_offset, bsz)
+		else
+			refvec.blocks[i] = RefVector{T}(v, block_offset, bsz)
+		end
+		block_offset += bsz
+	end
+
+	return
+end
+
+
+function check_block_sizes(N, n, block_sizes, offset)
+	if !(1 <= offset <= N-n+1) || !(0 <= n <= N)
+		error("Cannot reference vector of length $N starting from offset $offset with length $n")
+	end
+	if any(bsz -> bsz <= 0, block_sizes)
+		error("block_sizes must be positive")
+	end
+end
+
+
+# Abstract array interface
+@inline Base.size(vec::RefBlockVector) = (vec.n,)
+@propagate_inbounds Base.getindex(vec::RefBlockVector, i::Integer) = vec.ref[][i+vec.offset-1]
+@propagate_inbounds Base.setindex!(vec::RefBlockVector, val, i::Integer) = vec.ref[][i+vec.offset-1] = val
+@inline Base.IndexStyle(A::RefBlockVector) = IndexLinear()
+
+# utilities
+@propagate_inbounds block(vec::RefBlockVector, i) = vec.blocks[i]
+eachblock(vec::RefBlockVector) = (block(vec, i) for i = 1:length(vec.blocks))
diff --git a/src/visz_trees.jl b/src/visz_trees.jl
new file mode 100644
index 0000000000000000000000000000000000000000..2fadb6a3bd86460371933190a93592f71f46ab3a
--- /dev/null
+++ b/src/visz_trees.jl
@@ -0,0 +1,48 @@
+using GLMakie
+GLMakie.activate!()
+using dg1d
+
+
+function plot_tree1d()
+
+  t1d = Tree1d(5)
+  xs = [ Float64(c[1]) for c in t1d.coords ]
+  ys = zeros(length(xs))
+  is_bdry = dg1d.is_boundarycell.(t1d.cells)
+  bxs, bys = xs[is_bdry], ys[is_bdry]
+  fig = Figure()
+  scatterlines(fig[1,1], xs, ys)
+  scatter!(fig[1,1], bxs, bys, color=:red)
+
+  return fig
+end
+
+
+function plot_tree2d()
+
+  t2d = Tree2d(10,10)
+  xs_ys = [ t2d.coords[row][col] for row = 1:n_cells(t2d), col = 1:2 ]
+  xs, ys = xs_ys[:,1], xs_ys[:,2]
+  is_bdry = dg1d.is_boundarycell.(t2d.cells)
+  bxs, bys = xs[is_bdry], ys[is_bdry]
+  fig = Figure()
+  scatterlines(fig[1,1], xs, ys)
+  scatter!(fig[1,1], bxs, bys, color=:red)
+
+  return fig
+end
+
+
+function plot_tree3d()
+
+  t3d = Tree3d(10,10,10)
+  xs_ys_zs = [ t3d.coords[row][col] for row = 1:n_cells(t3d), col = 1:3 ]
+  xs, ys, zs = xs_ys_zs[:,1], xs_ys_zs[:,2], xs_ys_zs[:,3]
+  is_bdry = dg1d.is_boundarycell.(t3d.cells)
+  fig = Figure()
+  scatterlines(fig[1,1], xs, ys, zs, markersize=100)
+  bxs, bys, bzs = xs[is_bdry], ys[is_bdry], zs[is_bdry]
+  scatter!(fig[1,1], bxs, bys, bzs, color=:red, markersize=200)
+
+  return fig
+end
diff --git a/src/wip_dg_rhs.jl b/src/wip_dg_rhs.jl
new file mode 100644
index 0000000000000000000000000000000000000000..3a979ad524d104fd9603c2d0d2377399ee9be6b0
--- /dev/null
+++ b/src/wip_dg_rhs.jl
@@ -0,0 +1,188 @@
+export compute_rhs_weak_form!, 
+       compute_rhs_strong_form!
+
+
+"""
+    compute_rhs_weak_form!(rhs, f, nf_lhs, nf_rhs, mesh)
+    compute_rhs_weak_form!(rhs, f, s, nf_lhs, nf_rhs, mesh)
+"""
+function compute_rhs_weak_form!(rhs, f, nf_lhs, nf_rhs, mesh::Mesh)
+  @unpack invjac, element = mesh
+  @unpack invM, MDM, Ml_lhs, Ml_rhs, Npts = element
+  @unpack K = mesh
+  shape        = layout(mesh)
+  mat_rhs      = reshape(view(rhs,:), shape)
+  mat_f        = reshape(view(f,:), shape)
+  # mat_MDM      = reshape(MDM, (Npts,Npts))
+  # mul! currently allocates, although, (I think) it shouldn't ...
+  # a = @allocated begin
+  mul!(mat_rhs, MDM, mat_f)
+  # end
+  # display(a)
+  @. mat_rhs  -= (nf_rhs' * Ml_rhs - nf_lhs' * Ml_lhs)
+  mat_rhs    .*= invjac
+  return
+end
+function compute_rhs_weak_form!(rhs, f, s, nf_lhs, nf_rhs, mesh::Mesh)
+  compute_rhs_weak_form!(rhs, f, nf_lhs, nf_rhs, mesh)
+  rhs .+= s
+  return
+end
+
+
+"""
+    compute_rhs_strong_form!(rhs, f, nf_lhs, nf_rhs, mesh)
+    compute_rhs_strong_form!(rhs, f, s, nf_lhs, nf_rhs, mesh)
+"""
+function compute_rhs_strong_form!(rhs, f, nf_lhs, nf_rhs, mesh::Mesh)
+  @unpack invjac, element = mesh
+  @unpack Npts, invM, D, Ml_lhs, Ml_rhs = element
+  @unpack K = mesh
+  shape        = layout(mesh)
+  mat_rhs      = reshape(view(rhs,:), shape)
+  mat_f        = reshape(view(f,:), shape)
+  # compute rhs = - D * f inplace to rhs
+  # benchmarking showed that splitting of the factor of -1 is faster 
+  # because a temporary is not created then
+  mul!(mat_rhs, D, mat_f)
+  mat_rhs     .*= -1
+  @. mat_rhs  += (nf_rhs' * Ml_rhs - nf_lhs' * Ml_lhs)
+  mat_rhs    .*= invjac
+  return
+end
+function compute_rhs_strong_form!(rhs, f, s, nf_lhs, nf_rhs, mesh::Mesh)
+  compute_rhs_strong_form!(rhs, f, nf_lhs, nf_rhs, mesh)
+  rhs .+= s
+  return
+end
+
+
+function compute_rhs_weak_form!(rhs, sf1, sf2, nsf1, nsf2, grid::Grid2d)
+
+  for cell in grid.tree
+
+    basis = grid.bases[cell.index]
+    offset = grid.offsets[cell.index]
+
+    vol_rng = range_volume(basis, offset)
+    bdry_rng = range_boundary(basis, offset)
+
+    @views begin
+      vec_rhs = rhs[vol_idx]
+      vec_f1, vec_f2 = f1[vol_rng], f2[vol_rng]
+      # do we need four views here? I could imagine that two bigger ones suffice here to
+      # if we make use of the conservation property of fluxes
+      vec_nsf1, vec_nsf2 = nfs1[bdry_rng], nfs2[bdry_rng]
+
+      cell_rhs = reshape(vec_rhs, vol_sz)
+      cell_f1  = reshape(vec_f1, vol_sz)
+      cell_f2  = reshape(vec_f2, vol_sz)
+      # no reshaping needed because in 1D boundarys are lines
+      cell_nsf1 = vec_nsf1
+      cell_nsf2 = vec_nsf2
+    end
+
+    b1, b2 = basis
+    nx, ny = size(basis)
+
+    # add up derivatives
+    muladd!(cell_rhs1, b1.D, cell_f1, -1.0, false)
+    muladd!(transpose(cell_rhs1), cell_f2, b2.D, -1.0, 1.0)
+
+    # add up numerical fluxes
+    # xmin
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, 1, :)))
+      mat_rhs[I] += (sub_nf1max[i] * b1.Ml_rhs - sub_nf1min[i] * b1.Ml_lhs)
+    end
+    # xmax
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, nx, :)))
+      mat_rhs[I] += (sub_nf1max[i] * b1.Ml_rhs - sub_nf1min[i] * b1.Ml_lhs)
+    end
+    # ymin
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, :, 1)))
+      mat_rhs[I] += (sub_nf2max[i] * b2.Ml_rhs - sub_nf2min[i] * b2.Ml_lhs)
+    end
+    # ymax
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, :, ny)))
+      mat_rhs[I] += (sub_nf2max[i] * b2.Ml_rhs - sub_nf2min[i] * b2.Ml_lhs)
+    end
+
+  end
+  # apply jacobian
+  # TODO Benchmark whether this should be done here or inside the loop.
+  mat_rhs .*= invjac
+  return
+end
+function compute_rhs_weak_form!(rhs, s, sf1, sf2, nsf1, nsf2, grid::Grid2d)
+  compute_rhs_weak_form!(rhs, sf1, sf2, nsf1, nsf2, grid)
+  # an explicit source variable might not be needed, instead one could directly evaluate
+  # the source terms into the rhs when computing them
+  rhs .+= s
+  return
+end
+
+
+function compute_rhs_weak_form!(rhs, sf1, sf2, sf3, nsf1, nsf2, nsf3, grid::Grid3d)
+
+  for cell in grid.tree
+
+    basis = grid.bases[cell.index]
+    offset = grid.offsets[cell.index]
+
+    vol_rng = range_volume(basis, offset)
+    bdry_rng = range_boundary(basis, offset)
+
+    @views begin
+      vec_rhs = rhs[vol_idx]
+      vec_f1, vec_f2, vec_f3 = f1[vol_rng], f2[vol_rng], f3[vol_rng]
+      # do we need four views here? I could imagine that two bigger ones suffice here to
+      # if we make use of the conservation property of fluxes
+      vec_nsf1, vec_nsf2, vec_nsf3 = nfs1[bdry_rng], nfs2[bdry_rng], nsf3[bdry_rng]
+
+      cell_rhs = reshape(vec_rhs, vol_sz)
+      cell_f1  = reshape(vec_f1, vol_sz)
+      cell_f2  = reshape(vec_f2, vol_sz)
+      cell_f3  = reshape(vec_f3, vol_sz)
+      # no reshaping needed because in 1D boundarys are lines
+      cell_nsf1 = vec_nsf1
+      cell_nsf2 = vec_nsf2
+    end
+
+    b1, b2 = basis
+    nx, ny = size(basis)
+
+    # add up derivatives
+    muladd!(cell_rhs1, b1.D, cell_f1, -1.0, false)
+    muladd!(transpose(cell_rhs1), cell_f2, b2.D, -1.0, 1.0)
+
+    # add up numerical fluxes
+    # xmin
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, 1, :)))
+      mat_rhs[I] += (sub_nf1max[i] * b1.Ml_rhs - sub_nf1min[i] * b1.Ml_lhs)
+    end
+    # xmax
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, nx, :)))
+      mat_rhs[I] += (sub_nf1max[i] * b1.Ml_rhs - sub_nf1min[i] * b1.Ml_lhs)
+    end
+    # ymin
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, :, 1)))
+      mat_rhs[I] += (sub_nf2max[i] * b2.Ml_rhs - sub_nf2min[i] * b2.Ml_lhs)
+    end
+    # ymax
+    for (i, I) in enumerate(eachindex(@view(cell_rhs, :, ny)))
+      mat_rhs[I] += (sub_nf2max[i] * b2.Ml_rhs - sub_nf2min[i] * b2.Ml_lhs)
+    end
+
+  end
+  # apply jacobian
+  # TODO Benchmark whether this should be done here or inside the loop.
+  mat_rhs .*= invjac
+  return
+end
+function compute_rhs_weak_form!(rhs, s, sf1, sf2, sf3, nsf1, nsf2, nsf3, grid::Grid3d)
+  compute_rhs_weak_form!(rhs, sf1, sf2, sf3, nsf1, nsf2, nsf3, grid)
+  # an explicit source variable might not be needed, instead one could directly evaluate
+  # the source terms into the rhs when computing them
+  rhs .+= s
+  return
+end