changeset 866:1784b1c0af3e feature/laplace_opset

Merge with default
author Vidar Stiernström <vidar.stiernstrom@it.uu.se>
date Wed, 19 Jan 2022 14:44:24 +0100
parents 545a6c1a0a0e (current diff) 9a2776352c2a (diff)
children 4bd35ba8f34a
files Notes.md src/SbpOperators/SbpOperators.jl src/SbpOperators/volumeops/inner_products/inner_product.jl src/SbpOperators/volumeops/laplace/laplace.jl test/SbpOperators/volumeops/laplace/laplace_test.jl
diffstat 48 files changed, 1658 insertions(+), 753 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,3 @@
+syntax: glob
+docs/build/
+docs/build-local/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Makefile	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,54 @@
+JULIA_DEFAULT=julia --startup-file=no
+JULIA?=$(JULIA_DEFAULT)
+
+# Set the default browser
+WHICH_XDG_OPEN=$(shell which xdg-open)
+WHICH_OPEN=$(shell which open)
+BROWSER_DEFAULT  = $(if $(WHICH_XDG_OPEN), xdg-open)
+BROWSER_DEFAULT := $(if $(BROWSER_DEFAULT), $(BROWSER_DEFAULT), open)
+BROWSER?=$(BROWSER_DEFAULT)
+
+help:
+	@echo 'Targets:'
+	@echo '    help        - Show this help.'
+	@echo '    docs        - Generate docs for webserver deployment.'
+	@echo '    localdocs   - Generate docs for local viewing.'
+	@echo '    opendocs    - Open documentation in the browser remaking it if necessary.'
+	@echo ''
+	@echo 'Variables:'
+	@echo '    JULIA       - Controls which command is used to run julia'
+	@echo '                  Default $(JULIA_DEFAULT)'
+	@echo '    BROWSER     - Sets the command for how to open html files'
+	@echo '                  Default: xdg-open if it exists otherwise open'
+	@echo ''
+	@echo 'Variables can be set on the commandline using the -e flag for make, e.g.'
+	@echo '    make localdocs -e JULIA=path/to/julia'
+	@echo 'or as shell environment variables.'
+
+docs: docs/build
+
+localdocs: docs/build-local
+
+opendocs: localdocs
+	$(BROWSER) docs/build-local/index.html
+
+clean:
+	rm -rf docs/build
+	rm -rf docs/build-local
+
+.PHONY: help docs localdocs opendocs clean
+
+SRC_DIRS = src docs/src
+SRC_FILES_AND_DIRS = $(foreach dir,$(SRC_DIRS),$(shell find $(dir)))
+DEP_IGNORE = %/.DS_Store
+DOCS_DEPENDENCIES = docs/make.jl $(filter-out $(DEP_IGNORE),$(SRC_FILES_AND_DIRS))
+docs/build: $(DOCS_DEPENDENCIES)
+	$(JULIA) --project=docs docs/make.jl --build-dir build --prettyurls
+
+docs/build-local: $(DOCS_DEPENDENCIES)
+	$(JULIA) --project=docs docs/make.jl --build-dir build-local
+
+
+.PHONY: temp
+temp:
+	@echo $(SRC_FILES_AND_DIRS)
--- a/Manifest.toml	Fri Jul 02 14:23:33 2021 +0200
+++ b/Manifest.toml	Wed Jan 19 14:44:24 2022 +0100
@@ -1,41 +1,59 @@
 # This file is machine-generated - editing it directly is not advised
 
-[[Adapt]]
+julia_version = "1.7.0"
+manifest_format = "2.0"
+
+[[deps.Adapt]]
 deps = ["LinearAlgebra"]
 git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7"
 uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
 version = "3.3.1"
 
-[[Dates]]
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+
+[[deps.Dates]]
 deps = ["Printf"]
 uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
-[[Libdl]]
+[[deps.Libdl]]
 uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
 
-[[LinearAlgebra]]
-deps = ["Libdl"]
+[[deps.LinearAlgebra]]
+deps = ["Libdl", "libblastrampoline_jll"]
 uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 
-[[OffsetArrays]]
+[[deps.OffsetArrays]]
 deps = ["Adapt"]
-git-tree-sha1 = "2bf78c5fd7fa56d2bbf1efbadd45c1b8789e6f57"
+git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
 uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
-version = "1.10.2"
+version = "1.10.8"
 
-[[Printf]]
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+
+[[deps.Printf]]
 deps = ["Unicode"]
 uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
 
-[[TOML]]
+[[deps.TOML]]
 deps = ["Dates"]
 uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
 
-[[TiledIteration]]
+[[deps.TiledIteration]]
 deps = ["OffsetArrays"]
-git-tree-sha1 = "52c5f816857bfb3291c7d25420b1f4aca0a74d18"
+git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297"
 uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
-version = "0.3.0"
+version = "0.3.1"
 
-[[Unicode]]
+[[deps.Unicode]]
 uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
--- a/Notes.md	Fri Jul 02 14:23:33 2021 +0200
+++ b/Notes.md	Wed Jan 19 14:44:24 2022 +0100
@@ -53,6 +53,23 @@
 
 * Remove order as a table name and put it as a variable.
 
+### Parsing of stencil sets
+At the moment the only parsing that can be done at the top level is conversion
+from the toml file to a dict of strings. This forces the user to dig through
+the dictionary and apply the correct parsing methods for the different parts,
+e.g. `parse_stencil` or `parse_tuple`. While very flexible there is a tight
+coupling between what is written in the file and what code is run to make data
+in the file usable. While this coupling is hard to avoid it should be made
+explicit. This could be done by putting a reference to a parsing function in
+the operator-storage format or somehow specifying the type of each object.
+This mechanism should be extensible without changing the package. Perhaps
+there could be a way to register parsing functions or object types for the
+toml.
+
+If possible the goal should be for the parsing to get all the way to the
+stencils so that a user calls `read_stencil_set` and gets a
+dictionary-structure containing stencils, tuples, scalars and other types
+ready for input to the methods creating the operators.
 
 ## Variable second derivative
 
@@ -321,3 +338,15 @@
 See [this talk](https://www.youtube.com/watch?v=vPsfZUqI4_0) for some simple ideas for defining effecive memory usage and some comparison with peak performance.
 
 
+## Adjoint as a trait on the sbp_operator level?
+
+It would be nice to have a way of refering to adjoints with resepct to the sbp-inner-product.
+If it was possible you could reduce the number of times you have to deal with the inner product matrix.
+
+Since the LazyOperators package is sort of implementing matrix-free matrices there is no concept of inner products there at the moment. It seems to complicate large parts of the package if this was included there.
+
+A different approach would be to include it as a trait for operators so that you can specify what the adjoint for that operator is.
+
+
+## Name of the `VolumeOperator` type for constant stencils
+It seems that the name is too general. The name of the method `volume_operator` makes sense. It should return different types of `TensorMapping` specialized for the grid. A suggetion for a better name is `ConstantStencilVolumeOperator`
--- a/README.md	Fri Jul 02 14:23:33 2021 +0200
+++ b/README.md	Wed Jan 19 14:44:24 2022 +0100
@@ -27,3 +27,23 @@
 Pkg.test(test_args=["*/lazy_tensor_operations_test.jl", "Grids/*"])
 ```
 will run any file named `lazy_tensor_operations_test.jl` and all the files in the `Grids` folder.
+
+
+## Generating and using the documentation
+Generating the documentation can be done using either `make` or through activating the `docs` environment and including the script `docs/make.jl` at the REPL.
+
+Using `make` there are three targets
+```shell
+make docs
+make localdocs
+make opendocs
+make help
+```
+The first variant generates files suitable for webserver deployment, i.e with `prettyurls=true`. The second generates files sutible for local viewing in a web browser, i.e `prettyurls=false`. To view the documentation locally simply open `docs/build/index.html` in your web browser. The documentation can be automatically built and opened using
+```shell
+make opendocs
+```
+
+When including the `docs/make.jl` script `prettyurls` is set to `false` by default.
+
+Including `docs/make.jl` from the REPL may be preferable when repeatadely building the documentation since this avoids compilation latency.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Manifest.toml	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,145 @@
+# This file is machine-generated - editing it directly is not advised
+
+julia_version = "1.7.1"
+manifest_format = "2.0"
+
+[[deps.ANSIColoredPrinters]]
+git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c"
+uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9"
+version = "0.0.1"
+
+[[deps.Adapt]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "af92965fb30777147966f58acb05da51c5616b5f"
+uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
+version = "3.3.3"
+
+[[deps.Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+
+[[deps.Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+
+[[deps.Dates]]
+deps = ["Printf"]
+uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
+
+[[deps.DocStringExtensions]]
+deps = ["LibGit2"]
+git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
+uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
+version = "0.8.6"
+
+[[deps.Documenter]]
+deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"]
+git-tree-sha1 = "f425293f7e0acaf9144de6d731772de156676233"
+uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
+version = "0.27.10"
+
+[[deps.IOCapture]]
+deps = ["Logging", "Random"]
+git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a"
+uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
+version = "0.2.2"
+
+[[deps.InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+
+[[deps.JSON]]
+deps = ["Dates", "Mmap", "Parsers", "Unicode"]
+git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37"
+uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
+version = "0.21.2"
+
+[[deps.LibGit2]]
+deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
+uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
+
+[[deps.Libdl]]
+uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
+
+[[deps.LinearAlgebra]]
+deps = ["Libdl", "libblastrampoline_jll"]
+uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
+
+[[deps.Logging]]
+uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
+
+[[deps.Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+
+[[deps.Mmap]]
+uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+
+[[deps.NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+
+[[deps.OffsetArrays]]
+deps = ["Adapt"]
+git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed"
+uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
+version = "1.10.8"
+
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+
+[[deps.Parsers]]
+deps = ["Dates"]
+git-tree-sha1 = "d7fa6237da8004be601e19bd6666083056649918"
+uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
+version = "2.1.3"
+
+[[deps.Printf]]
+deps = ["Unicode"]
+uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+
+[[deps.REPL]]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
+uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
+
+[[deps.Random]]
+deps = ["SHA", "Serialization"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+
+[[deps.SHA]]
+uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+
+[[deps.Sbplib]]
+deps = ["TOML", "TiledIteration"]
+path = ".."
+uuid = "5a373a26-915f-4769-bcab-bf03835de17b"
+version = "0.1.0"
+
+[[deps.Serialization]]
+uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
+
+[[deps.Sockets]]
+uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
+
+[[deps.TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+
+[[deps.Test]]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[deps.TiledIteration]]
+deps = ["OffsetArrays"]
+git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297"
+uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac"
+version = "0.3.1"
+
+[[deps.Unicode]]
+uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/Project.toml	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,3 @@
+[deps]
+Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
+Sbplib = "5a373a26-915f-4769-bcab-bf03835de17b"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/make.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,42 @@
+using Documenter
+using Sbplib
+
+using Sbplib.DiffOps
+using Sbplib.Grids
+using Sbplib.LazyTensors
+using Sbplib.RegionIndices
+using Sbplib.SbpOperators
+using Sbplib.StaticDicts
+
+sitename = "Sbplib.jl"
+
+if "--prettyurls" ∈ ARGS
+    prettyurls = true
+else
+    prettyurls = false
+end
+
+if "--build-dir" ∈ ARGS
+    i = findlast(==("--build-dir"), ARGS)
+    build = ARGS[i+1]
+else
+    build = "build-local"
+end
+
+pages = [
+    "Home" => "index.md",
+    "operator_file_format.md",
+    "Submodules" => [
+        "submodules/grids.md",
+        "submodules/diff_ops.md",
+        "submodules/lazy_tensors.md",
+        "submodules/region_indices.md",
+        "submodules/sbp_operators.md",
+        "submodules/static_dicts.md",
+    ],
+    "doc_index.md",
+]
+# This ordering is not respected by @contents. See https://github.com/JuliaDocs/Documenter.jl/issues/936
+
+format=Documenter.HTML(;prettyurls)
+makedocs(;sitename, pages, format, build)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/doc_index.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,4 @@
+# Interface index
+
+```@index
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/index.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,9 @@
+# Sbplib.jl
+
+```@contents
+Depth = 1
+```
+
+> Matrix free finite differences methods for all!
+>
+>  -- The mighty Trocatron, ruler of errors and grids
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/operator_file_format.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,72 @@
+# Operator file format
+
+The intention is that Sbplib.jl should be a general and extensible framework
+for working with finite difference methods. It therefore includes a set of
+tools for storing and sharing operator definitions as well as a set of widely
+used operators.
+
+## Using the included operators
+
+Most users will likely access the included operators by simply passing the
+filename of the wanted operator set to the appropriate function.  The location
+of the included stencil sets can be computed using
+[`sbp_operators_path`](@ref).
+```@meta
+# TODO: provide examples of functions to pass the files to
+```
+Advanced user might want to get access to the individual objects of an
+operator file. This can be accomplished using functions such as
+* [`read_stencil_set`](@ref)
+* [`parse_scalar`](@ref)
+* [`parse_stencil`](@ref)
+* [`parse_tuple`](@ref)
+
+When parsing operator objects they are interpreted using `Rational`s and
+possibly have to be converted to a desired type before use. This allows
+preserving maximum accuracy when needed.
+```@meta
+# TBD: "possibly have to be converted to a desired type before use" Is this the case? Can it be fixed?
+```
+
+## File format
+The file format is based on TOML and can be parsed using `TOML.parse`. A file
+can optionally start with a `[meta]` section which can specify things like who
+the author was, a description and how to cite the operators.
+
+After the `[meta]` section one or more stencil sets follow, each one beginning
+with `[[stencil_set]]`. Each stencil set should include descriptors like
+`order`, `name` or `number_of_bondary_points` to make them unique within the
+TOML-file. What descriptors to use are up to the author of the file to decide.
+
+Beyond identifying information the stencil set can contain any valid TOML.
+This data is then parsed by the functions creating specific operators like
+``D_1`` or ``D_2``.
+
+### Numbers
+Number can be represented as regular TOML numbers e.g. `1`, `-0.4` or
+`4.32e-3`. Alternatively they can be represented as strings which allows
+specifying fraction e.g. `"1/2"` or `"0"`.
+
+All numbers are accurately converted to `Rational`s when using the
+[`parse_scalar`](@ref) function.
+
+### Stencils
+Stencils are parsed using [`parse_stencil`](@ref). They can be specified
+either as a simple arrays
+```toml
+stencil = ["-1/2","0", "1/2"]
+```
+which assumes a centered stencil. Or as a TOML inline table
+```toml
+stencil =  {s = ["-24/17", "59/34", "-4/17", "-3/34", "0", "0"], c = 1},
+```
+which allows specifying the center of the stencil using the key `c`.
+
+## Creating your own operator files
+Operator files can be created either to add new variants of existing types of
+operators like ``D_1`` or ``D_2`` or to describe completely new types of
+operators like for example a novel kind of interpolation operator. In the
+second case new parsing functions are also necessary.
+
+The files can then be used to easily test or share different variants of
+operators.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/diff_ops.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# DiffOps
+
+## Contents
+```@contents
+Pages = ["diff_ops.md"]
+```
+
+## Index
+```@index
+Pages = ["diff_ops.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [DiffOps]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [DiffOps]
+Public = false # Hide exported objects
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/grids.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# Grids
+
+## Contents
+```@contents
+Pages = ["grids.md"]
+```
+
+## Index
+```@index
+Pages = ["grids.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [Grids]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [Grids]
+Public = false # Hide exported objects
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/lazy_tensors.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# LazyTensors
+
+## Contents
+```@contents
+Pages = ["lazy_tensors.md"]
+```
+
+## Index
+```@index
+Pages = ["lazy_tensors.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [LazyTensors]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [LazyTensors]
+Public = false # Hide exported objects
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/region_indices.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# RegionIndices
+
+## Contents
+```@contents
+Pages = ["region_indices.md"]
+```
+
+## Index
+```@index
+Pages = ["region_indices.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [RegionIndices]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [RegionIndices]
+Public = false # Hide exported objects
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/sbp_operators.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# SbpOperators
+
+## Contents
+```@contents
+Pages = ["sbp_operators.md"]
+```
+
+## Index
+```@index
+Pages = ["sbp_operators.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [SbpOperators]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [SbpOperators]
+Public = false # Hide exported objects
+```
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/src/submodules/static_dicts.md	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,23 @@
+# StaticDicts
+
+## Contents
+```@contents
+Pages = ["static_dicts.md"]
+```
+
+## Index
+```@index
+Pages = ["static_dicts.md"]
+```
+
+## Public interface
+```@autodocs
+Modules = [StaticDicts]
+Private = false # Hide unexported objects
+```
+
+## Internal interface
+```@autodocs
+Modules = [StaticDicts]
+Public = false # Hide exported objects
+```
--- a/src/DiffOps/DiffOps.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/DiffOps/DiffOps.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -93,6 +93,7 @@
 
 
 """
+    BoundaryCondition
 A BoundaryCondition should implement the method
     sat(::DiffOp, v::AbstractArray, data::AbstractArray, ...)
 """
--- a/src/LazyTensors/lazy_array.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/LazyTensors/lazy_array.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -42,10 +42,10 @@
 
 """
     LazyElementwiseOperation{T,D,Op} <: LazyArray{T,D}
-Struct allowing for lazy evaluation of elementwise operations on AbstractArrays.
+Struct allowing for lazy evaluation of elementwise operations on `AbstractArray`s.
 
-A LazyElementwiseOperation contains two arrays together with an operation.
-The operations are carried out when the LazyElementwiseOperation is indexed.
+A `LazyElementwiseOperation` contains two arrays together with an operation.
+The operations are carried out when the `LazyElementwiseOperation` is indexed.
 """
 struct LazyElementwiseOperation{T,D,Op,T1<:AbstractArray{T,D},T2<:AbstractArray{T,D}} <: LazyArray{T,D}
     a::T1
--- a/src/LazyTensors/lazy_tensor_operations.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/LazyTensors/lazy_tensor_operations.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -76,7 +76,7 @@
 """
     TensorMappingComposition{T,R,K,D}
 
-Lazily compose two TensorMappings, so that they can be handled as a single TensorMapping.
+Lazily compose two `TensorMapping`s, so that they can be handled as a single `TensorMapping`.
 """
 struct TensorMappingComposition{T,R,K,D, TM1<:TensorMapping{T,R,K}, TM2<:TensorMapping{T,K,D}} <: TensorMapping{T,R,D}
     t1::TM1
@@ -165,8 +165,8 @@
 apply_transpose(tmi::IdentityMapping{T,D}, v::AbstractArray{T,D}, I::Vararg{Any,D}) where {T,D} = v[I...]
 
 """
-Base.:∘(tm, tmi)
-Base.:∘(tmi, tm)
+    Base.:∘(tm, tmi)
+    Base.:∘(tmi, tm)
 
 Composes a `Tensormapping` `tm` with an `IdentityMapping` `tmi`, by returning `tm`
 """
@@ -316,7 +316,7 @@
     slice_tuple(t, Val(l), Val(u))
 
 Get a slice of a tuple in a type stable way.
-Equivalent to t[l:u] but type stable.
+Equivalent to `t[l:u]` but type stable.
 """
 function slice_tuple(t,::Val{L},::Val{U}) where {L,U}
     return ntuple(i->t[i+L-1], U-L+1)
@@ -327,7 +327,7 @@
 
 Split the tuple `t` into two parts. the first part is `M` long.
 E.g
-```
+```julia
 split_tuple((1,2,3,4),Val(3)) -> (1,2,3), (4,)
 ```
 """
@@ -357,17 +357,19 @@
 flatten_tuple(t::Tuple) = ((flatten_tuple.(t)...)...,) # simplify?
 flatten_tuple(ts::Vararg) = flatten_tuple(ts)
 
-"""
-   LazyOuterProduct(tms...)
+@doc raw"""
+    LazyOuterProduct(tms...)
 
 Creates a `TensorMappingComposition` for the outerproduct of `tms...`.
 This is done by separating the outer product into regular products of outer products involving only identity mappings and one non-identity mapping.
 
 First let
 ```math
-A = A_{I,J}
-B = B_{M,N}
-C = C_{P,Q}
+\begin{aligned}
+A &= A_{I,J} \\
+B &= B_{M,N} \\
+C &= C_{P,Q} \\
+\end{aligned}
 ```
 
 where ``I``, ``M``, ``P`` are  multi-indexes for the ranges of ``A``, ``B``, ``C``, and ``J``, ``N``, ``Q`` are multi-indexes of the domains.
@@ -385,10 +387,12 @@
 ```math
 A⊗B⊗C = (A⊗I_{|M|}⊗I_{|P|})(I_{|J|}⊗B⊗I_{|P|})(I_{|J|}⊗I_{|N|}⊗C)
 ```
-where |.| of a multi-index is a vector of sizes for each dimension. ``I_v`` denotes the identity tensor of size ``v[i]`` in each direction
+where ``|⋅|`` of a multi-index is a vector of sizes for each dimension. ``I_v`` denotes the identity tensor of size ``v[i]`` in each direction
 To apply ``A⊗B⊗C`` we evaluate
 
+```math
 (A⊗B⊗C)v = [(A⊗I_{|M|}⊗I_{|P|})  [(I_{|J|}⊗B⊗I_{|P|}) [(I_{|J|}⊗I_{|N|}⊗C)v]]]
+```
 """
 function LazyOuterProduct end
 export LazyOuterProduct
--- a/src/LazyTensors/tensor_mapping.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/LazyTensors/tensor_mapping.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,21 +1,24 @@
 """
     TensorMapping{T,R,D}
 
-Describes a mapping of a D dimension tensor to an R dimension tensor.
+Describes a mapping of a `D` dimension tensor to an `R` dimension tensor.
 The action of the mapping is implemented through the method
-
+```julia
     apply(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T}
+```
 
 The size of the range and domain that the operator works with should be returned by
 the functions
-
+```julia
     range_size(::TensorMapping)
     domain_size(::TensorMapping)
-
+```
 to allow querying for one or the other.
 
 Optionally the action of the transpose may be defined through
+```julia
     apply_transpose(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T}
+```
 """
 abstract type TensorMapping{T,R,D} end
 export TensorMapping
@@ -37,11 +40,13 @@
 export apply_transpose
 
 """
+    range_dim(::TensorMapping)
 Return the dimension of the range space of a given mapping
 """
 range_dim(::TensorMapping{T,R,D}) where {T,R,D} = R
 
 """
+    domain_dim(::TensorMapping)
 Return the dimension of the domain space of a given mapping
 """
 domain_dim(::TensorMapping{T,R,D}) where {T,R,D} = D
--- a/src/SbpOperators/SbpOperators.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/SbpOperators.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -5,11 +5,16 @@
 using Sbplib.Grids
 using Sbplib.StaticDicts
 
+@enum Parity begin
+    odd = -1
+    even = 1
+end
+
 include("stencil.jl")
-include("d2.jl")
 include("readoperator.jl")
 include("volumeops/volume_operator.jl")
-include("volumeops/derivatives/secondderivative.jl")
+include("volumeops/constant_interior_scaling_operator.jl")
+include("volumeops/derivatives/second_derivative.jl")
 include("volumeops/laplace/laplace.jl")
 include("volumeops/inner_products/inner_product.jl")
 include("volumeops/inner_products/inverse_inner_product.jl")
--- a/src/SbpOperators/boundaryops/boundary_operator.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/boundaryops/boundary_operator.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -9,7 +9,7 @@
 of `IdentityMappings` in orthogonal coordinate directions, e.g for `Dim=3`,
 the boundary restriction operator in the y-direction direction is `Ix⊗op⊗Iz`.
 """
-function boundary_operator(grid::EquidistantGrid{Dim,T}, closure_stencil::Stencil{T}, boundary::CartesianBoundary) where {Dim,T}
+function boundary_operator(grid::EquidistantGrid, closure_stencil, boundary::CartesianBoundary)
     #TODO:Check that dim(boundary) <= Dim?
 
     # Create 1D boundary operator
@@ -18,8 +18,8 @@
     op = BoundaryOperator(restrict(grid, d), closure_stencil, r)
 
     # Create 1D IdentityMappings for each coordinate direction
-    one_d_grids = restrict.(Ref(grid), Tuple(1:Dim))
-    Is = IdentityMapping{T}.(size.(one_d_grids))
+    one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid)))
+    Is = IdentityMapping{eltype(grid)}.(size.(one_d_grids))
 
     # Formulate the correct outer product sequence of the identity mappings and
     # the boundary operator
--- a/src/SbpOperators/boundaryops/boundary_restriction.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/boundaryops/boundary_restriction.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -9,7 +9,10 @@
 On a one-dimensional `grid`, `e` is a `BoundaryOperator`. On a multi-dimensional `grid`, `e` is the inflation of
 a `BoundaryOperator`. Also see the documentation of `SbpOperators.boundary_operator(...)` for more details.
 """
-boundary_restriction(grid::EquidistantGrid, closure_stencil::Stencil, boundary::CartesianBoundary) = SbpOperators.boundary_operator(grid, closure_stencil, boundary)
-boundary_restriction(grid::EquidistantGrid{1}, closure_stencil::Stencil, region::Region) = boundary_restriction(grid, closure_stencil, CartesianBoundary{1,typeof(region)}())
+function boundary_restriction(grid::EquidistantGrid, closure_stencil, boundary::CartesianBoundary)
+    converted_stencil = convert(Stencil{eltype(grid)}, closure_stencil)
+    return SbpOperators.boundary_operator(grid, converted_stencil, boundary)
+end
+boundary_restriction(grid::EquidistantGrid{1}, closure_stencil, region::Region) = boundary_restriction(grid, closure_stencil, CartesianBoundary{1,typeof(region)}())
 
 export boundary_restriction
--- a/src/SbpOperators/boundaryops/normal_derivative.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/boundaryops/normal_derivative.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -9,10 +9,10 @@
 On a one-dimensional `grid`, `d` is a `BoundaryOperator`. On a multi-dimensional `grid`, `d` is the inflation of
 a `BoundaryOperator`. Also see the documentation of `SbpOperators.boundary_operator(...)` for more details.
 """
-function normal_derivative(grid::EquidistantGrid, closure_stencil::Stencil, boundary::CartesianBoundary)
+function normal_derivative(grid::EquidistantGrid, closure_stencil, boundary::CartesianBoundary)
     direction = dim(boundary)
     h_inv = inverse_spacing(grid)[direction]
     return SbpOperators.boundary_operator(grid, scale(closure_stencil,h_inv), boundary)
 end
-normal_derivative(grid::EquidistantGrid{1}, closure_stencil::Stencil, region::Region) = normal_derivative(grid, closure_stencil, CartesianBoundary{1,typeof(region)}())
+normal_derivative(grid::EquidistantGrid{1}, closure_stencil, region::Region) = normal_derivative(grid, closure_stencil, CartesianBoundary{1,typeof(region)}())
 export normal_derivative
--- a/src/SbpOperators/d2.jl	Fri Jul 02 14:23:33 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,17 +0,0 @@
-export D2, closuresize
-
-@enum Parity begin
-    odd = -1
-    even = 1
-end
-
-struct D2{T,M}
-    innerStencil::Stencil{T}
-    closureStencils::NTuple{M,Stencil{T}}
-    eClosure::Stencil{T}
-    dClosure::Stencil{T}
-    quadratureClosure::NTuple{M,Stencil{T}}
-    parity::Parity
-end
-
-closuresize(D::D2{T,M}) where {T,M} = M
--- a/src/SbpOperators/operators/standard_diagonal.toml	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/operators/standard_diagonal.toml	Wed Jan 19 14:44:24 2022 +0100
@@ -1,36 +1,60 @@
 [meta]
 authors = "Ken Mattson"
-descripion = "Standard operators for equidistant grids"
+description = "Standard operators for equidistant grids"
 type = "equidistant"
+cite = """
+    Ken Mattsson, Jan Nordström,
+    Summation by parts operators for finite difference approximations of second derivatives,
+    Journal of Computational Physics,
+    Volume 199, Issue 2,
+    2004,
+    Pages 503-540,
+    ISSN 0021-9991,
+    https://doi.org/10.1016/j.jcp.2004.03.001.
+"""
 
-[order2]
-H.inner = ["1"]
+[[stencil_set]]
+
+order = 2
+
+H.inner = "1"
 H.closure = ["1/2"]
 
 D1.inner_stencil = ["-1/2", "0", "1/2"]
 D1.closure_stencils = [
-    ["-1", "1"],
+    {s = ["-1", "1"], c = 1},
 ]
 
 D2.inner_stencil = ["1", "-2", "1"]
 D2.closure_stencils = [
-    ["1", "-2", "1"],
+    {s = ["1", "-2", "1"], c = 1},
 ]
 
 e.closure = ["1"]
-d1.closure = ["-3/2", "2", "-1/2"]
+d1.closure = {s = ["-3/2", "2", "-1/2"], c = 1}
+
+[[stencil_set]]
+
+order = 4
 
-[order4]
-H.inner = ["1"]
+H.inner = "1"
 H.closure = ["17/48", "59/48", "43/48", "49/48"]
 
+D1.inner_stencil = ["1/12","-2/3","0","2/3","-1/12"]
+D1.closure_stencils = [
+    {s = [ "-24/17",  "59/34",  "-4/17", "-3/34",     "0",     "0"], c = 1},
+    {s = [   "-1/2",      "0",    "1/2",     "0",     "0",     "0"], c = 2},
+    {s = [   "4/43", "-59/86",      "0", "59/86", "-4/43",     "0"], c = 3},
+    {s = [   "3/98",      "0", "-59/98",     "0", "32/49", "-4/49"], c = 4},
+]
+
 D2.inner_stencil = ["-1/12","4/3","-5/2","4/3","-1/12"]
 D2.closure_stencils = [
-    [     "2",    "-5",      "4",       "-1",     "0",     "0"],
-    [     "1",    "-2",      "1",        "0",     "0",     "0"],
-    [ "-4/43", "59/43", "-110/43",   "59/43", "-4/43",     "0"],
-    [ "-1/49",     "0",   "59/49", "-118/49", "64/49", "-4/49"],
+    {s = [     "2",    "-5",      "4",       "-1",     "0",     "0"], c = 1},
+    {s = [     "1",    "-2",      "1",        "0",     "0",     "0"], c = 2},
+    {s = [ "-4/43", "59/43", "-110/43",   "59/43", "-4/43",     "0"], c = 3},
+    {s = [ "-1/49",     "0",   "59/49", "-118/49", "64/49", "-4/49"], c = 4},
 ]
 
 e.closure = ["1"]
-d1.closure = ["-11/6", "3", "-3/2", "1/3"]
+d1.closure = {s = ["-11/6", "3", "-3/2", "1/3"], c = 1}
--- a/src/SbpOperators/readoperator.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/readoperator.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,155 +1,160 @@
 using TOML
 
-export read_D2_operator
-export read_stencil
-export read_stencils
-export read_tuple
+export read_stencil_set
+export get_stencil_set
+
+export parse_stencil
+export parse_scalar
+export parse_tuple
+
+export sbp_operators_path
+
 
-export get_stencil
-export get_stencils
-export get_tuple
+"""
+    read_stencil_set(filename; filters)
 
-function read_D2_operator(fn; order)
-    operators = TOML.parsefile(fn)["order$order"]
-    D2 = operators["D2"]
-    H = operators["H"]
-    e = operators["e"]
-    d1 = operators["d1"]
+Picks out a stencil set from a TOML file based on some key-value
+filters. If more than one set matches the filters an error is raised. The
+returned stencil set contains parsed TOML intended for functions like
+`parse_scalar` and `parse_stencil`.
+
+The stencil set is not parsed beyond the inital TOML parse. To get usable
+stencils use the `parse_stencil` functions on the fields of the stencil set.
+
+The reason for this is that since stencil sets are intended to be very
+general, and currently do not include any way to specify how to parse a given
+section, the exact parsing is left to the user.
 
-    # Create inner stencil
-    innerStencil = get_stencil(operators, "D2", "inner_stencil")
+For more information see [Operator file format](@ref) in the documentation.
+
+See also [`sbp_operators_path`](@ref), [`get_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref),.
+"""
+read_stencil_set(filename; filters...) = get_stencil_set(TOML.parsefile(filename); filters...)
+
+"""
+    get_stencil_set(parsed_toml; filters...)
+
+Picks out a stencil set from an already parsed TOML based on some key-value
+filters.
 
-    # Create boundary stencils
-    boundarySize = length(D2["closure_stencils"])
-    closureStencils = Vector{typeof(innerStencil)}() # TBD: is the the right way to get the correct type?
-    for i ∈ 1:boundarySize
-        closureStencils = (closureStencils..., get_stencil(operators, "D2", "closure_stencils", i; center=i))
+See also [`read_stencil_set`](@ref).
+"""
+function get_stencil_set(parsed_toml; filters...)
+    matches = findall(parsed_toml["stencil_set"]) do set
+        for (key, val) ∈ filters
+            if set[string(key)] != val
+                return false
+            end
+        end
+
+        return true
     end
-    # TODO: Get rid of the padding here. Any padding should be handled by the consturctor accepting the stencils.
-    eClosure = Stencil(pad_tuple(toml_string_array_to_tuple(Float64, e["closure"]), boundarySize)..., center=1)
-    dClosure = Stencil(pad_tuple(toml_string_array_to_tuple(Float64, d1["closure"]), boundarySize)..., center=1)
 
-    q_tuple = pad_tuple(toml_string_array_to_tuple(Float64, H["closure"]), boundarySize)
-    quadratureClosure = Vector{typeof(innerStencil)}()
-    for i ∈ 1:boundarySize
-        quadratureClosure = (quadratureClosure..., Stencil(q_tuple[i], center=1))
+    if length(matches) != 1
+        throw(ArgumentError("filters must pick out a single stencil set"))
     end
 
-    d2 = SbpOperators.D2(
-        innerStencil,
-        closureStencils,
-        eClosure,
-        dClosure,
-        quadratureClosure,
-        even
-    )
+    i = matches[1]
+    return parsed_toml["stencil_set"][i]
+end
+
+"""
+    parse_stencil(parsed_toml)
+
+Accepts parsed TOML and reads it as a stencil.
+
+See also [`read_stencil_set`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref).
+"""
+function parse_stencil(parsed_toml)
+    check_stencil_toml(parsed_toml)
+
+    if parsed_toml isa Array
+        weights = parse_rational.(parsed_toml)
+        return CenteredStencil(weights...)
+    end
+
+    weights = parse_rational.(parsed_toml["s"])
+    return Stencil(weights..., center = parsed_toml["c"])
+end
+
+"""
+    parse_stencil(T, parsed_toml)
+
+Parses the input as a stencil with element type `T`.
+"""
+parse_stencil(T, parsed_toml) = Stencil{T}(parse_stencil(parsed_toml))
+
+function check_stencil_toml(parsed_toml)
+    if !(parsed_toml isa Dict || parsed_toml isa Vector{String})
+        throw(ArgumentError("the TOML for a stencil must be a vector of strings or a table."))
+    end
+
+    if parsed_toml isa Vector{String}
+        return
+    end
 
-    return d2
+    if !(haskey(parsed_toml, "s") && haskey(parsed_toml, "c"))
+        throw(ArgumentError("the table form of a stencil must have fields `s` and `c`."))
+    end
+
+    if !(parsed_toml["s"] isa Vector{String})
+        throw(ArgumentError("a stencil must be specified as a vector of strings."))
+    end
+
+    if !(parsed_toml["c"] isa Int)
+        throw(ArgumentError("the center of a stencil must be specified as an integer."))
+    end
+end
+
+"""
+    parse_scalar(parsed_toml)
+
+Parse a scalar, represented as a string or a number in the TOML, and return it as a `Rational`
+
+See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref) [`parse_tuple`](@ref).
+"""
+function parse_scalar(parsed_toml)
+    try
+        return parse_rational(parsed_toml)
+    catch e
+        throw(ArgumentError("must be a number or a string representing a number."))
+    end
+end
+
+"""
+    parse_tuple(parsed_toml)
+
+Parse an array as a tuple of scalars.
+
+See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref).
+"""
+function parse_tuple(parsed_toml)
+    if !(parsed_toml isa Array)
+        throw(ArgumentError("argument must be an array"))
+    end
+    return Tuple(parse_scalar.(parsed_toml))
 end
 
 
 """
-    read_stencil(fn, path...; [center])
-
-Read a stencil at `path` from the file with name `fn`.
-If a center is specified the given element of the stecil is set as the center.
-
-See also: [`read_stencils`](@ref), [`read_tuple`](@ref), [`get_stencil`](@ref).
+    parse_rational(parsed_toml)
 
-# Examples
-```
-read_stencil(sbp_operators_path()*"standard_diagonal.toml", "order2", "D2", "inner_stencil")
-read_stencil(sbp_operators_path()*"standard_diagonal.toml", "order2", "d1", "closure"; center=1)
-```
-"""
-read_stencil(fn, path...; center=nothing) = get_stencil(TOML.parsefile(fn), path...; center=center)
-
-"""
-    read_stencils(fn, path...; centers)
-
-Read stencils at `path` from the file `fn`.
-Centers of the stencils are specified as a tuple or array in `centers`.
-
-See also: [`read_stencil`](@ref), [`read_tuple`](@ref), [`get_stencils`](@ref).
-"""
-read_stencils(fn, path...; centers) = get_stencils(TOML.parsefile(fn), path...; centers=centers)
-
+Parse a string or a number as a rational.
 """
-    read_tuple(fn, path...)
-
-Read tuple at `path` from the file `fn`.
-
-See also: [`read_stencil`](@ref), [`read_stencils`](@ref), [`get_tuple`](@ref).
-"""
-read_tuple(fn, path...) = get_tuple(TOML.parsefile(fn), path...)
-
-"""
-    get_stencil(parsed_toml, path...; center=nothing)
-
-Same as [`read_stencil`](@ref)) but takes already parsed toml.
-"""
-get_stencil(parsed_toml, path...; center=nothing) = get_stencil(parsed_toml[path[1]], path[2:end]...; center=center)
-function get_stencil(parsed_toml; center=nothing)
-    @assert parsed_toml isa Vector{String}
-    stencil_weights = Float64.(parse_rational.(parsed_toml))
-
-    width = length(stencil_weights)
-
-    if isnothing(center)
-        center = div(width,2)+1
+function parse_rational(parsed_toml)
+    if parsed_toml isa String
+        expr = Meta.parse(replace(parsed_toml, "/"=>"//"))
+        return eval(:(Rational($expr)))
+    else
+        return Rational(parsed_toml)
     end
-
-    return Stencil(stencil_weights..., center=center)
 end
 
 """
-    get_stencils(parsed_toml, path...; centers)
-
-Same as [`read_stencils`](@ref)) but takes already parsed toml.
-"""
-get_stencils(parsed_toml, path...; centers) = get_stencils(parsed_toml[path[1]], path[2:end]...; centers=centers)
-function get_stencils(parsed_toml; centers)
-    @assert parsed_toml isa Vector{Vector{String}}
-    @assert length(centers) == length(parsed_toml)
+    sbp_operators_path()
 
-    stencils = ()
-    for i ∈ 1:length(parsed_toml)
-        stencil = get_stencil(parsed_toml[i], center = centers[i])
-        stencils = (stencils..., stencil)
-    end
+Calculate the path for the operators folder with included stencil sets.
 
-    return stencils
-end
-
-"""
-    get_tuple(parsed_toml, path...)
-
-Same as [`read_tuple`](@ref)) but takes already parsed toml.
+See also [`read_stencil_set`](@ref)
 """
-get_tuple(parsed_toml, path...) = get_tuple(parsed_toml[path[1]], path[2:end]...)
-function get_tuple(parsed_toml)
-    @assert parsed_toml isa Vector{String}
-    t = Tuple(Float64.(parse_rational.(parsed_toml)))
-    return t
-end
-
-# TODO: Probably should be deleted once we have gotten rid of read_D2_operator()
-function toml_string_array_to_tuple(::Type{T}, arr::AbstractVector{String}) where T
-    return Tuple(T.(parse_rational.(arr)))
-end
-
-function parse_rational(str)
-    expr = Meta.parse(replace(str, "/"=>"//"))
-    return eval(:(Rational($expr)))
-end
-
-function pad_tuple(t::NTuple{N, T}, n::Integer) where {N,T}
-    if N >= n
-        return t
-    else
-        return pad_tuple((t..., zero(T)), n)
-    end
-end
-
 sbp_operators_path() = (@__DIR__) * "/operators/"
-export sbp_operators_path
--- a/src/SbpOperators/stencil.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/stencil.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,10 +1,10 @@
 export CenteredStencil
 
-struct Stencil{T<:Real,N}
+struct Stencil{T,N}
     range::Tuple{Int,Int}
     weights::NTuple{N,T}
 
-    function Stencil(range::Tuple{Int,Int},weights::NTuple{N,T}) where {T <: Real, N}
+    function Stencil(range::Tuple{Int,Int},weights::NTuple{N,T}) where {T, N}
         @assert range[2]-range[1]+1 == N
         new{T,N}(range,weights)
     end
@@ -15,13 +15,19 @@
 
 Create a stencil with the given weights with element `center` as the center of the stencil.
 """
-function Stencil(weights::Vararg{Number}; center::Int)
+function Stencil(weights::Vararg{T}; center::Int) where T # Type parameter T makes sure the weights are valid for the Stencil constuctors and throws an earlier, more readable, error
     N = length(weights)
     range = (1, N) .- center
 
     return Stencil(range, weights)
 end
 
+function Stencil{T}(s::Stencil) where T
+    return Stencil(s.range, T.(s.weights))
+end
+
+Base.convert(::Type{Stencil{T}}, stencil) where T = Stencil{T}(stencil)
+
 function CenteredStencil(weights::Vararg)
     if iseven(length(weights))
         throw(ArgumentError("a centered stencil must have an odd number of weights."))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SbpOperators/volumeops/constant_interior_scaling_operator.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,48 @@
+"""
+    ConstantInteriorScalingOperator{T,N} <: TensorMapping{T,1,1}
+
+A one-dimensional operator scaling a vector. The first and last `N` points are
+scaled with individual weights while all interior points are scaled the same.
+"""
+struct ConstantInteriorScalingOperator{T,N} <: TensorMapping{T,1,1}
+    interior_weight::T
+    closure_weights::NTuple{N,T}
+    size::Int
+
+    function ConstantInteriorScalingOperator(interior_weight::T, closure_weights::NTuple{N,T}, size::Int) where {T,N}
+        if size < 2*length(closure_weights)
+            throw(DomainError(size, "size must be larger that two times the closure size."))
+        end
+
+        return new{T,N}(interior_weight, closure_weights, size)
+    end
+end
+
+function ConstantInteriorScalingOperator(grid::EquidistantGrid{1}, interior_weight, closure_weights)
+    return ConstantInteriorScalingOperator(interior_weight, Tuple(closure_weights), size(grid)[1])
+end
+
+closure_size(::ConstantInteriorScalingOperator{T,N}) where {T,N} = N
+
+LazyTensors.range_size(op::ConstantInteriorScalingOperator) = (op.size,)
+LazyTensors.domain_size(op::ConstantInteriorScalingOperator) = (op.size,)
+
+# TBD: @inbounds in apply methods?
+function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Lower}) where T
+    return op.closure_weights[Int(i)]*v[Int(i)]
+end
+
+function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Interior}) where T
+    return op.interior_weight*v[Int(i)]
+end
+
+function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Upper}) where T
+    return op.closure_weights[op.size[1]-Int(i)+1]*v[Int(i)]
+end
+
+function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i) where T
+    r = getregion(i, closure_size(op), op.size[1])
+    return LazyTensors.apply(op, v, Index(i, r))
+end
+
+LazyTensors.apply_transpose(op::ConstantInteriorScalingOperator, v, i) = apply(op, v, i)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/SbpOperators/volumeops/derivatives/second_derivative.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,19 @@
+"""
+    second_derivative(grid::EquidistantGrid, inner_stencil, closure_stencils, direction)
+
+Creates the second-derivative operator `D2` as a `TensorMapping`
+
+`D2` approximates the second-derivative d²/dξ² on `grid` along the coordinate dimension specified by
+`direction`, using the stencil `inner_stencil` in the interior and a set of stencils `closure_stencils`
+for the points in the closure regions.
+
+On a one-dimensional `grid`, `D2` is a `VolumeOperator`. On a multi-dimensional `grid`, `D2` is the outer product of the
+one-dimensional operator with the `IdentityMapping`s in orthogonal coordinate dirrections.
+Also see the documentation of `SbpOperators.volume_operator(...)` for more details.
+"""
+function second_derivative(grid::EquidistantGrid, inner_stencil, closure_stencils, direction)
+    h_inv = inverse_spacing(grid)[direction]
+    return SbpOperators.volume_operator(grid, scale(inner_stencil,h_inv^2), scale.(closure_stencils,h_inv^2), even, direction)
+end
+second_derivative(grid::EquidistantGrid{1}, inner_stencil, closure_stencils) = second_derivative(grid,inner_stencil,closure_stencils,1)
+export second_derivative
--- a/src/SbpOperators/volumeops/derivatives/secondderivative.jl	Fri Jul 02 14:23:33 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-"""
-    second_derivative(grid::EquidistantGrid{Dim}, inner_stencil, closure_stencils, direction)
-    second_derivative(grid::EquidistantGrid{1}, inner_stencil, closure_stencils)
-
-Creates the second-derivative operator `D2` as a `TensorMapping`
-
-`D2` approximates the second-derivative d²/dξ² on `grid` along the coordinate dimension specified by
-`direction`, using the stencil `inner_stencil` in the interior and a set of stencils `closure_stencils`
-for the points in the closure regions.
-
-On a one-dimensional `grid`, `D2` is a `VolumeOperator`. On a multi-dimensional `grid`, `D2` is the outer product of the
-one-dimensional operator with the `IdentityMapping`s in orthogonal coordinate dirrections.
-Also see the documentation of `SbpOperators.volume_operator(...)` for more details.
-"""
-function second_derivative(grid::EquidistantGrid{Dim}, inner_stencil, closure_stencils, direction) where Dim
-    h_inv = inverse_spacing(grid)[direction]
-    return SbpOperators.volume_operator(grid, scale(inner_stencil,h_inv^2), scale.(closure_stencils,h_inv^2), even, direction)
-end
-second_derivative(grid::EquidistantGrid{1}, inner_stencil, closure_stencils) = second_derivative(grid,inner_stencil,closure_stencils,1)
-export second_derivative
--- a/src/SbpOperators/volumeops/inner_products/inner_product.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/volumeops/inner_products/inner_product.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,29 +1,34 @@
 """
-    inner_product(grid::EquidistantGrid, closure_stencils, inner_stencil)
+    inner_product(grid::EquidistantGrid, interior_weight, closure_weights)
 
-Creates the discrete inner product operator `H` as a `TensorMapping` on an equidistant
-grid, defined as `(u,v)  = u'Hv` for grid functions `u,v`.
+Creates the discrete inner product operator `H` as a `TensorMapping` on an
+equidistant grid, defined as `(u,v)  = u'Hv` for grid functions `u,v`.
 
-`inner_product(grid::EquidistantGrid, closure_stencils, inner_stencil)` creates
-`H` on `grid` the using a set of stencils `closure_stencils` for the points in
-the closure regions and the stencil and `inner_stencil` in the interior. If
-`inner_stencil` is omitted a central interior stencil with weight 1 is used.
+`inner_product` creates `H` on `grid` using the `interior_weight` for the
+interior points and the `closure_weights` for the points close to the
+boundary.
 
-On a 1-dimensional `grid`, `H` is a `VolumeOperator`. On a N-dimensional
-`grid`, `H` is the outer product of the 1-dimensional inner product operators in
-each coordinate direction. Also see the documentation of
-`SbpOperators.volume_operator(...)` for more details. On a 0-dimensional `grid`,
+On a 1-dimensional grid, `H` is a `ConstantInteriorScalingOperator`. On a
+N-dimensional grid, `H` is the outer product of the 1-dimensional inner
+product operators for each coordinate direction. On a 0-dimensional grid,
 `H` is a 0-dimensional `IdentityMapping`.
 """
-function inner_product(grid::EquidistantGrid, closure_stencils, inner_stencil = CenteredStencil(one(eltype(grid))))
-    h = spacing(grid)
-    H = SbpOperators.volume_operator(grid, scale(inner_stencil,h[1]), scale.(closure_stencils,h[1]), even, 1)
-    for i ∈ 2:dimension(grid)
-        Hᵢ = SbpOperators.volume_operator(grid, scale(inner_stencil,h[i]), scale.(closure_stencils,h[i]), even, i)
-        H = H∘Hᵢ
+function inner_product(grid::EquidistantGrid, interior_weight, closure_weights)
+    Hs = ()
+
+    for i ∈ 1:dimension(grid)
+        Hs = (Hs..., inner_product(restrict(grid, i), interior_weight, closure_weights))
     end
-    return H
+
+    return foldl(⊗, Hs)
 end
 export inner_product
 
-inner_product(grid::EquidistantGrid{0}, closure_stencils, inner_stencil) = IdentityMapping{eltype(grid)}()
+function inner_product(grid::EquidistantGrid{1}, interior_weight, closure_weights)
+    h = spacing(grid)[1]
+
+    H = SbpOperators.ConstantInteriorScalingOperator(grid, h*interior_weight, h.*closure_weights)
+    return H
+end
+
+inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityMapping{eltype(grid)}()
--- a/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,43 +1,30 @@
 """
-    inverse_inner_product(grid::EquidistantGrid, inv_inner_stencil, inv_closure_stencils)
-    inverse_inner_product(grid::EquidistantGrid, closure_stencils::NTuple{M,Stencil{T,1}})
+    inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights)
 
-Creates the inverse inner product operator `H⁻¹` as a `TensorMapping` on an
-equidistant grid. `H⁻¹` is defined implicitly by `H⁻¹∘H = I`, where
-`H` is the corresponding inner product operator and `I` is the `IdentityMapping`.
-
-`inverse_inner_product(grid::EquidistantGrid, inv_inner_stencil, inv_closure_stencils)`
-constructs `H⁻¹` using a set of stencils `inv_closure_stencils` for the points
-in the closure regions and the stencil `inv_inner_stencil` in the interior. If
-`inv_closure_stencils` is omitted, a central interior stencil with weight 1 is used.
+Constructs the inverse inner product operator `H⁻¹` as a `TensorMapping` using
+the weights of `H`, `interior_weight`, `closure_weights`. `H⁻¹` is inverse of
+the inner product operator `H`.
 
-`inverse_inner_product(grid::EquidistantGrid, closure_stencils::NTuple{M,Stencil{T,1}})`
-constructs a diagonal inverse inner product operator where `closure_stencils` are the
-closure stencils of `H` (not `H⁻¹`!).
+On a 1-dimensional grid, `H⁻¹` is a `ConstantInteriorScalingOperator`. On an
+N-dimensional grid, `H⁻¹` is the outer product of the 1-dimensional inverse
+inner product operators for each coordinate direction. On a 0-dimensional
+`grid`, `H⁻¹` is a 0-dimensional `IdentityMapping`.
+"""
+function inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights)
+    H⁻¹s = ()
 
-On a 1-dimensional `grid`, `H⁻¹` is a `VolumeOperator`. On a N-dimensional
-`grid`, `H⁻¹` is the outer product of the 1-dimensional inverse inner product
-operators in each coordinate direction. Also see the documentation of
-`SbpOperators.volume_operator(...)` for more details. On a 0-dimensional `grid`,
-`H⁻¹` is a 0-dimensional `IdentityMapping`.
-"""
-function inverse_inner_product(grid::EquidistantGrid, inv_closure_stencils, inv_inner_stencil = CenteredStencil(one(eltype(grid))))
-    h⁻¹ = inverse_spacing(grid)
-    H⁻¹ = SbpOperators.volume_operator(grid,scale(inv_inner_stencil,h⁻¹[1]),scale.(inv_closure_stencils,h⁻¹[1]),even,1)
-    for i ∈ 2:dimension(grid)
-        Hᵢ⁻¹ = SbpOperators.volume_operator(grid,scale(inv_inner_stencil,h⁻¹[i]),scale.(inv_closure_stencils,h⁻¹[i]),even,i)
-        H⁻¹ = H⁻¹∘Hᵢ⁻¹
+    for i ∈ 1:dimension(grid)
+        H⁻¹s = (H⁻¹s..., inverse_inner_product(restrict(grid, i), interior_weight, closure_weights))
     end
+
+    return foldl(⊗, H⁻¹s)
+end
+
+function inverse_inner_product(grid::EquidistantGrid{1}, interior_weight, closure_weights)
+    h⁻¹ = inverse_spacing(grid)[1]
+    H⁻¹ = SbpOperators.ConstantInteriorScalingOperator(grid, h⁻¹*1/interior_weight, h⁻¹./closure_weights)
     return H⁻¹
 end
 export inverse_inner_product
 
-inverse_inner_product(grid::EquidistantGrid{0}, inv_closure_stencils, inv_inner_stencil) = IdentityMapping{eltype(grid)}()
-
-function inverse_inner_product(grid::EquidistantGrid, closure_stencils::NTuple{M,Stencil{T,1}}) where {M,T}
-     inv_closure_stencils = reciprocal_stencil.(closure_stencils)
-     inv_inner_stencil = CenteredStencil(one(T))
-     return inverse_inner_product(grid, inv_closure_stencils, inv_inner_stencil)
-end
-
-reciprocal_stencil(s::Stencil{T}) where T = Stencil(s.range,one(T)./s.weights)
+inverse_inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityMapping{eltype(grid)}()
--- a/src/SbpOperators/volumeops/laplace/laplace.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/volumeops/laplace/laplace.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,16 +1,16 @@
 """
     Laplace{T, Dim, TMdiffop} <: TensorMapping{T,Dim,Dim}
-    Laplace(grid::AbstractGrid, fn; order)
+    Laplace(grid, filename; order)
 
 Implements the Laplace operator, approximating ∑d²/xᵢ² , i = 1,...,`Dim` as a
 `TensorMapping`. Additionally, `Laplace` stores the inner product and boundary
-operators relevant for constructing a SBP finite difference scheme as `TensorMapping`s.
+operators relevant for constructing a SBP finite difference scheme as a `TensorMapping`.
 
-Laplace(grid, fn; order) creates the Laplace operator defined on `grid`,
+`Laplace(grid, filename; order)` creates the Laplace operator defined on `grid`,
 where the operators are read from TOML. The differential operator is created
-using `laplace(grid::AbstractGrid,...)`.
+using `laplace(grid,...)`.
 
-Note that all properties of Laplace, excluding the Differential operator `D`, are
+Note that all properties of Laplace, excluding the differential operator `Laplace.D`, are
 abstract types. For performance reasons, they should therefore be
 accessed via the provided getter functions (e.g `inner_product(::Laplace)`).
 
@@ -21,32 +21,34 @@
     H_inv::TensorMapping # Inverse inner product operator
     e::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Boundary restriction operators.
     d::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Normal derivative operators
-    H_boundary::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Boundary quadrature operators # TODO: Boundary inner product?
+    H_boundary::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Boundary quadrature operators
 end
 export Laplace
 
-function Laplace(grid::AbstractGrid, fn; order)
-    # TODO: Removed once we can construct the volume and
-    # boundary operators by op(grid, fn; order,...).
+function Laplace(grid, filename; order)
+    
     # Read stencils
-    op = read_D2_operator(fn; order)
-    D_inner_stecil = op.innerStencil
-    D_closure_stencils = op.closureStencils
-    H_closure_stencils = op.quadratureClosure
-    e_closure_stencil = op.eClosure
-    d_closure_stencil = op.dClosure
+    stencil_set = read_stencil_set(filename; order)
+    # TODO: Removed once we can construct the volume and
+    # boundary operators by op(grid, read_stencil_set(fn; order,...)).
+    D_inner_stecil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+    D_closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+    H_inner_stencils = parse_scalar(stencil_set["H"]["inner"])
+    H_closure_stencils = parse_tuple(stencil_set["H"]["closure"])
+    e_closure_stencil = parse_stencil(stencil_set["e"]["closure"])
+    d_closure_stencil = parse_stencil(stencil_set["d1"]["closure"])
 
     # Volume operators
     Δ =  laplace(grid, D_inner_stecil, D_closure_stencils)
-    H =  inner_product(grid, H_closure_stencils)
-    H⁻¹ =  inverse_inner_product(grid, H_closure_stencils)
+    H =  inner_product(grid, H_inner_stencils, H_closure_stencils)
+    H⁻¹ = inverse_inner_product(grid, H_inner_stencils, H_closure_stencils)
 
     # Boundary operator - id pairs
     ids = boundary_identifiers(grid)
     n_ids = length(ids)
-    e_pairs = ntuple(i -> ids[i] => boundary_restriction(grid,e_closure_stencil,ids[i]),n_ids)
-    d_pairs = ntuple(i -> ids[i] => normal_derivative(grid,d_closure_stencil,ids[i]),n_ids)
-    Hᵧ_pairs = ntuple(i -> ids[i] => inner_product(boundary_grid(grid,ids[i]),H_closure_stencils),n_ids)
+    e_pairs = ntuple(i -> ids[i] => boundary_restriction(grid, e_closure_stencil, ids[i]), n_ids)
+    d_pairs = ntuple(i -> ids[i] => normal_derivative(grid, d_closure_stencil, ids[i]), n_ids)
+    Hᵧ_pairs = ntuple(i -> ids[i] => inner_product(boundary_grid(grid, ids[i]), H_inner_stencils, H_closure_stencils), n_ids)
 
     return Laplace(Δ, H, H⁻¹, StaticDict(e_pairs), StaticDict(d_pairs), StaticDict(Hᵧ_pairs))
 end
@@ -60,7 +62,7 @@
 
 
 """
-    inner_product(L::Lapalace)
+    inner_product(L::Laplace)
 
 Returns the inner product operator associated with `L`
 
@@ -70,7 +72,7 @@
 
 
 """
-    inverse_inner_product(L::Lapalace)
+    inverse_inner_product(L::Laplace)
 
 Returns the inverse of the inner product operator associated with `L`
 
@@ -80,65 +82,64 @@
 
 
 """
-    boundary_restriction(L::Lapalace,id::BoundaryIdentifier)
-    boundary_restriction(L::Lapalace,ids::NTuple{N,BoundaryIdentifier})
-    boundary_restriction(L::Lapalace,ids...)
+    boundary_restriction(L::Laplace, id::BoundaryIdentifier)
+    boundary_restriction(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    boundary_restriction(L::Laplace, ids...)
 
 Returns boundary restriction operator(s) associated with `L` for the boundary(s)
 identified by id(s).
 
 """
-boundary_restriction(L::Laplace,id::BoundaryIdentifier) = L.e[id]
-boundary_restriction(L::Laplace,ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.e[ids[i]],N)
-boundary_restriction(L::Laplace,ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.e[ids[i]],N)
+boundary_restriction(L::Laplace, id::BoundaryIdentifier) = L.e[id]
+boundary_restriction(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.e[ids[i]],N)
+boundary_restriction(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.e[ids[i]],N)
 export boundary_restriction
 
 
 """
-    normal_derivative(L::Lapalace,id::BoundaryIdentifier)
-    normal_derivative(L::Lapalace,ids::NTuple{N,BoundaryIdentifier})
-    normal_derivative(L::Lapalace,ids...)
+    normal_derivative(L::Laplace, id::BoundaryIdentifier)
+    normal_derivative(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    normal_derivative(L::Laplace, ids...)
 
 Returns normal derivative operator(s) associated with `L` for the boundary(s)
 identified by id(s).
 
 """
-normal_derivative(L::Laplace,id::BoundaryIdentifier) = L.d[id]
-normal_derivative(L::Laplace,ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.d[ids[i]],N)
-normal_derivative(L::Laplace,ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.d[ids[i]],N)
+normal_derivative(L::Laplace, id::BoundaryIdentifier) = L.d[id]
+normal_derivative(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.d[ids[i]],N)
+normal_derivative(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.d[ids[i]],N)
 export normal_derivative
 
 
-# TODO: boundary_inner_product?
 """
-    boundary_quadrature(L::Lapalace,id::BoundaryIdentifier)
-    boundary_quadrature(L::Lapalace,ids::NTuple{N,BoundaryIdentifier})
-    boundary_quadrature(L::Lapalace,ids...)
+    boundary_quadrature(L::Laplace, id::BoundaryIdentifier)
+    boundary_quadrature(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    boundary_quadrature(L::Laplace, ids...)
 
 Returns boundary quadrature operator(s) associated with `L` for the boundary(s)
 identified by id(s).
 
 """
-boundary_quadrature(L::Laplace,id::BoundaryIdentifier) = L.H_boundary[id]
-boundary_quadrature(L::Laplace,ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.H_boundary[ids[i]],N)
-boundary_quadrature(L::Laplace,ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.H_boundary[ids[i]],N)
+boundary_quadrature(L::Laplace, id::BoundaryIdentifier) = L.H_boundary[id]
+boundary_quadrature(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.H_boundary[ids[i]],N)
+boundary_quadrature(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.H_boundary[ids[i]],N)
 export boundary_quadrature
 
 
 """
-    laplace(grid, inner_stencil, closure_stencils)
+    laplace(grid::EquidistantGrid{Dim}, inner_stencil, closure_stencils)
 
 Creates the Laplace operator operator `Δ` as a `TensorMapping`
 
-`Δ` approximates the Laplace operator ∑d²/xᵢ² , i = 1,...,N on the N-dimensional
-`grid`, using the stencil `inner_stencil` in the interior and a set of stencils
-`closure_stencils` for the points in the closure regions.
+`Δ` approximates the Laplace operator ∑d²/xᵢ² , i = 1,...,`Dim` on `grid`, using
+the stencil `inner_stencil` in the interior and a set of stencils `closure_stencils`
+for the points in the closure regions.
 
 On a one-dimensional `grid`, `Δ` is equivalent to `second_derivative`. On a
 multi-dimensional `grid`, `Δ` is the sum of multi-dimensional `second_derivative`s
 where the sum is carried out lazily.
 """
-function laplace(grid::AbstractGrid, inner_stencil, closure_stencils)
+function laplace(grid::EquidistantGrid, inner_stencil, closure_stencils)
     Δ = second_derivative(grid, inner_stencil, closure_stencils, 1)
     for d = 2:dimension(grid)
         Δ += second_derivative(grid, inner_stencil, closure_stencils, d)
--- a/src/SbpOperators/volumeops/volume_operator.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/SbpOperators/volumeops/volume_operator.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,21 +1,22 @@
 """
-    volume_operator(grid,inner_stencil,closure_stencils,parity,direction)
+    volume_operator(grid, inner_stencil, closure_stencils, parity, direction)
+
 Creates a volume operator on a `Dim`-dimensional grid acting along the
-specified coordinate `direction`. The action of the operator is determined by the
-stencils `inner_stencil` and `closure_stencils`.
-When `Dim=1`, the corresponding `VolumeOperator` tensor mapping is returned.
-When `Dim>1`, the `VolumeOperator` `op` is inflated by the outer product
-of `IdentityMappings` in orthogonal coordinate directions, e.g for `Dim=3`,
-the boundary restriction operator in the y-direction direction is `Ix⊗op⊗Iz`.
+specified coordinate `direction`. The action of the operator is determined by
+the stencils `inner_stencil` and `closure_stencils`. When `Dim=1`, the
+corresponding `VolumeOperator` tensor mapping is returned. When `Dim>1`, the
+returned operator is the appropriate outer product of a one-dimensional
+operators and `IdentityMapping`s, e.g for `Dim=3` the volume operator in the
+y-direction is `I⊗op⊗I`.
 """
-function volume_operator(grid::EquidistantGrid{Dim,T}, inner_stencil::Stencil{T}, closure_stencils::NTuple{M,Stencil{T}}, parity, direction) where {Dim,T,M}
+function volume_operator(grid::EquidistantGrid, inner_stencil, closure_stencils, parity, direction)
     #TODO: Check that direction <= Dim?
 
     # Create 1D volume operator in along coordinate direction
     op = VolumeOperator(restrict(grid, direction), inner_stencil, closure_stencils, parity)
     # Create 1D IdentityMappings for each coordinate direction
-    one_d_grids = restrict.(Ref(grid), Tuple(1:Dim))
-    Is = IdentityMapping{T}.(size.(one_d_grids))
+    one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid)))
+    Is = IdentityMapping{eltype(grid)}.(size.(one_d_grids))
     # Formulate the correct outer product sequence of the identity mappings and
     # the volume operator
     parts = Base.setindex(Is, op, direction)
@@ -34,7 +35,7 @@
 end
 
 function VolumeOperator(grid::EquidistantGrid{1}, inner_stencil, closure_stencils, parity)
-    return VolumeOperator(inner_stencil, closure_stencils, size(grid), parity)
+    return VolumeOperator(inner_stencil, Tuple(closure_stencils), size(grid), parity)
 end
 
 closure_size(::VolumeOperator{T,N,M}) where {T,N,M} = M
--- a/src/StaticDicts/StaticDicts.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/src/StaticDicts/StaticDicts.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -10,9 +10,9 @@
 
 The immutable nature means that `StaticDict` can be compared with `===`, in
 constrast to regular `Dict` or `ImmutableDict` which can not. (See
-https://github.com/JuliaLang/julia/issues/4648 for details) One important
+<https://github.com/JuliaLang/julia/issues/4648> for details.) One important
 aspect of this is that `StaticDict` can be used in a struct while still
-allowing the struct to be comared using the default implementation of `==` for
+allowing the struct to be compared using the default implementation of `==` for
 structs.
 
 Lookups are done by linear search.
--- a/test/Manifest.toml	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/Manifest.toml	Wed Jan 19 14:44:24 2022 +0100
@@ -1,144 +1,264 @@
 # This file is machine-generated - editing it directly is not advised
 
-[[Artifacts]]
-deps = ["Pkg"]
-git-tree-sha1 = "c30985d8821e0cd73870b17b0ed0ce6dc44cb744"
+julia_version = "1.7.0"
+manifest_format = "2.0"
+
+[[deps.ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+
+[[deps.Artifacts]]
 uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
-version = "1.3.0"
 
-[[Base64]]
+[[deps.Base64]]
 uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
 
-[[CompilerSupportLibraries_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "8e695f735fca77e9708e795eda62afdb869cbb70"
+[[deps.ChainRulesCore]]
+deps = ["Compat", "LinearAlgebra", "SparseArrays"]
+git-tree-sha1 = "4c26b4e9e91ca528ea212927326ece5918a04b47"
+uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+version = "1.11.2"
+
+[[deps.ChangesOfVariables]]
+deps = ["ChainRulesCore", "LinearAlgebra", "Test"]
+git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1"
+uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0"
+version = "0.1.2"
+
+[[deps.Compat]]
+deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"]
+git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582"
+uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
+version = "3.41.0"
+
+[[deps.CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
 uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
-version = "0.3.4+0"
 
-[[Dates]]
+[[deps.Dates]]
 deps = ["Printf"]
 uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
 
-[[DeepDiffs]]
+[[deps.DeepDiffs]]
 git-tree-sha1 = "9824894295b62a6a4ab6adf1c7bf337b3a9ca34c"
 uuid = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
 version = "1.2.0"
 
-[[DiffRules]]
-deps = ["NaNMath", "Random", "SpecialFunctions"]
-git-tree-sha1 = "eb0c34204c8410888844ada5359ac8b96292cfd1"
+[[deps.DelimitedFiles]]
+deps = ["Mmap"]
+uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
+
+[[deps.DiffRules]]
+deps = ["LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"]
+git-tree-sha1 = "9bc5dac3c8b6706b58ad5ce24cffd9861f07c94f"
 uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
-version = "1.0.1"
+version = "1.9.0"
 
-[[Distributed]]
+[[deps.Distributed]]
 deps = ["Random", "Serialization", "Sockets"]
 uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
 
-[[Glob]]
+[[deps.DocStringExtensions]]
+deps = ["LibGit2"]
+git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b"
+uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
+version = "0.8.6"
+
+[[deps.Downloads]]
+deps = ["ArgTools", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+
+[[deps.Glob]]
 git-tree-sha1 = "4df9f7e06108728ebf00a0a11edee4b29a482bb2"
 uuid = "c27321d9-0574-5035-807b-f59d2c89b15c"
 version = "1.3.0"
 
-[[InteractiveUtils]]
+[[deps.InteractiveUtils]]
 deps = ["Markdown"]
 uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
 
-[[JLLWrappers]]
-git-tree-sha1 = "7cec881362e5b4e367ff0279dd99a06526d51a55"
+[[deps.InverseFunctions]]
+deps = ["Test"]
+git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65"
+uuid = "3587e190-3f89-42d0-90ee-14403ec27112"
+version = "0.1.2"
+
+[[deps.IrrationalConstants]]
+git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151"
+uuid = "92d709cd-6900-40b7-9082-c6be49f344b6"
+version = "0.1.1"
+
+[[deps.JLLWrappers]]
+deps = ["Preferences"]
+git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e"
 uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
-version = "1.1.2"
+version = "1.3.0"
+
+[[deps.LibCURL]]
+deps = ["LibCURL_jll", "MozillaCACerts_jll"]
+uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
 
-[[LibGit2]]
-deps = ["Printf"]
+[[deps.LibCURL_jll]]
+deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
+uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
+
+[[deps.LibGit2]]
+deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
 uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"
 
-[[Libdl]]
+[[deps.LibSSH2_jll]]
+deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
+uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
+
+[[deps.Libdl]]
 uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
 
-[[LinearAlgebra]]
-deps = ["Libdl"]
+[[deps.LinearAlgebra]]
+deps = ["Libdl", "libblastrampoline_jll"]
 uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
 
-[[Logging]]
+[[deps.LogExpFunctions]]
+deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"]
+git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1"
+uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688"
+version = "0.3.6"
+
+[[deps.Logging]]
 uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
 
-[[Markdown]]
+[[deps.Markdown]]
 deps = ["Base64"]
 uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
 
-[[NaNMath]]
-git-tree-sha1 = "c84c576296d0e2fbb3fc134d3e09086b3ea617cd"
+[[deps.MbedTLS_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
+
+[[deps.Mmap]]
+uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+
+[[deps.MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+
+[[deps.NaNMath]]
+git-tree-sha1 = "f755f36b19a5116bb580de457cda0c140153f283"
 uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
-version = "0.3.4"
+version = "0.3.6"
+
+[[deps.NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
 
-[[OpenSpecFun_jll]]
+[[deps.OpenBLAS_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
+uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
+
+[[deps.OpenLibm_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "05823500-19ac-5b8b-9628-191a04bc5112"
+
+[[deps.OpenSpecFun_jll]]
 deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "9db77584158d0ab52307f8c04f8e7c08ca76b5b3"
+git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1"
 uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
-version = "0.5.3+4"
+version = "0.5.5+0"
 
-[[Pkg]]
-deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
+[[deps.Pkg]]
+deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
 uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
 
-[[Printf]]
+[[deps.Preferences]]
+deps = ["TOML"]
+git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a"
+uuid = "21216c6a-2e73-6563-6e65-726566657250"
+version = "1.2.2"
+
+[[deps.Printf]]
 deps = ["Unicode"]
 uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
 
-[[REPL]]
-deps = ["InteractiveUtils", "Markdown", "Sockets"]
+[[deps.REPL]]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
 uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
 
-[[Random]]
-deps = ["Serialization"]
+[[deps.Random]]
+deps = ["SHA", "Serialization"]
 uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
 
-[[Requires]]
+[[deps.Requires]]
 deps = ["UUIDs"]
-git-tree-sha1 = "28faf1c963ca1dc3ec87f166d92982e3c4a1f66d"
+git-tree-sha1 = "8f82019e525f4d5c669692772a6f4b0a58b06a6a"
 uuid = "ae029012-a4dd-5104-9daa-d747884805df"
-version = "1.1.0"
+version = "1.2.0"
 
-[[SHA]]
+[[deps.SHA]]
 uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
 
-[[Serialization]]
+[[deps.Serialization]]
 uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
 
-[[Sockets]]
+[[deps.SharedArrays]]
+deps = ["Distributed", "Mmap", "Random", "Serialization"]
+uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
+
+[[deps.Sockets]]
 uuid = "6462fe0b-24de-5631-8697-dd941f90decc"
 
-[[SpecialFunctions]]
-deps = ["OpenSpecFun_jll"]
-git-tree-sha1 = "d8d8b8a9f4119829410ecd706da4cc8594a1e020"
+[[deps.SparseArrays]]
+deps = ["LinearAlgebra", "Random"]
+uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
+
+[[deps.SpecialFunctions]]
+deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"]
+git-tree-sha1 = "e08890d19787ec25029113e88c34ec20cac1c91e"
 uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
-version = "0.10.3"
+version = "2.0.0"
 
-[[TOML]]
+[[deps.Statistics]]
+deps = ["LinearAlgebra", "SparseArrays"]
+uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+
+[[deps.TOML]]
 deps = ["Dates"]
-git-tree-sha1 = "d0ac7eaad0fb9f6ba023a1d743edca974ae637c4"
 uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
-version = "1.0.0"
 
-[[Test]]
-deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
+[[deps.Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+
+[[deps.Test]]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
 uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
 
-[[TestSetExtensions]]
+[[deps.TestSetExtensions]]
 deps = ["DeepDiffs", "Distributed", "Test"]
 git-tree-sha1 = "3a2919a78b04c29a1a57b05e1618e473162b15d0"
 uuid = "98d24dd4-01ad-11ea-1b02-c9a08f80db04"
 version = "2.0.0"
 
-[[Tullio]]
-deps = ["DiffRules", "LinearAlgebra", "Requires"]
-git-tree-sha1 = "b27ec3ce782f69c1c24f373bfb6aa60300ed57c7"
+[[deps.Tullio]]
+deps = ["ChainRulesCore", "DiffRules", "LinearAlgebra", "Requires"]
+git-tree-sha1 = "0288b7a395fc412952baf756fac94e4f28bfec65"
 uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc"
-version = "0.2.8"
+version = "0.3.2"
 
-[[UUIDs]]
+[[deps.UUIDs]]
 deps = ["Random", "SHA"]
 uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
 
-[[Unicode]]
+[[deps.Unicode]]
 uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+
+[[deps.Zlib_jll]]
+deps = ["Libdl"]
+uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
+
+[[deps.libblastrampoline_jll]]
+deps = ["Artifacts", "Libdl", "OpenBLAS_jll"]
+uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
+
+[[deps.nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+
+[[deps.p7zip_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
--- a/test/SbpOperators/boundaryops/boundary_restriction_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/boundaryops/boundary_restriction_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -8,27 +8,28 @@
 import Sbplib.SbpOperators.BoundaryOperator
 
 @testset "boundary_restriction" begin
-    op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+	stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order = 4)
+	e_closure = parse_stencil(stencil_set["e"]["closure"])
     g_1D = EquidistantGrid(11, 0.0, 1.0)
     g_2D = EquidistantGrid((11,15), (0.0, 0.0), (1.0,1.0))
 
     @testset "boundary_restriction" begin
         @testset "1D" begin
-            e_l = boundary_restriction(g_1D,op.eClosure,Lower())
-            @test e_l == boundary_restriction(g_1D,op.eClosure,CartesianBoundary{1,Lower}())
-            @test e_l == BoundaryOperator(g_1D,op.eClosure,Lower())
+            e_l = boundary_restriction(g_1D,e_closure,Lower())
+            @test e_l == boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Lower}())
+            @test e_l == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Lower())
             @test e_l isa BoundaryOperator{T,Lower} where T
             @test e_l isa TensorMapping{T,0,1} where T
 
-            e_r = boundary_restriction(g_1D,op.eClosure,Upper())
-            @test e_r == boundary_restriction(g_1D,op.eClosure,CartesianBoundary{1,Upper}())
-            @test e_r == BoundaryOperator(g_1D,op.eClosure,Upper())
+            e_r = boundary_restriction(g_1D,e_closure,Upper())
+            @test e_r == boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Upper}())
+            @test e_r == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Upper())
             @test e_r isa BoundaryOperator{T,Upper} where T
             @test e_r isa TensorMapping{T,0,1} where T
         end
 
         @testset "2D" begin
-            e_w = boundary_restriction(g_2D,op.eClosure,CartesianBoundary{1,Upper}())
+            e_w = boundary_restriction(g_2D,e_closure,CartesianBoundary{1,Upper}())
             @test e_w isa InflatedTensorMapping
             @test e_w isa TensorMapping{T,1,2} where T
         end
@@ -36,8 +37,8 @@
 
     @testset "Application" begin
         @testset "1D" begin
-            e_l = boundary_restriction(g_1D, op.eClosure, CartesianBoundary{1,Lower}())
-            e_r = boundary_restriction(g_1D, op.eClosure, CartesianBoundary{1,Upper}())
+            e_l = boundary_restriction(g_1D, e_closure, CartesianBoundary{1,Lower}())
+            e_r = boundary_restriction(g_1D, e_closure, CartesianBoundary{1,Upper}())
 
             v = evalOn(g_1D,x->1+x^2)
             u = fill(3.124)
@@ -48,10 +49,10 @@
         end
 
         @testset "2D" begin
-            e_w = boundary_restriction(g_2D, op.eClosure, CartesianBoundary{1,Lower}())
-            e_e = boundary_restriction(g_2D, op.eClosure, CartesianBoundary{1,Upper}())
-            e_s = boundary_restriction(g_2D, op.eClosure, CartesianBoundary{2,Lower}())
-            e_n = boundary_restriction(g_2D, op.eClosure, CartesianBoundary{2,Upper}())
+            e_w = boundary_restriction(g_2D, e_closure, CartesianBoundary{1,Lower}())
+            e_e = boundary_restriction(g_2D, e_closure, CartesianBoundary{1,Upper}())
+            e_s = boundary_restriction(g_2D, e_closure, CartesianBoundary{2,Lower}())
+            e_n = boundary_restriction(g_2D, e_closure, CartesianBoundary{2,Upper}())
 
             v = rand(11, 15)
             u = fill(3.124)
--- a/test/SbpOperators/boundaryops/normal_derivative_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/boundaryops/normal_derivative_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -11,21 +11,21 @@
     g_1D = EquidistantGrid(11, 0.0, 1.0)
     g_2D = EquidistantGrid((11,12), (0.0, 0.0), (1.0,1.0))
     @testset "normal_derivative" begin
-        op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+    	stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+    	d_closure = parse_stencil(stencil_set["d1"]["closure"])
         @testset "1D" begin
-            d_l = normal_derivative(g_1D, op.dClosure, Lower())
-            @test d_l == normal_derivative(g_1D, op.dClosure, CartesianBoundary{1,Lower}())
+            d_l = normal_derivative(g_1D, d_closure, Lower())
+            @test d_l == normal_derivative(g_1D, d_closure, CartesianBoundary{1,Lower}())
             @test d_l isa BoundaryOperator{T,Lower} where T
             @test d_l isa TensorMapping{T,0,1} where T
         end
         @testset "2D" begin
-            op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-            d_w = normal_derivative(g_2D, op.dClosure, CartesianBoundary{1,Lower}())
-            d_n = normal_derivative(g_2D, op.dClosure, CartesianBoundary{2,Upper}())
+            d_w = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}())
+            d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}())
             Ix = IdentityMapping{Float64}((size(g_2D)[1],))
             Iy = IdentityMapping{Float64}((size(g_2D)[2],))
-            d_l = normal_derivative(restrict(g_2D,1),op.dClosure,Lower())
-            d_r = normal_derivative(restrict(g_2D,2),op.dClosure,Upper())
+            d_l = normal_derivative(restrict(g_2D,1),d_closure,Lower())
+            d_r = normal_derivative(restrict(g_2D,2),d_closure,Upper())
             @test d_w ==  d_l⊗Iy
             @test d_n ==  Ix⊗d_r
             @test d_w isa TensorMapping{T,1,2} where T
@@ -38,11 +38,12 @@
         v∂y = evalOn(g_2D, (x,y)-> 2*(y-1) + x)
         # TODO: Test for higher order polynomials?
         @testset "2nd order" begin
-            op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-            d_w = normal_derivative(g_2D, op.dClosure, CartesianBoundary{1,Lower}())
-            d_e = normal_derivative(g_2D, op.dClosure, CartesianBoundary{1,Upper}())
-            d_s = normal_derivative(g_2D, op.dClosure, CartesianBoundary{2,Lower}())
-            d_n = normal_derivative(g_2D, op.dClosure, CartesianBoundary{2,Upper}())
+        	stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+        	d_closure = parse_stencil(stencil_set["d1"]["closure"])
+            d_w = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}())
+            d_e = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Upper}())
+            d_s = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Lower}())
+            d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}())
 
             @test d_w*v ≈ v∂x[1,:] atol = 1e-13
             @test d_e*v ≈ -v∂x[end,:] atol = 1e-13
@@ -51,11 +52,12 @@
         end
 
         @testset "4th order" begin
-            op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-            d_w = normal_derivative(g_2D, op.dClosure, CartesianBoundary{1,Lower}())
-            d_e = normal_derivative(g_2D, op.dClosure, CartesianBoundary{1,Upper}())
-            d_s = normal_derivative(g_2D, op.dClosure, CartesianBoundary{2,Lower}())
-            d_n = normal_derivative(g_2D, op.dClosure, CartesianBoundary{2,Upper}())
+            stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+        	d_closure = parse_stencil(stencil_set["d1"]["closure"])
+            d_w = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}())
+            d_e = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Upper}())
+            d_s = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Lower}())
+            d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}())
 
             @test d_w*v ≈ v∂x[1,:] atol = 1e-13
             @test d_e*v ≈ -v∂x[end,:] atol = 1e-13
--- a/test/SbpOperators/readoperator_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/readoperator_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -5,6 +5,153 @@
 
 import Sbplib.SbpOperators.Stencil
 
+@testset "readoperator" begin
+    toml_str = """
+        [meta]
+        authors = "Ken Mattson"
+        description = "Standard operators for equidistant grids"
+        type = "equidistant"
+        cite = "A paper a long time ago in a galaxy far far away."
+
+        [[stencil_set]]
+
+        order = 2
+        test = 2
+
+        H.inner = ["1"]
+        H.closure = ["1/2"]
+
+        D1.inner_stencil = ["-1/2", "0", "1/2"]
+        D1.closure_stencils = [
+            {s = ["-1", "1"], c = 1},
+        ]
+
+        D2.inner_stencil = ["1", "-2", "1"]
+        D2.closure_stencils = [
+            {s = ["1", "-2", "1"], c = 1},
+        ]
+
+        e.closure = ["1"]
+        d1.closure = {s = ["-3/2", "2", "-1/2"], c = 1}
+
+        [[stencil_set]]
+
+        order = 4
+        test = 1
+        H.inner = ["1"]
+        H.closure = ["17/48", "59/48", "43/48", "49/48"]
+
+        D2.inner_stencil = ["-1/12","4/3","-5/2","4/3","-1/12"]
+        D2.closure_stencils = [
+            {s = [     "2",    "-5",      "4",       "-1",     "0",     "0"], c = 1},
+            {s = [     "1",    "-2",      "1",        "0",     "0",     "0"], c = 2},
+            {s = [ "-4/43", "59/43", "-110/43",   "59/43", "-4/43",     "0"], c = 3},
+            {s = [ "-1/49",     "0",   "59/49", "-118/49", "64/49", "-4/49"], c = 4},
+        ]
+
+        e.closure = ["1"]
+        d1.closure = {s = ["-11/6", "3", "-3/2", "1/3"], c = 1}
+
+        [[stencil_set]]
+        order = 4
+        test = 2
+
+        H.closure = ["-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"]
+    """
+
+    parsed_toml = TOML.parse(toml_str)
+
+    @testset "get_stencil_set" begin
+        @test get_stencil_set(parsed_toml; order = 2) isa Dict
+        @test get_stencil_set(parsed_toml; order = 2) == parsed_toml["stencil_set"][1]
+        @test get_stencil_set(parsed_toml; test = 1) == parsed_toml["stencil_set"][2]
+        @test get_stencil_set(parsed_toml; order = 4, test = 2) == parsed_toml["stencil_set"][3]
+
+        @test_throws ArgumentError get_stencil_set(parsed_toml; test = 2)
+        @test_throws ArgumentError get_stencil_set(parsed_toml; order = 4)
+    end
+
+    @testset "parse_stencil" begin
+        toml = """
+            s1 = ["-1/12","4/3","-5/2","4/3","-1/12"]
+            s2 = {s = ["2", "-5", "4", "-1", "0", "0"], c = 1}
+            s3 = {s = ["1", "-2", "1", "0", "0", "0"], c = 2}
+            s4 = "not a stencil"
+            s5 = [-1, 4, 3]
+            s6 = {k = ["1", "-2", "1", "0", "0", "0"], c = 2}
+            s7 = {s = [-1, 4, 3], c = 2}
+            s8 = {s = ["1", "-2", "1", "0", "0", "0"], c = [2,2]}
+        """
+
+        @test parse_stencil(TOML.parse(toml)["s1"]) == CenteredStencil(-1//12, 4//3, -5//2, 4//3, -1//12)
+        @test parse_stencil(TOML.parse(toml)["s2"]) == Stencil(2//1, -5//1, 4//1, -1//1, 0//1, 0//1; center=1)
+        @test parse_stencil(TOML.parse(toml)["s3"]) == Stencil(1//1, -2//1, 1//1, 0//1, 0//1, 0//1; center=2)
+
+        @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s4"])
+        @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s5"])
+        @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s6"])
+        @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s7"])
+        @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s8"])
+
+        stencil_set = get_stencil_set(parsed_toml; order = 4, test = 1)
+
+        @test parse_stencil.(stencil_set["D2"]["closure_stencils"]) == [
+            Stencil(  2//1,  -5//1,     4//1,    -1//1,   0//1,   0//1; center=1),
+            Stencil(  1//1,  -2//1,     1//1,     0//1,   0//1,   0//1; center=2),
+            Stencil(-4//43, 59//43, -110//43,   59//43, -4//43,   0//1; center=3),
+            Stencil(-1//49,   0//1,   59//49, -118//49, 64//49, -4//49; center=4),
+        ]
+
+
+        @test parse_stencil(Float64, TOML.parse(toml)["s1"]) == CenteredStencil(-1/12, 4/3, -5/2, 4/3, -1/12)
+        @test parse_stencil(Float64, TOML.parse(toml)["s2"]) == Stencil(2/1, -5/1, 4/1, -1/1, 0/1, 0/1; center=1)
+        @test parse_stencil(Float64, TOML.parse(toml)["s3"]) == Stencil(1/1, -2/1, 1/1, 0/1, 0/1, 0/1; center=2)
+    end
+
+    @testset "parse_scalar" begin
+        toml = TOML.parse("""
+            a1 = 1
+            a2 = 1.5
+            a3 = 1.0
+            a4 = 10
+            a5 = "1/2"
+            a6 = "1.5"
+
+            e1 = [1,2,3]
+            e2 = "a string value"
+        """)
+
+        @test parse_scalar(toml["a1"]) == 1//1
+        @test parse_scalar(toml["a2"]) == 3//2
+        @test parse_scalar(toml["a3"]) == 1//1
+        @test parse_scalar(toml["a4"]) == 10//1
+        @test parse_scalar(toml["a5"]) == 1//2
+        @test parse_scalar(toml["a6"]) == 3//2
+
+        @test_throws ArgumentError parse_scalar(toml["e1"])
+        @test_throws ArgumentError parse_scalar(toml["e2"])
+    end
+
+    @testset "parse_tuple" begin
+        toml = TOML.parse("""
+            t1 = [1,3,4]
+            t2 = ["1/2","3/4","2/1"]
+
+            e1 = "not a tuple"
+            e2.a="1"
+            e3 = 1
+            e4 = ["1/2","3/4","not a number"]
+        """)
+
+        @test parse_tuple(toml["t1"]) == (1//1,3//1,4//1)
+        @test parse_tuple(toml["t2"]) == (1//2,3//4,2//1)
+
+        @test_throws ArgumentError parse_tuple(toml["e1"])
+        @test_throws ArgumentError parse_tuple(toml["e2"])
+        @test_throws ArgumentError parse_tuple(toml["e3"])
+        @test_throws ArgumentError parse_tuple(toml["e4"])
+    end
+end
 
 @testset "parse_rational" begin
     @test SbpOperators.parse_rational("1") isa Rational
@@ -13,81 +160,13 @@
     @test SbpOperators.parse_rational("1/2") == 1//2
     @test SbpOperators.parse_rational("37/13") isa Rational
     @test SbpOperators.parse_rational("37/13") == 37//13
-end
 
-@testset "readoperator" begin
-    toml_str = """
-        [meta]
-        type = "equidistant"
-
-        [order2]
-        H.inner = ["1"]
-
-        D1.inner_stencil = ["-1/2", "0", "1/2"]
-        D1.closure_stencils = [
-            ["-1", "1"],
-        ]
-
-        d1.closure = ["-3/2", "2", "-1/2"]
-
-        [order4]
-        H.closure = ["17/48", "59/48", "43/48", "49/48"]
-
-        D2.inner_stencil = ["-1/12","4/3","-5/2","4/3","-1/12"]
-        D2.closure_stencils = [
-            [     "2",    "-5",      "4",       "-1",     "0",     "0"],
-            [     "1",    "-2",      "1",        "0",     "0",     "0"],
-            [ "-4/43", "59/43", "-110/43",   "59/43", "-4/43",     "0"],
-            [ "-1/49",     "0",   "59/49", "-118/49", "64/49", "-4/49"],
-        ]
-    """
-
-    parsed_toml = TOML.parse(toml_str)
-    @testset "get_stencil" begin
-        @test get_stencil(parsed_toml, "order2", "D1", "inner_stencil") == Stencil(-1/2, 0., 1/2, center=2)
-        @test get_stencil(parsed_toml, "order2", "D1", "inner_stencil", center=1) == Stencil(-1/2, 0., 1/2; center=1)
-        @test get_stencil(parsed_toml, "order2", "D1", "inner_stencil", center=3) == Stencil(-1/2, 0., 1/2; center=3)
-
-        @test get_stencil(parsed_toml, "order2", "H", "inner") == Stencil(1.; center=1)
+    @test SbpOperators.parse_rational(0.5) isa Rational
+    @test SbpOperators.parse_rational(0.5) == 1//2
 
-        @test_throws AssertionError get_stencil(parsed_toml, "meta", "type")
-        @test_throws AssertionError get_stencil(parsed_toml, "order2", "D1", "closure_stencils")
-    end
-
-    @testset "get_stencils" begin
-        @test get_stencils(parsed_toml, "order2", "D1", "closure_stencils", centers=(1,)) == (Stencil(-1., 1., center=1),)
-        @test get_stencils(parsed_toml, "order2", "D1", "closure_stencils", centers=(2,)) == (Stencil(-1., 1., center=2),)
-        @test get_stencils(parsed_toml, "order2", "D1", "closure_stencils", centers=[2]) == (Stencil(-1., 1., center=2),)
-
-        @test get_stencils(parsed_toml, "order4", "D2", "closure_stencils",centers=[1,1,1,1]) == (
-            Stencil(    2.,    -5.,      4.,     -1.,    0.,    0., center=1),
-            Stencil(    1.,    -2.,      1.,      0.,    0.,    0., center=1),
-            Stencil( -4/43,  59/43, -110/43,   59/43, -4/43,    0., center=1),
-            Stencil( -1/49,     0.,   59/49, -118/49, 64/49, -4/49, center=1),
-        )
+    @test SbpOperators.parse_rational("0.5") isa Rational
+    @test SbpOperators.parse_rational("0.5") == 1//2
 
-        @test get_stencils(parsed_toml, "order4", "D2", "closure_stencils",centers=(4,2,3,1)) == (
-            Stencil(    2.,    -5.,      4.,     -1.,    0.,    0., center=4),
-            Stencil(    1.,    -2.,      1.,      0.,    0.,    0., center=2),
-            Stencil( -4/43,  59/43, -110/43,   59/43, -4/43,    0., center=3),
-            Stencil( -1/49,     0.,   59/49, -118/49, 64/49, -4/49, center=1),
-        )
-
-        @test get_stencils(parsed_toml, "order4", "D2", "closure_stencils",centers=1:4) == (
-            Stencil(    2.,    -5.,      4.,     -1.,    0.,    0., center=1),
-            Stencil(    1.,    -2.,      1.,      0.,    0.,    0., center=2),
-            Stencil( -4/43,  59/43, -110/43,   59/43, -4/43,    0., center=3),
-            Stencil( -1/49,     0.,   59/49, -118/49, 64/49, -4/49, center=4),
-        )
-
-        @test_throws AssertionError get_stencils(parsed_toml, "order4", "D2", "closure_stencils",centers=(1,2,3))
-        @test_throws AssertionError get_stencils(parsed_toml, "order4", "D2", "closure_stencils",centers=(1,2,3,5,4))
-        @test_throws AssertionError get_stencils(parsed_toml, "order4", "D2", "inner_stencil",centers=(1,2))
-    end
-
-    @testset "get_tuple" begin
-        @test get_tuple(parsed_toml, "order2", "d1", "closure") == (-3/2, 2, -1/2)
-
-        @test_throws AssertionError get_tuple(parsed_toml, "meta", "type")
-    end
+    @test SbpOperators.parse_rational(2) isa Rational
+    @test SbpOperators.parse_rational(2) == 2//1
 end
--- a/test/SbpOperators/stencil_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/stencil_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -15,4 +15,17 @@
 
     @test CenteredStencil(1,2,3,4,5) == Stencil((-2, 2), (1,2,3,4,5))
     @test_throws ArgumentError CenteredStencil(1,2,3,4)
+
+    # Changing the type of the weights
+    @test Stencil{Float64}(Stencil(1,2,3,4,5; center=2)) == Stencil(1.,2.,3.,4.,5.; center=2)
+    @test Stencil{Float64}(CenteredStencil(1,2,3,4,5)) == CenteredStencil(1.,2.,3.,4.,5.)
+    @test Stencil{Int}(Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1,2,3,4,5; center=2)
+    @test Stencil{Rational}(Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1//1,2//1,3//1,4//1,5//1; center=2)
+
+    @testset "convert" begin
+        @test convert(Stencil{Float64}, Stencil(1,2,3,4,5; center=2)) == Stencil(1.,2.,3.,4.,5.; center=2)
+        @test convert(Stencil{Float64}, CenteredStencil(1,2,3,4,5)) == CenteredStencil(1.,2.,3.,4.,5.)
+        @test convert(Stencil{Int}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1,2,3,4,5; center=2)
+        @test convert(Stencil{Rational}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1//1,2//1,3//1,4//1,5//1; center=2)
+    end
 end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/SbpOperators/volumeops/constant_interior_scaling_operator_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,36 @@
+using Test
+
+using Sbplib.LazyTensors
+using Sbplib.SbpOperators
+import Sbplib.SbpOperators: ConstantInteriorScalingOperator
+using Sbplib.Grids
+
+@testset "ConstantInteriorScalingOperator" begin
+    @test ConstantInteriorScalingOperator(1, (2,3), 10) isa ConstantInteriorScalingOperator{Int,2}
+    @test ConstantInteriorScalingOperator(1., (2.,3.), 10) isa ConstantInteriorScalingOperator{Float64,2}
+
+    a = ConstantInteriorScalingOperator(4, (2,3), 10)
+    v = ones(Int, 10)
+    @test a*v == [2,3,4,4,4,4,4,4,3,2]
+    @test a'*v == [2,3,4,4,4,4,4,4,3,2]
+
+    @test range_size(a) == (10,)
+    @test domain_size(a) == (10,)
+
+
+    a = ConstantInteriorScalingOperator(.5, (.1,.2), 7)
+    v = ones(7)
+
+    @test a*v == [.1,.2,.5,.5,.5,.2,.1]
+    @test a'*v == [.1,.2,.5,.5,.5,.2,.1]
+
+    @test range_size(a) == (7,)
+    @test domain_size(a) == (7,)
+
+    @test_throws DomainError ConstantInteriorScalingOperator(4,(2,3), 3)
+
+    @testset "Grid constructor" begin
+        g = EquidistantGrid(11, 0., 2.)
+        @test ConstantInteriorScalingOperator(g, 3., (.1,.2)) isa ConstantInteriorScalingOperator{Float64}
+    end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/SbpOperators/volumeops/derivatives/second_derivative_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -0,0 +1,118 @@
+using Test
+
+using Sbplib.SbpOperators
+using Sbplib.Grids
+using Sbplib.LazyTensors
+
+import Sbplib.SbpOperators.VolumeOperator
+
+@testset "SecondDerivative" begin
+    stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+    inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+    closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+    Lx = 3.5
+    Ly = 3.
+    g_1D = EquidistantGrid(121, 0.0, Lx)
+    g_2D = EquidistantGrid((121,123), (0.0, 0.0), (Lx, Ly))
+
+    @testset "Constructors" begin
+        @testset "1D" begin
+            Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils)
+            @test Dₓₓ == second_derivative(g_1D,inner_stencil,closure_stencils,1)
+            @test Dₓₓ isa VolumeOperator
+        end
+        @testset "2D" begin
+            Dₓₓ = second_derivative(g_2D,inner_stencil,closure_stencils,1)
+            D2 = second_derivative(g_1D,inner_stencil,closure_stencils)
+            I = IdentityMapping{Float64}(size(g_2D)[2])
+            @test Dₓₓ == D2⊗I
+            @test Dₓₓ isa TensorMapping{T,2,2} where T
+        end
+    end
+
+    # Exact differentiation is measured point-wise. In other cases
+    # the error is measured in the l2-norm.
+    @testset "Accuracy" begin
+        @testset "1D" begin
+            l2(v) = sqrt(spacing(g_1D)[1]*sum(v.^2));
+            monomials = ()
+            maxOrder = 4;
+            for i = 0:maxOrder-1
+                f_i(x) = 1/factorial(i)*x^i
+                monomials = (monomials...,evalOn(g_1D,f_i))
+            end
+            v = evalOn(g_1D,x -> sin(x))
+            vₓₓ = evalOn(g_1D,x -> -sin(x))
+
+            # 2nd order interior stencil, 1nd order boundary stencil,
+            # implies that L*v should be exact for monomials up to order 2.
+            @testset "2nd order" begin
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+			    closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+                Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils)
+                @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
+                @test Dₓₓ*monomials[2] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
+                @test Dₓₓ*monomials[3] ≈ monomials[1] atol = 5e-10
+                @test Dₓₓ*v ≈ vₓₓ rtol = 5e-2 norm = l2
+            end
+
+            # 4th order interior stencil, 2nd order boundary stencil,
+            # implies that L*v should be exact for monomials up to order 3.
+            @testset "4th order" begin
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+			    closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+                Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils)
+                # NOTE: high tolerances for checking the "exact" differentiation
+                # due to accumulation of round-off errors/cancellation errors?
+                @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
+                @test Dₓₓ*monomials[2] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
+                @test Dₓₓ*monomials[3] ≈ monomials[1] atol = 5e-10
+                @test Dₓₓ*monomials[4] ≈ monomials[2] atol = 5e-10
+                @test Dₓₓ*v ≈ vₓₓ rtol = 5e-4 norm = l2
+            end
+        end
+
+        @testset "2D" begin
+            l2(v) = sqrt(prod(spacing(g_2D))*sum(v.^2));
+            binomials = ()
+            maxOrder = 4;
+            for i = 0:maxOrder-1
+                f_i(x,y) = 1/factorial(i)*y^i + x^i
+                binomials = (binomials...,evalOn(g_2D,f_i))
+            end
+            v = evalOn(g_2D, (x,y) -> sin(x)+cos(y))
+            v_yy = evalOn(g_2D,(x,y) -> -cos(y))
+
+            # 2nd order interior stencil, 1st order boundary stencil,
+            # implies that L*v should be exact for binomials up to order 2.
+            @testset "2nd order" begin
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+                closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+                Dyy = second_derivative(g_2D,inner_stencil,closure_stencils,2)
+                @test Dyy*binomials[1] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
+                @test Dyy*binomials[2] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
+                @test Dyy*binomials[3] ≈ evalOn(g_2D,(x,y)->1.) atol = 5e-9
+                @test Dyy*v ≈ v_yy rtol = 5e-2 norm = l2
+            end
+
+            # 4th order interior stencil, 2nd order boundary stencil,
+            # implies that L*v should be exact for binomials up to order 3.
+            @testset "4th order" begin
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+                closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+                Dyy = second_derivative(g_2D,inner_stencil,closure_stencils,2)
+                # NOTE: high tolerances for checking the "exact" differentiation
+                # due to accumulation of round-off errors/cancellation errors?
+                @test Dyy*binomials[1] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
+                @test Dyy*binomials[2] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
+                @test Dyy*binomials[3] ≈ evalOn(g_2D,(x,y)->1.) atol = 5e-9
+                @test Dyy*binomials[4] ≈ evalOn(g_2D,(x,y)->y) atol = 5e-9
+                @test Dyy*v ≈ v_yy rtol = 5e-4 norm = l2
+            end
+        end
+    end
+end
--- a/test/SbpOperators/volumeops/derivatives/secondderivative_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-using Test
-
-using Sbplib.SbpOperators
-using Sbplib.Grids
-using Sbplib.LazyTensors
-
-import Sbplib.SbpOperators.VolumeOperator
-
-@testset "SecondDerivative" begin
-    op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-    Lx = 3.5
-    Ly = 3.
-    g_1D = EquidistantGrid(121, 0.0, Lx)
-    g_2D = EquidistantGrid((121,123), (0.0, 0.0), (Lx, Ly))
-
-    @testset "Constructors" begin
-        @testset "1D" begin
-            Dₓₓ = second_derivative(g_1D,op.innerStencil,op.closureStencils)
-            @test Dₓₓ == second_derivative(g_1D,op.innerStencil,op.closureStencils,1)
-            @test Dₓₓ isa VolumeOperator
-        end
-        @testset "2D" begin
-            Dₓₓ = second_derivative(g_2D,op.innerStencil,op.closureStencils,1)
-            D2 = second_derivative(g_1D,op.innerStencil,op.closureStencils)
-            I = IdentityMapping{Float64}(size(g_2D)[2])
-            @test Dₓₓ == D2⊗I
-            @test Dₓₓ isa TensorMapping{T,2,2} where T
-        end
-    end
-
-    # Exact differentiation is measured point-wise. In other cases
-    # the error is measured in the l2-norm.
-    @testset "Accuracy" begin
-        @testset "1D" begin
-            l2(v) = sqrt(spacing(g_1D)[1]*sum(v.^2));
-            monomials = ()
-            maxOrder = 4;
-            for i = 0:maxOrder-1
-                f_i(x) = 1/factorial(i)*x^i
-                monomials = (monomials...,evalOn(g_1D,f_i))
-            end
-            v = evalOn(g_1D,x -> sin(x))
-            vₓₓ = evalOn(g_1D,x -> -sin(x))
-
-            # 2nd order interior stencil, 1nd order boundary stencil,
-            # implies that L*v should be exact for monomials up to order 2.
-            @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                Dₓₓ = second_derivative(g_1D,op.innerStencil,op.closureStencils)
-                @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
-                @test Dₓₓ*monomials[2] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
-                @test Dₓₓ*monomials[3] ≈ monomials[1] atol = 5e-10
-                @test Dₓₓ*v ≈ vₓₓ rtol = 5e-2 norm = l2
-            end
-
-            # 4th order interior stencil, 2nd order boundary stencil,
-            # implies that L*v should be exact for monomials up to order 3.
-            @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                Dₓₓ = second_derivative(g_1D,op.innerStencil,op.closureStencils)
-                # NOTE: high tolerances for checking the "exact" differentiation
-                # due to accumulation of round-off errors/cancellation errors?
-                @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
-                @test Dₓₓ*monomials[2] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10
-                @test Dₓₓ*monomials[3] ≈ monomials[1] atol = 5e-10
-                @test Dₓₓ*monomials[4] ≈ monomials[2] atol = 5e-10
-                @test Dₓₓ*v ≈ vₓₓ rtol = 5e-4 norm = l2
-            end
-        end
-
-        @testset "2D" begin
-            l2(v) = sqrt(prod(spacing(g_2D))*sum(v.^2));
-            binomials = ()
-            maxOrder = 4;
-            for i = 0:maxOrder-1
-                f_i(x,y) = 1/factorial(i)*y^i + x^i
-                binomials = (binomials...,evalOn(g_2D,f_i))
-            end
-            v = evalOn(g_2D, (x,y) -> sin(x)+cos(y))
-            v_yy = evalOn(g_2D,(x,y) -> -cos(y))
-
-            # 2nd order interior stencil, 1st order boundary stencil,
-            # implies that L*v should be exact for binomials up to order 2.
-            @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                Dyy = second_derivative(g_2D,op.innerStencil,op.closureStencils,2)
-                @test Dyy*binomials[1] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
-                @test Dyy*binomials[2] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
-                @test Dyy*binomials[3] ≈ evalOn(g_2D,(x,y)->1.) atol = 5e-9
-                @test Dyy*v ≈ v_yy rtol = 5e-2 norm = l2
-            end
-
-            # 4th order interior stencil, 2nd order boundary stencil,
-            # implies that L*v should be exact for binomials up to order 3.
-            @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                Dyy = second_derivative(g_2D,op.innerStencil,op.closureStencils,2)
-                # NOTE: high tolerances for checking the "exact" differentiation
-                # due to accumulation of round-off errors/cancellation errors?
-                @test Dyy*binomials[1] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
-                @test Dyy*binomials[2] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9
-                @test Dyy*binomials[3] ≈ evalOn(g_2D,(x,y)->1.) atol = 5e-9
-                @test Dyy*binomials[4] ≈ evalOn(g_2D,(x,y)->y) atol = 5e-9
-                @test Dyy*v ≈ v_yy rtol = 5e-4 norm = l2
-            end
-        end
-    end
-end
--- a/test/SbpOperators/volumeops/inner_products/inner_product_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/volumeops/inner_products/inner_product_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -14,36 +14,39 @@
     g_3D = EquidistantGrid((10,10, 10), (0.0, 0.0, 0.0), (Lx,Ly,Lz))
     integral(H,v) = sum(H*v)
     @testset "inner_product" begin
-        op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+        quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
         @testset "0D" begin
-            H = inner_product(EquidistantGrid{Float64}(),op.quadratureClosure)
+            H = inner_product(EquidistantGrid{Float64}(), quadrature_interior, quadrature_closure)
             @test H == IdentityMapping{Float64}()
             @test H isa TensorMapping{T,0,0} where T
         end
         @testset "1D" begin
-            H = inner_product(g_1D,op.quadratureClosure)
-            inner_stencil = CenteredStencil(1.)
-            @test H == inner_product(g_1D,op.quadratureClosure,inner_stencil)
+            H = inner_product(g_1D, quadrature_interior, quadrature_closure)
+            @test H == inner_product(g_1D, quadrature_interior, quadrature_closure)
             @test H isa TensorMapping{T,1,1} where T
         end
         @testset "2D" begin
-            H = inner_product(g_2D,op.quadratureClosure)
-            H_x = inner_product(restrict(g_2D,1),op.quadratureClosure)
-            H_y = inner_product(restrict(g_2D,2),op.quadratureClosure)
+            H = inner_product(g_2D, quadrature_interior, quadrature_closure)
+            H_x = inner_product(restrict(g_2D,1), quadrature_interior, quadrature_closure)
+            H_y = inner_product(restrict(g_2D,2), quadrature_interior, quadrature_closure)
             @test H == H_x⊗H_y
             @test H isa TensorMapping{T,2,2} where T
         end
     end
 
     @testset "Sizes" begin
-        op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+        quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
         @testset "1D" begin
-            H = inner_product(g_1D,op.quadratureClosure)
+            H = inner_product(g_1D, quadrature_interior, quadrature_closure)
             @test domain_size(H) == size(g_1D)
             @test range_size(H) == size(g_1D)
         end
         @testset "2D" begin
-            H = inner_product(g_2D,op.quadratureClosure)
+            H = inner_product(g_2D, quadrature_interior, quadrature_closure)
             @test domain_size(H) == size(g_2D)
             @test range_size(H) == size(g_2D)
         end
@@ -59,8 +62,10 @@
             u = evalOn(g_1D,x->sin(x))
 
             @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                H = inner_product(g_1D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_1D, quadrature_interior, quadrature_closure)
                 for i = 1:2
                     @test integral(H,v[i]) ≈ v[i+1][end] - v[i+1][1] rtol = 1e-14
                 end
@@ -68,8 +73,10 @@
             end
 
             @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                H = inner_product(g_1D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_1D, quadrature_interior, quadrature_closure)
                 for i = 1:4
                     @test integral(H,v[i]) ≈ v[i+1][end] -  v[i+1][1] rtol = 1e-14
                 end
@@ -82,14 +89,18 @@
             v = b*ones(Float64, size(g_2D))
             u = evalOn(g_2D,(x,y)->sin(x)+cos(y))
             @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                H = inner_product(g_2D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_2D, quadrature_interior, quadrature_closure)
                 @test integral(H,v) ≈ b*Lx*Ly rtol = 1e-13
                 @test integral(H,u) ≈ π rtol = 1e-4
             end
             @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                H = inner_product(g_2D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_2D, quadrature_interior, quadrature_closure)
                 @test integral(H,v) ≈ b*Lx*Ly rtol = 1e-13
                 @test integral(H,u) ≈ π rtol = 1e-8
             end
--- a/test/SbpOperators/volumeops/inner_products/inverse_inner_product_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/volumeops/inner_products/inverse_inner_product_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -12,40 +12,38 @@
     g_1D = EquidistantGrid(77, 0.0, Lx)
     g_2D = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly))
     @testset "inverse_inner_product" begin
-        op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+        quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
         @testset "0D" begin
-            Hi = inverse_inner_product(EquidistantGrid{Float64}(),op.quadratureClosure)
+            Hi = inverse_inner_product(EquidistantGrid{Float64}(), quadrature_interior, quadrature_closure)
             @test Hi == IdentityMapping{Float64}()
             @test Hi isa TensorMapping{T,0,0} where T
         end
         @testset "1D" begin
-            Hi = inverse_inner_product(g_1D, op.quadratureClosure);
-            inner_stencil = CenteredStencil(1.)
-            closures = ()
-            for i = 1:length(op.quadratureClosure)
-                closures = (closures...,Stencil(op.quadratureClosure[i].range,1.0./op.quadratureClosure[i].weights))
-            end
-            @test Hi == inverse_inner_product(g_1D,closures,inner_stencil)
+            Hi = inverse_inner_product(g_1D,  quadrature_interior, quadrature_closure)
             @test Hi isa TensorMapping{T,1,1} where T
         end
         @testset "2D" begin
-            Hi = inverse_inner_product(g_2D,op.quadratureClosure)
-            Hi_x = inverse_inner_product(restrict(g_2D,1),op.quadratureClosure)
-            Hi_y = inverse_inner_product(restrict(g_2D,2),op.quadratureClosure)
+            Hi = inverse_inner_product(g_2D, quadrature_interior, quadrature_closure)
+            Hi_x = inverse_inner_product(restrict(g_2D,1), quadrature_interior, quadrature_closure)
+            Hi_y = inverse_inner_product(restrict(g_2D,2), quadrature_interior, quadrature_closure)
             @test Hi == Hi_x⊗Hi_y
             @test Hi isa TensorMapping{T,2,2} where T
         end
     end
 
     @testset "Sizes" begin
-        op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+        quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
         @testset "1D" begin
-            Hi = inverse_inner_product(g_1D,op.quadratureClosure)
+            Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure)
             @test domain_size(Hi) == size(g_1D)
             @test range_size(Hi) == size(g_1D)
         end
         @testset "2D" begin
-            Hi = inverse_inner_product(g_2D,op.quadratureClosure)
+            Hi = inverse_inner_product(g_2D, quadrature_interior, quadrature_closure)
             @test domain_size(Hi) == size(g_2D)
             @test range_size(Hi) == size(g_2D)
         end
@@ -56,16 +54,20 @@
             v = evalOn(g_1D,x->sin(x))
             u = evalOn(g_1D,x->x^3-x^2+1)
             @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                H = inner_product(g_1D,op.quadratureClosure)
-                Hi = inverse_inner_product(g_1D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_1D, quadrature_interior, quadrature_closure)
+                Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure)
                 @test Hi*H*v ≈ v rtol = 1e-15
                 @test Hi*H*u ≈ u rtol = 1e-15
             end
             @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                H = inner_product(g_1D,op.quadratureClosure)
-                Hi = inverse_inner_product(g_1D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_1D, quadrature_interior, quadrature_closure)
+                Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure)
                 @test Hi*H*v ≈ v rtol = 1e-15
                 @test Hi*H*u ≈ u rtol = 1e-15
             end
@@ -74,16 +76,20 @@
             v = evalOn(g_2D,(x,y)->sin(x)+cos(y))
             u = evalOn(g_2D,(x,y)->x*y + x^5 - sqrt(y))
             @testset "2nd order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=2)
-                H = inner_product(g_2D,op.quadratureClosure)
-                Hi = inverse_inner_product(g_2D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_2D, quadrature_interior, quadrature_closure)
+                Hi = inverse_inner_product(g_2D, quadrature_interior, quadrature_closure)
                 @test Hi*H*v ≈ v rtol = 1e-15
                 @test Hi*H*u ≈ u rtol = 1e-15
             end
             @testset "4th order" begin
-                op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-                H = inner_product(g_2D,op.quadratureClosure)
-                Hi = inverse_inner_product(g_2D,op.quadratureClosure)
+                stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+                quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+                quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+                H = inner_product(g_2D, quadrature_interior, quadrature_closure)
+                Hi = inverse_inner_product(g_2D, quadrature_interior, quadrature_closure)
                 @test Hi*H*v ≈ v rtol = 1e-15
                 @test Hi*H*u ≈ u rtol = 1e-15
             end
--- a/test/SbpOperators/volumeops/laplace/laplace_test.jl	Fri Jul 02 14:23:33 2021 +0200
+++ b/test/SbpOperators/volumeops/laplace/laplace_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -6,75 +6,82 @@
 using Sbplib.RegionIndices
 using Sbplib.StaticDicts
 
+operator_path = sbp_operators_path()*"standard_diagonal.toml"
+# Default stencils (4th order)
+stencil_set = read_stencil_set(operator_path; order=4)
+inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+e_closure = parse_stencil(stencil_set["e"]["closure"])
+d_closure = parse_stencil(stencil_set["d1"]["closure"])
+quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
+
 @testset "Laplace" begin
     g_1D = EquidistantGrid(101, 0.0, 1.)
     g_3D = EquidistantGrid((51,101,52), (0.0, -1.0, 0.0), (1., 1., 1.))
-    op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4)
     @testset "Constructors" begin
 
         @testset "1D" begin
-            # Create all tensor mappings included in Laplace
-            Δ = laplace(g_1D, op.innerStencil, op.closureStencils)
-            H = inner_product(g_1D, op.quadratureClosure)
-            Hi = inverse_inner_product(g_1D, op.quadratureClosure)
+            Δ = laplace(g_1D, inner_stencil, closure_stencils)
+            H = inner_product(g_1D, quadrature_interior, quadrature_closure)
+            Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure)
 
             (id_l, id_r) = boundary_identifiers(g_1D)
 
-            e_l = boundary_restriction(g_1D,op.eClosure,id_l)
-            e_r = boundary_restriction(g_1D,op.eClosure,id_r)
+            e_l = boundary_restriction(g_1D, e_closure,id_l)
+            e_r = boundary_restriction(g_1D, e_closure,id_r)
             e_dict = StaticDict(id_l => e_l, id_r => e_r)
 
-            d_l = normal_derivative(g_1D,op.dClosure,id_l)
-            d_r = normal_derivative(g_1D,op.dClosure,id_r)
+            d_l = normal_derivative(g_1D, d_closure,id_l)
+            d_r = normal_derivative(g_1D, d_closure,id_r)
             d_dict = StaticDict(id_l => d_l, id_r => d_r)
 
-            H_l = inner_product(boundary_grid(g_1D,id_l),op.quadratureClosure)
-            H_r = inner_product(boundary_grid(g_1D,id_r),op.quadratureClosure)
+            H_l = inner_product(boundary_grid(g_1D,id_l), quadrature_interior, quadrature_closure)
+            H_r = inner_product(boundary_grid(g_1D,id_r), quadrature_interior, quadrature_closure)
             Hb_dict = StaticDict(id_l => H_l, id_r => H_r)
 
-            L = Laplace(g_1D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
-            @test L == Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
+            L = Laplace(g_1D, operator_path; order=4)
+            @test L == Laplace(Δ, H, Hi, e_dict, d_dict, Hb_dict)
             @test L isa TensorMapping{T,1,1}  where T
-            @inferred Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
+            @inferred Laplace(Δ, H, Hi, e_dict, d_dict, Hb_dict)
         end
         @testset "3D" begin
-            # Create all tensor mappings included in Laplace
-            Δ = laplace(g_3D, op.innerStencil, op.closureStencils)
-            H = inner_product(g_3D, op.quadratureClosure)
-            Hi = inverse_inner_product(g_3D, op.quadratureClosure)
+            Δ = laplace(g_3D, inner_stencil, closure_stencils)
+            H = inner_product(g_3D, quadrature_interior, quadrature_closure)
+            Hi = inverse_inner_product(g_3D, quadrature_interior, quadrature_closure)
 
             (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
-            e_l = boundary_restriction(g_3D,op.eClosure,id_l)
-            e_r = boundary_restriction(g_3D,op.eClosure,id_r)
-            e_s = boundary_restriction(g_3D,op.eClosure,id_s)
-            e_n = boundary_restriction(g_3D,op.eClosure,id_n)
-            e_b = boundary_restriction(g_3D,op.eClosure,id_b)
-            e_t = boundary_restriction(g_3D,op.eClosure,id_t)
+            e_l = boundary_restriction(g_3D, e_closure,id_l)
+            e_r = boundary_restriction(g_3D, e_closure,id_r)
+            e_s = boundary_restriction(g_3D, e_closure,id_s)
+            e_n = boundary_restriction(g_3D, e_closure,id_n)
+            e_b = boundary_restriction(g_3D, e_closure,id_b)
+            e_t = boundary_restriction(g_3D, e_closure,id_t)
             e_dict = StaticDict(id_l => e_l, id_r => e_r,
                                 id_s => e_s, id_n => e_n,
                                 id_b => e_b, id_t => e_t)
 
-            d_l = normal_derivative(g_3D,op.dClosure,id_l)
-            d_r = normal_derivative(g_3D,op.dClosure,id_r)
-            d_s = normal_derivative(g_3D,op.dClosure,id_s)
-            d_n = normal_derivative(g_3D,op.dClosure,id_n)
-            d_b = normal_derivative(g_3D,op.dClosure,id_b)
-            d_t = normal_derivative(g_3D,op.dClosure,id_t)
+            d_l = normal_derivative(g_3D, d_closure,id_l)
+            d_r = normal_derivative(g_3D, d_closure,id_r)
+            d_s = normal_derivative(g_3D, d_closure,id_s)
+            d_n = normal_derivative(g_3D, d_closure,id_n)
+            d_b = normal_derivative(g_3D, d_closure,id_b)
+            d_t = normal_derivative(g_3D, d_closure,id_t)
             d_dict = StaticDict(id_l => d_l, id_r => d_r,
                                 id_s => d_s, id_n => d_n,
                                 id_b => d_b, id_t => d_t)
 
-            H_l = inner_product(boundary_grid(g_3D,id_l),op.quadratureClosure)
-            H_r = inner_product(boundary_grid(g_3D,id_r),op.quadratureClosure)
-            H_s = inner_product(boundary_grid(g_3D,id_s),op.quadratureClosure)
-            H_n = inner_product(boundary_grid(g_3D,id_n),op.quadratureClosure)
-            H_b = inner_product(boundary_grid(g_3D,id_b),op.quadratureClosure)
-            H_t = inner_product(boundary_grid(g_3D,id_t),op.quadratureClosure)
+            H_l = inner_product(boundary_grid(g_3D,id_l), quadrature_interior, quadrature_closure)
+            H_r = inner_product(boundary_grid(g_3D,id_r), quadrature_interior, quadrature_closure)
+            H_s = inner_product(boundary_grid(g_3D,id_s), quadrature_interior, quadrature_closure)
+            H_n = inner_product(boundary_grid(g_3D,id_n), quadrature_interior, quadrature_closure)
+            H_b = inner_product(boundary_grid(g_3D,id_b), quadrature_interior, quadrature_closure)
+            H_t = inner_product(boundary_grid(g_3D,id_t), quadrature_interior, quadrature_closure)
             Hb_dict = StaticDict(id_l => H_l, id_r => H_r,
                                  id_s => H_s, id_n => H_n,
                                  id_b => H_b, id_t => H_t)
 
-            L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
+            L = Laplace(g_3D, operator_path; order=4)
             @test L == Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
             @test L isa TensorMapping{T,3,3} where T
             @inferred Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
@@ -83,92 +90,92 @@
 
     @testset "laplace" begin
         @testset "1D" begin
-            L = laplace(g_1D, op.innerStencil, op.closureStencils)
-            @test L == second_derivative(g_1D, op.innerStencil, op.closureStencils)
+            L = laplace(g_1D, inner_stencil, closure_stencils)
+            @test L == second_derivative(g_1D, inner_stencil, closure_stencils)
             @test L isa TensorMapping{T,1,1}  where T
         end
         @testset "3D" begin
-            L = laplace(g_3D, op.innerStencil, op.closureStencils)
+            L = laplace(g_3D, inner_stencil, closure_stencils)
             @test L isa TensorMapping{T,3,3} where T
-            Dxx = second_derivative(g_3D, op.innerStencil, op.closureStencils,1)
-            Dyy = second_derivative(g_3D, op.innerStencil, op.closureStencils,2)
-            Dzz = second_derivative(g_3D, op.innerStencil, op.closureStencils,3)
+            Dxx = second_derivative(g_3D, inner_stencil, closure_stencils, 1)
+            Dyy = second_derivative(g_3D, inner_stencil, closure_stencils, 2)
+            Dzz = second_derivative(g_3D, inner_stencil, closure_stencils, 3)
             @test L == Dxx + Dyy + Dzz
             @test L isa TensorMapping{T,3,3} where T
         end
     end
 
     @testset "inner_product" begin
-        L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
-        @test inner_product(L) == inner_product(g_3D,op.quadratureClosure)
+        L = Laplace(g_3D, operator_path; order=4)
+        @test inner_product(L) == inner_product(g_3D, quadrature_interior, quadrature_closure)
     end
 
     @testset "inverse_inner_product" begin
-        L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
-        @test inverse_inner_product(L) == inverse_inner_product(g_3D,op.quadratureClosure)
+        L = Laplace(g_3D, operator_path; order=4)
+        @test inverse_inner_product(L) == inverse_inner_product(g_3D, quadrature_interior, quadrature_closure)
     end
 
     @testset "boundary_restriction" begin
-        L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        L = Laplace(g_3D, operator_path; order=4)
         (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
-        @test boundary_restriction(L,id_l) == boundary_restriction(g_3D,op.eClosure,id_l)
-        @test boundary_restriction(L,id_r) == boundary_restriction(g_3D,op.eClosure,id_r)
-        @test boundary_restriction(L,id_s) == boundary_restriction(g_3D,op.eClosure,id_s)
-        @test boundary_restriction(L,id_n) == boundary_restriction(g_3D,op.eClosure,id_n)
-        @test boundary_restriction(L,id_b) == boundary_restriction(g_3D,op.eClosure,id_b)
-        @test boundary_restriction(L,id_t) == boundary_restriction(g_3D,op.eClosure,id_t)
+        @test boundary_restriction(L, id_l) == boundary_restriction(g_3D, e_closure,id_l)
+        @test boundary_restriction(L, id_r) == boundary_restriction(g_3D, e_closure,id_r)
+        @test boundary_restriction(L, id_s) == boundary_restriction(g_3D, e_closure,id_s)
+        @test boundary_restriction(L, id_n) == boundary_restriction(g_3D, e_closure,id_n)
+        @test boundary_restriction(L, id_b) == boundary_restriction(g_3D, e_closure,id_b)
+        @test boundary_restriction(L, id_t) == boundary_restriction(g_3D, e_closure,id_t)
 
         ids = boundary_identifiers(g_3D)
-        es = boundary_restriction(L,ids)
-        @test es ==  (boundary_restriction(L,id_l),
-                      boundary_restriction(L,id_r),
-                      boundary_restriction(L,id_s),
-                      boundary_restriction(L,id_n),
-                      boundary_restriction(L,id_b),
-                      boundary_restriction(L,id_t));
-        @test es == boundary_restriction(L,ids...)
+        es = boundary_restriction(L, ids)
+        @test es ==  (boundary_restriction(L, id_l),
+                      boundary_restriction(L, id_r),
+                      boundary_restriction(L, id_s),
+                      boundary_restriction(L, id_n),
+                      boundary_restriction(L, id_b),
+                      boundary_restriction(L, id_t));
+        @test es == boundary_restriction(L, ids...)
     end
 
     @testset "normal_derivative" begin
-        L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        L = Laplace(g_3D, operator_path; order=4)
         (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
-        @test normal_derivative(L,id_l) == normal_derivative(g_3D,op.dClosure,id_l)
-        @test normal_derivative(L,id_r) == normal_derivative(g_3D,op.dClosure,id_r)
-        @test normal_derivative(L,id_s) == normal_derivative(g_3D,op.dClosure,id_s)
-        @test normal_derivative(L,id_n) == normal_derivative(g_3D,op.dClosure,id_n)
-        @test normal_derivative(L,id_b) == normal_derivative(g_3D,op.dClosure,id_b)
-        @test normal_derivative(L,id_t) == normal_derivative(g_3D,op.dClosure,id_t)
+        @test normal_derivative(L, id_l) == normal_derivative(g_3D, d_closure,id_l)
+        @test normal_derivative(L, id_r) == normal_derivative(g_3D, d_closure,id_r)
+        @test normal_derivative(L, id_s) == normal_derivative(g_3D, d_closure,id_s)
+        @test normal_derivative(L, id_n) == normal_derivative(g_3D, d_closure,id_n)
+        @test normal_derivative(L, id_b) == normal_derivative(g_3D, d_closure,id_b)
+        @test normal_derivative(L, id_t) == normal_derivative(g_3D, d_closure,id_t)
 
         ids = boundary_identifiers(g_3D)
-        ds = normal_derivative(L,ids)
-        @test ds ==  (normal_derivative(L,id_l),
-                      normal_derivative(L,id_r),
-                      normal_derivative(L,id_s),
-                      normal_derivative(L,id_n),
-                      normal_derivative(L,id_b),
-                      normal_derivative(L,id_t));
-        @test ds == normal_derivative(L,ids...)
+        ds = normal_derivative(L, ids)
+        @test ds ==  (normal_derivative(L, id_l),
+                      normal_derivative(L, id_r),
+                      normal_derivative(L, id_s),
+                      normal_derivative(L, id_n),
+                      normal_derivative(L, id_b),
+                      normal_derivative(L, id_t));
+        @test ds == normal_derivative(L, ids...)
     end
 
     @testset "boundary_quadrature" begin
-        L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
+        L = Laplace(g_3D, operator_path; order=4)
         (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
-        @test boundary_quadrature(L,id_l) == inner_product(boundary_grid(g_3D,id_l),op.quadratureClosure)
-        @test boundary_quadrature(L,id_r) == inner_product(boundary_grid(g_3D,id_r),op.quadratureClosure)
-        @test boundary_quadrature(L,id_s) == inner_product(boundary_grid(g_3D,id_s),op.quadratureClosure)
-        @test boundary_quadrature(L,id_n) == inner_product(boundary_grid(g_3D,id_n),op.quadratureClosure)
-        @test boundary_quadrature(L,id_b) == inner_product(boundary_grid(g_3D,id_b),op.quadratureClosure)
-        @test boundary_quadrature(L,id_t) == inner_product(boundary_grid(g_3D,id_t),op.quadratureClosure)
+        @test boundary_quadrature(L, id_l) == inner_product(boundary_grid(g_3D, id_l), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_r) == inner_product(boundary_grid(g_3D, id_r), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_s) == inner_product(boundary_grid(g_3D, id_s), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_n) == inner_product(boundary_grid(g_3D, id_n), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_b) == inner_product(boundary_grid(g_3D, id_b), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_t) == inner_product(boundary_grid(g_3D, id_t), quadrature_interior, quadrature_closure)
 
         ids = boundary_identifiers(g_3D)
-        H_gammas = boundary_quadrature(L,ids)
-        @test H_gammas ==  (boundary_quadrature(L,id_l),
-                            boundary_quadrature(L,id_r),
-                            boundary_quadrature(L,id_s),
-                            boundary_quadrature(L,id_n),
-                            boundary_quadrature(L,id_b),
-                            boundary_quadrature(L,id_t));
-        @test H_gammas == boundary_quadrature(L,ids...)
+        H_gammas = boundary_quadrature(L, ids)
+        @test H_gammas ==  (boundary_quadrature(L, id_l),
+                            boundary_quadrature(L, id_r),
+                            boundary_quadrature(L, id_s),
+                            boundary_quadrature(L, id_n),
+                            boundary_quadrature(L, id_b),
+                            boundary_quadrature(L, id_t));
+        @test H_gammas == boundary_quadrature(L, ids...)
     end
 
     # Exact differentiation is measured point-wise. In other cases
@@ -187,7 +194,10 @@
         # 2nd order interior stencil, 1st order boundary stencil,
         # implies that L*v should be exact for binomials up to order 2.
         @testset "2nd order" begin
-            L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=2)
+            stencil_set = read_stencil_set(operator_path; order=2)
+            inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+            closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+            L = laplace(g_3D, inner_stencil, closure_stencils)
             @test L*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9
             @test L*polynomials[2] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9
             @test L*polynomials[3] ≈ polynomials[1] atol = 5e-9
@@ -197,7 +207,10 @@
         # 4th order interior stencil, 2nd order boundary stencil,
         # implies that L*v should be exact for binomials up to order 3.
         @testset "4th order" begin
-            L = Laplace(g_3D, sbp_operators_path()*"standard_diagonal.toml"; order=4)
+            stencil_set = read_stencil_set(operator_path; order=4)
+            inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+            closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+            L = laplace(g_3D, inner_stencil, closure_stencils)
             # NOTE: high tolerances for checking the "exact" differentiation
             # due to accumulation of round-off errors/cancellation errors?
             @test L*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9