Mercurial > repos > public > sbplib_julia
changeset 2057:8a2a0d678d6f feature/lazy_tensors/pretty_printing
Merge default
| author | Jonatan Werpers <jonatan@werpers.com> |
|---|---|
| date | Tue, 10 Feb 2026 22:41:19 +0100 |
| parents | c0bff9f6e0fb (current diff) 2f23d1ef6e1c (diff) |
| children | 797553341212 |
| files | Notes.md src/LazyTensors/lazy_tensor_operations.jl src/LazyTensors/tensor_types.jl test/LazyTensors/lazy_tensor_operations_test.jl test/LazyTensors/tensor_types_test.jl |
| diffstat | 118 files changed, 9411 insertions(+), 2564 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Mon May 23 07:20:27 2022 +0200 +++ b/.hgignore Tue Feb 10 22:41:19 2026 +0100 @@ -1,3 +1,5 @@ syntax: glob docs/build/ docs/build-local/ +benchmark/results +benchmark/tune.json
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgtags Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,3 @@ +89d7f7b028240dd05f69bce3801b08fc90762792 v0.1.3 +b00eb94848baf86f3877c3ecef4d77503e8dfe4f v0.1.4 +b09b0da220728402f2d49adc467ee96982550bc1 v0.1.5
--- a/Makefile Mon May 23 07:20:27 2022 +0200 +++ b/Makefile Tue Feb 10 22:41:19 2026 +0100 @@ -10,16 +10,24 @@ 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 ' 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 ' benchmark - Run benchmark suite.' + @echo ' benchmarkrev - Run benchmark suite for revision REV.' + @echo ' benchmarkcmp - Run benchmark suite comparing TARGET to BASELINE.' @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 ' REV - Valid Mercurial revision specifier used in benchmarkrev' + @echo ' TARGET - Valid Mercurial revision specifier used in benchmarkcmp' + @echo ' as the target revision' + @echo ' BASELINE - Valid Mercurial revision specifier used in benchmarkcmp' + @echo ' as the baseline revision' @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' @@ -32,11 +40,26 @@ opendocs: localdocs $(BROWSER) docs/build-local/index.html -clean: +cleandocs: rm -rf docs/build - rm -rf docs/build-local + rm -rf docs/build-local + +benchmark: + $(JULIA) --project=benchmark benchmark/make.jl + +benchmarkrev: + $(JULIA) --project=benchmark benchmark/make.jl --rev $(REV) -.PHONY: help docs localdocs opendocs clean +benchmarkcmp: + $(JULIA) --project=benchmark benchmark/make.jl --cmp $(TARGET) $(BASELINE) + +cleanbenchmark: + rm -rf benchmark/results + rm -f benchmark/tune.json + +clean: cleandocs cleanbenchmark + +.PHONY: help clean docs localdocs opendocs cleandocs benchmark benchmarkrev benchmarkcmp cleanbenchmark SRC_DIRS = src docs/src SRC_FILES_AND_DIRS = $(foreach dir,$(SRC_DIRS),$(shell find $(dir))) @@ -47,8 +70,3 @@ 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 Mon May 23 07:20:27 2022 +0200 +++ b/Manifest.toml Tue Feb 10 22:41:19 2026 +0100 @@ -1,59 +1,722 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.1" +julia_version = "1.12.4" manifest_format = "2.0" +project_hash = "b34dd02a79a0ac02863ec0bc982b7a29ded57ff3" + +[[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.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.Aqua]] +deps = ["Compat", "Pkg", "Test"] +git-tree-sha1 = "d57fd255a8932b6509baf43284c416fc44d0b903" +uuid = "4c88cf16-eb10-579e-8560-4a9242c79595" +version = "0.8.14" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BenchmarkTools]] +deps = ["Compat", "JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] +git-tree-sha1 = "7fecfb1123b8d0232218e2da0c213004ff15358d" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.6.3" + +[[deps.CodeTracking]] +deps = ["InteractiveUtils", "UUIDs"] +git-tree-sha1 = "b7231a755812695b8046e8471ddc34c8268cbad5" +uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" +version = "3.0.0" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.8" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.18.1" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.Compiler]] +git-tree-sha1 = "382d79bfe72a406294faca39ef0c3cef6e6ce1f1" +uuid = "807dbc54-b67e-4c79-8afb-eafe4df6f2e1" +version = "0.1.1" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.3.0+1" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DeepDiffs]] +git-tree-sha1 = "9824894295b62a6a4ab6adf1c7bf337b3a9ca34c" +uuid = "ab62b9b5-e342-54a8-a765-a90f495de1a6" +version = "1.2.0" + +[[deps.DiffRules]] +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" +uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" +version = "1.15.1" + +[[deps.Diffinitive]] +deps = ["LinearAlgebra", "StaticArrays", "TOML"] +path = "." +uuid = "5a373a26-915f-4769-bcab-bf03835de17b" +version = "0.1.5" + + [deps.Diffinitive.extensions] + DiffinitiveMakieExt = "Makie" + DiffinitivePlotsExt = "Plots" + DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] + DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] + + [deps.Diffinitive.weakdeps] + Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" + SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + Tokens = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.5" + +[[deps.Documenter]] +deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] +git-tree-sha1 = "b37458ae37d8bdb643d763451585cd8d0e5b4a9e" +uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +version = "1.16.1" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.7.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "27af30de8b5445644e8ffe3bcb0d72049c089cf1" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.7.3+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.Git]] +deps = ["Git_LFS_jll", "Git_jll", "JLLWrappers", "OpenSSH_jll"] +git-tree-sha1 = "824a1890086880696fc908fe12a17bcf61738bd8" +uuid = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" +version = "1.5.0" + +[[deps.Git_LFS_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "bb8471f313ed941f299aa53d32a94ab3bee08844" +uuid = "020c3dae-16b3-5ae5-87b3-4cb189e250b2" +version = "3.7.0+0" + +[[deps.Git_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "46bd50c245fe49ed87377c014a928a549b9ef7ed" +uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" +version = "2.52.0+0" + +[[deps.Glob]] +git-tree-sha1 = "83cb0092e2792b9e3a865b6655e88f5b862607e2" +uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" +version = "1.4.0" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "0ee181ec08df7d7c911901ea38baf16f755114dc" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "1.0.0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.6" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JET]] +deps = ["CodeTracking", "Compiler", "InteractiveUtils", "JuliaInterpreter", "JuliaSyntax", "LoweredCodeUtils", "MacroTools", "Pkg", "PrecompileTools", "Preferences", "Revise", "Test"] +git-tree-sha1 = "3642228b0d4ab0b263a5946f60ace6c2b5616256" +uuid = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +version = "0.11.3" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.1" + +[[deps.JSON]] +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "b3ad4a0255688dcb895a52fafbaae3023b588a90" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "1.4.0" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + +[[deps.JuliaInterpreter]] +deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] +git-tree-sha1 = "80580012d4ed5a3e8b18c7cd86cebe4b816d17a6" +uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" +version = "0.10.9" + +[[deps.JuliaSyntax]] +git-tree-sha1 = "0d4b3dab95018bcf3925204475693d9f09dc45b8" +uuid = "70703baa-626e-46a2-a12c-08ffd08c73b4" +version = "1.0.2" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.LRUCache]] +git-tree-sha1 = "5519b95a490ff5fe629c4a7aa3b3dfc9160498b3" +uuid = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" +version = "1.6.2" +weakdeps = ["Serialization"] + + [deps.LRUCache.extensions] + SerializationExt = ["Serialization"] + +[[deps.LazilyInitializedFields]] +git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" +uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" +version = "1.3.0" + +[[deps.LeftChildRightSiblingTrees]] +deps = ["AbstractTrees"] +git-tree-sha1 = "95ba48564903b43b2462318aa243ee79d81135ff" +uuid = "1d6d02ad-be62-4b6b-8a6d-2f90e265016e" +version = "0.2.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.15.0+0" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" [[deps.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.12.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.29" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoweredCodeUtils]] +deps = ["CodeTracking", "Compiler", "JuliaInterpreter"] +git-tree-sha1 = "65ae3db6ab0e5b1b5f217043c558d9d1d33cc88d" +uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" +version = "3.5.0" -[[deps.OffsetArrays]] -deps = ["Adapt"] -git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed" -uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.10.8" +[[deps.MacroTools]] +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.16" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MarkdownAST]] +deps = ["AbstractTrees", "Markdown"] +git-tree-sha1 = "465a70f0fc7d443a00dcdc3267a497397b8a3899" +uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +version = "0.1.2" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2025.11.4" + +[[deps.Mustache]] +deps = ["Printf", "Tables"] +git-tree-sha1 = "3cbd5dda543bc59f2e482607ccf84b633724fc32" +uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" +version = "1.0.21" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "9b8215b1ee9e78a293f99797cd31375471b2bcae" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.1.3" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.29+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.7+0" + +[[deps.OpenSSH_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll"] +git-tree-sha1 = "301412a644646fdc0ad67d0a87487466b491e53d" +uuid = "9bd350c2-7e96-507f-8002-3f2e150b4e1b" +version = "10.2.1+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.4+0" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.6+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.8.1" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.44.0+1" + +[[deps.PackageExtensionCompat]] +git-tree-sha1 = "fb28e33b8a95c4cee25ce296c817d89cc2e53518" +uuid = "65ce6f38-6b18-4e1d-a461-8949797d7930" +version = "1.0.2" +weakdeps = ["Requires", "TOML"] + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.3" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.1" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PkgBenchmark]] +deps = ["BenchmarkTools", "Dates", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Pkg", "Printf", "TerminalLoggers", "UUIDs"] +git-tree-sha1 = "10940959d1174f5402729bb0891cafb785c0e927" +uuid = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d" +version = "0.2.15" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.3" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "522f093a29b31a93e34eaea17ba055d850edea28" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.1" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.Profile]] +deps = ["StyledStrings"] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.ProgressLogging]] +deps = ["Logging", "SHA", "UUIDs"] +git-tree-sha1 = "f0803bc1171e455a04124affa9c21bba5ac4db32" +uuid = "33c8b6b6-d38a-422a-b730-caa89a2f386c" +version = "0.1.6" + +[[deps.PtrArrays]] +git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.3.0" + +[[deps.REPL]] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RegistryInstances]] +deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] +git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" +uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" +version = "0.1.0" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.1" + +[[deps.Revise]] +deps = ["CodeTracking", "FileWatching", "InteractiveUtils", "JuliaInterpreter", "LibGit2", "LoweredCodeUtils", "OrderedCollections", "Preferences", "REPL", "UUIDs"] +git-tree-sha1 = "14d1bfb0a30317edc77e11094607ace3c800f193" +uuid = "295af30f-e4ad-537b-8983-00126c2a3abe" +version = "3.13.2" +weakdeps = ["Distributed"] + + [deps.Revise.extensions] + DistributedExt = "Distributed" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.SparseArrayKit]] +deps = ["LinearAlgebra", "PackageExtensionCompat", "TensorOperations", "TupleTools", "VectorInterface"] +git-tree-sha1 = "1f7291542f44195c86bc6db293c5affb97c020e1" +uuid = "a9a3c162-d163-4c15-8926-b8794fbefed2" +version = "0.4.2" +weakdeps = ["SparseArrays"] + + [deps.SparseArrayKit.extensions] + SparseArrayKitSparseArrays = "SparseArrays" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.12.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "f2685b435df2613e25fc10ad8c26dddb8640f547" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.6.1" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "eee1b9ad8b29ef0d936e3ec9838c7ec089620308" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.16" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "6ab403037779dae8c514bad259f32a447262455a" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.4" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.Strided]] +deps = ["LinearAlgebra", "StridedViews", "TupleTools"] +git-tree-sha1 = "c2e72c33ac8871d104901db736aecb36b223f10c" +uuid = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" +version = "2.3.2" + +[[deps.StridedViews]] +deps = ["LinearAlgebra", "PackageExtensionCompat"] +git-tree-sha1 = "e34a59ea9c7abc8f10bfd77578de9d64bded2859" +uuid = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" +version = "0.4.3" + + [deps.StridedViews.extensions] + StridedViewsCUDAExt = "CUDA" + StridedViewsPtrArraysExt = "PtrArrays" + + [deps.StridedViews.weakdeps] + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + PtrArrays = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" + +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "9297459be9e338e546f5c4bedb59b3b5674da7f1" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.6.2" + + [deps.StructUtils.extensions] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.8.3+2" [[deps.TOML]] deps = ["Dates"] uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" -[[deps.TiledIteration]] -deps = ["OffsetArrays"] -git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297" -uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" -version = "0.3.1" +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.12.1" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TensorOperations]] +deps = ["LRUCache", "LinearAlgebra", "PackageExtensionCompat", "PrecompileTools", "Preferences", "PtrArrays", "Strided", "StridedViews", "TupleTools", "VectorInterface"] +git-tree-sha1 = "53a6e991a7b8090a8bba2aeaa63fd7e07becb934" +uuid = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" +version = "5.5.0" + + [deps.TensorOperations.extensions] + TensorOperationsBumperExt = "Bumper" + TensorOperationsChainRulesCoreExt = "ChainRulesCore" + TensorOperationsEnzymeExt = ["Enzyme", "ChainRulesCore"] + TensorOperationsMooncakeExt = "Mooncake" + TensorOperationscuTENSORExt = ["cuTENSOR", "CUDA"] + + [deps.TensorOperations.weakdeps] + Bumper = "8ce10254-0962-460f-a3d8-1f77fea1446e" + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" + Mooncake = "da2b9cff-9c12-43a0-ae48-6db2b0edb7d6" + cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" + +[[deps.TerminalLoggers]] +deps = ["LeftChildRightSiblingTrees", "Logging", "Markdown", "Printf", "ProgressLogging", "UUIDs"] +git-tree-sha1 = "f133fab380933d042f6796eda4e130272ba520ca" +uuid = "5d786b92-1e48-4d6f-9151-6b4477ca9bed" +version = "0.1.7" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TestSetExtensions]] +deps = ["DeepDiffs", "Distributed", "Test"] +git-tree-sha1 = "ccebd99935be339d2ad907589708ba1f0d62bab3" +uuid = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" +version = "3.0.0" + +[[deps.Tokens]] +deps = ["SparseArrayKit", "SparseArrays"] +git-tree-sha1 = "c4f40125383ce3bfcfcd49a1b206080b7afd9a34" +uuid = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" +version = "0.1.1" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.Tullio]] +deps = ["DiffRules", "LinearAlgebra", "Requires"] +git-tree-sha1 = "972698b132b9df8791ae74aa547268e977b55f68" +uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" +version = "0.3.8" + + [deps.Tullio.extensions] + TullioCUDAExt = "CUDA" + TullioChainRulesCoreExt = "ChainRulesCore" + TullioFillArraysExt = "FillArrays" + TullioTrackerExt = "Tracker" + + [deps.Tullio.weakdeps] + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" + Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" + +[[deps.TupleTools]] +git-tree-sha1 = "41e43b9dc950775eac654b9f845c839cd2f1821e" +uuid = "9d95972d-f1c8-5527-a6e0-b4b365fa01f6" +version = "1.6.0" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.VectorInterface]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "9166406dedd38c111a6574e9814be83d267f8aec" +uuid = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +version = "0.5.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" [[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.15.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.64.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.7.0+0"
--- a/Notes.md Mon May 23 07:20:27 2022 +0200 +++ b/Notes.md Tue Feb 10 22:41:19 2026 +0100 @@ -1,5 +1,41 @@ # Notes +## How to dispatch for different operators +We have a problem in how dispatch for different operators work. + * We want to keep the types simple and flat (Awkward to forward `apply`) + * We want to dispatch SATs on the parameters of the continuous operator. (a * div for example) + * We want to allow keeping the same stencil_set across different calls. (maybe not so bad for the user to be responsible) + +Could remove the current opset idea and introduce a description of continuous operators + ```julia +abstract type DifferentialOperator end + +struct Laplace <: DifferentialOperator end +struct Advection <: DifferentialOperator + v +end + +difference_operator(::Laplace, grid, stencil_set) = ... # Returns a plain LazyTensor. Replaces the current `laplace()` function. +sat_tensors(::Laplace, grid, stencil_set, bc) = ... + +sat(::DifferentialOperator, grid, stencil_set, bc) = ... + ``` + + +### Update 2024-06-26 +We will run into trouble if we start assuming things about the coupling +between the continuous and discrete setting. We could add representations of +continuous operators but we will also need representations of discrete +operators. Ideally it should be possible to ignore the continuous +representations and only work with the discrete operators without losing +functionality. The discrete representations does not have to be LazyTensors. +The could be used as inputs to methods for `sat`, `difference_operator` and so +on. + +To see need for a fully functional discrete layer we can consider the +optimization of material parameters or something similar. In this case we do +not necessarily want to handle continuous objects. + ## Reading operators Jonatan's suggestion is to add methods to `Laplace`, `SecondDerivative` and @@ -79,59 +115,6 @@ more verbose version. Compare to what the standard library does for regular vectors. -## Variable second derivative - -2020-12-08 after discussion with Vidar: -We will have to handle the variable second derivative in a new variant of -VolumeOperator, "SecondDerivativeVariable?". Somehow it needs to know about -the coefficients. They should be provided as an AbstractVector. Where they are -provided is another question. It could be that you provide a reference to the -array to the constructor of SecondDerivativeVariable. If that array is mutable -you are free to change it whenever and the changes should propagate -accordingly. Another option is that the counter part to "Laplace" for this -variable second derivate returns a function or acts like a functions that -takes an Abstract array and returns a SecondDerivativeVariable with the -appropriate array. This would allow syntax like `D2(a)*v`. Can this be made -performant? - -For the 1d case we can have a constructor -`SecondDerivativeVariable(D2::SecondDerivativeVariable, a)` that just creates -a copy with a different `a`. - -Apart from just the second derivative in 1D we need operators for higher -dimensions. What happens if a=a(x,y)? Maybe this can be solved orthogonally to -the `D2(a)*v` issue, meaning that if a constant nD version of -SecondDerivativeVariable is available then maybe it can be wrapped to support -function like syntax. We might have to implement `SecondDerivativeVariable` -for N dimensions which takes a N dimensional a. If this could be easily -closured to allow D(a) syntax we would have come a long way. - -For `Laplace` which might use a variable D2 if it is on a curvilinear grid we -might want to choose how to calculate the metric coefficients. They could be -known on closed form, they could be calculated from the grid coordinates or -they could be provided as a vector. Which way you want to do it might change -depending on for example if you are memory bound or compute bound. This choice -cannot be done on the grid since the grid shouldn't care about the computer -architecture. The most sensible option seems to be to have an argument to the -`Laplace` function which controls how the coefficients are gotten from the -grid. The argument could for example be a function which is to be applied to -the grid. - -What happens if the grid or the varible coefficient is dependent on time? -Maybe it becomes important to support `D(a)` or even `D(t,a)` syntax in a more -general way. - -``` -g = TimeDependentGrid() -L = Laplace(g) -function Laplace(g::TimeDependentGrid) - g_logical = logical(g) # g_logical is time independent - ... Build a L(a) assuming we can do that ... - a(t) = metric_coeffs(g,t) - return t->L(a(t)) -end -``` - ## Known size of range and domain? Is there any reason to use a trait to differentiate between fixed size and unknown size? @@ -143,19 +126,10 @@ Maybe if we should have dynamic sizing it could be only for the range. `domain_size` would not be implemented. And the `range_size` would be a function of a vector that the LazyTensor is applied to. ## Reasearch and thinking - - [ ] Use a trait to indicate that a LazyTensor har the same range and domain? - [ ] Check how the native julia doc generator works - [ ] Check if Vidars design docs fit in there - [ ] Create a macro @lazy which replaces a binary op (+,-) by its lazy equivalent? Would be a neat way to indicate which evaluations are lazy without cluttering/confusing with special characters. - - [ ] Dispatch on Lower() instead of the type Lower so `::Lower` instead of `::Type{Lower}` ??? - Seems better unless there is some specific reason to use the type instead of the value. - - [ ] How do we handle mixes of periodic and non-periodic grids? Seems it should be supported on the grid level and on the 1d operator level. Between there it should be transparent. - [ ] Can we have a trait to tell if a LazyTensor is transposable? - - [ ] Is it ok to have "Constructors" for abstract types which create subtypes? For example a Grids() functions that gives different kind of grids based on input? - - [ ] Figure out how to treat the borrowing parameters of operators. Include in into the struct? Expose via function dispatched on the operator type and grid? - -## Identifiers for regions -The identifiers (`Upper`, `Lower`, `Interior`) used for region indecies should probabily be included in the grid module. This allows new grid types to come with their own regions. ## Regions and tensormappings - [ ] Use a trait to indicate if a LazyTensor uses indices with regions. @@ -206,80 +180,108 @@ Preferably dimensions and sizes should be checked when lazy objects are created, for example TensorApplication, TensorComposition and so on. If dimension checks decreases performance we can make them skippable later. -## Vector valued grid functions -Från slack konversation: +## Changes to `eval_on` +There are reasons to replace `eval_on` with regular `map` from Base, and +implement a kind of lazy map perhaps `lmap` that work on indexable +collections. + +The benefit of doing this is that we can treat grids as gridfunctions for the +coordinate function, and get a more flexible tool. For example `map`/`lmap` +can then be used both to evaluate a function on the grid but also get a +component of a vector valued grid function or similar. -Jonatan Werpers: -Med vektorvärda gridfunktioner vill vi ju fortfarande att grid funktionen ska vara till exempel AbstractArray{LitenVektor,2} -Och att man ska kunna göra allt man vill med LitenVektor -typ addera, jämföra osv -Och då borde points returnera AbstractArray{LitenVektor{Float,2},2} för ett 2d nät -Men det kanske bara ska vara Static arrays? +Below is a partial implementation of `lmap` with some ideas +```julia +struct LazyMapping{T,IT,F} + f::F + indexable_iterator::IT # ___ +end -Vidar Stiernström: -Ja, jag vet inte riktigt vad som är en rimlig representation -Du menar en vektor av static arrays då? +function LazyMapping(f,I) + IT = eltype(I) + T = f(zero(T)) + F = typeof(f) -Jonatan Werpers: -Ja, att LitenVektor är en StaticArray + return LazyMapping{T,IT,F}(f,I) +end -Vidar Stiernström: -Tuplar känns typ rätt inuitivt för att representera värdet i en punkt -men -det suger att man inte har + och - för dem +getindex(lm::LazyMapping, I...) = lm.f(lm.I[I...]) +# indexabl interface +# iterable has shape + +iterate(lm::LazyMapping) = _lazy_mapping_iterate(lm, iterate(lm.I)) +iterate(lm::LazyMapping, state) = _lazy_mapping_iterate(lm, iterate(lm.I, state)) -Jonatan Werpers: -Ja precis +_lazy_mapping_iterate(lm, ::Nothing) = nothing +_lazy_mapping_iterate(lm, (next, state)) = lm.f(next), state + +lmap(f, I) = LazyIndexableMap(f,I) +``` -Vidar Stiernström: -så kanske är bra med static arrays i detta fall +The interaction of the map methods with the probable design of multiblock +functions involving nested indecies complicate the picture slightly. It's +unclear at the time of writing how this would work with `Base.map`. Perhaps we +want to implement our own versions of both eager and lazy map. -Jonatan Werpers: -Man vill ju kunna köra en Operator rakt på och vara klar eller? -Vidar Stiernström: -Har inte alls tänkt på hur det vi gör funkar mot vektorvärda funktioner -men känns som staticarrays är hur man vill göra det -tuplar är ju immutable också -blir jobbigt om man bara agerar på en komponent då +### 2024-04 +MappedArrays.jl provides a simple array type and function like the description +of LazyMapping above. One option is to remove `eval_on` completely and rely on +destructuring arguments if handling the function input as a vector is +undesirable. + +If we can let multi-block grids be iterators over grid points we could even +handle those by specialized implementation of `map` and `mappedarray`. + +## Multiblock implementation +We want multiblock things to work very similarly to regular one block things. -Jonatan Werpers: -Hm… -Tål att tänkas på -Men det lär ju bli mer indirektion med mutables eller? -Hur fungerar det? -Det finns ju hur som helst både SVector och MVector i StaticArrays +### Grid functions +Should probably support a nested indexing so that we first have an index for +subgrid and then an index for nodes on that grid. E.g `g[1,2][2,3]` or +`g[3][43,21]`. -Vidar Stiernström: -När vi jobbat i c/c++ och kollat runt lite hur man brukar göra så lagrar man i princip alla sina obekanta i en lång vektor och så får man specificera i funktioerna vilken komponent man agerar på och till vilken man skriver -så man lagrar grejer enl: w = [u1, v1, u2, v2, …] i 1D. -Men alltså har ingen aning hur julia hanterar detta +We could also possibly provide a combined indexing style `g[1,2,3,4]` where +the first group of indices are for the subgrid and the remaining are for the +nodes. + +We should make sure the underlying buffer for grid functions are continuously +stored and are easy to convert to, so that interaction with for example +DifferentialEquations is simple and without much boilerplate. -Jonatan Werpers: -Det vi är ute efter kanske är att en grid funcktion är en AbstractArray{T,2} where T<:NågotSomViKanRäknaMed -Och så får den typen var lite vad som helst. +#### `map` and `collect` and nested indexing +We need to make sure `collect`, `map` and a potential lazy map work correctly +through the nested indexing. Also see notes on `eval_on` above. -Vidar Stiernström: -Tror det kan vara farligt att ha nåt som är AbstractArray{LitenArray{NDof},Dim} -Jag gissar att det kompilatorn vill ha är en stor array med doubles +Possibly this can be achieved by providing special nested indexing but not +adhering to an array interface at the top level, instead being implemented as +an iterator over the grid points. A custom trait can let map and other methods +know the shape (or structure) of the nesting so that they can efficiently +allocate result arrays. + +### Tensor applications +Should behave as grid functions -Jonatan Werpers: -Och sen är det upp till den som använder grejerna att vara smart -Vill man vara trixig kan man väl då imlementera SuperHaxxorGridFunction <: AbstractArray{Array{…},2} som lagrar allt linjärt eller något sånt -Det kommer väl lösa sig när man börjar implementera vektorvärda saker -Euler nästa! -New -Vidar Stiernström: -Det vore skönt att inte behöva skriva såhär varje gång man testar mot en tupel :smile: @test [gp[i]...] ≈ [p[i]...] atol=5e-13 +### LazyTensors +Could be built as a tuple or array of LazyTensors for each grid with a simple apply function. + +Nested indexing for these is problably not needed unless it simplifies their own implementation. + +Possibly useful to provide a simple type that doesn't know about connections between the grids. Antother type can include knowledge of the. -Jonatan Werpers: -https://github.com/JuliaArrays/ArraysOfArrays.jl -https://github.com/jw3126/Setfield.jl +We have at least two option for how to implement them: +* Matrix of LazyTensors +* Looking at the grid and determining what the apply should do. + +### Overall design implications of nested indices +If some grids accept nested indexing there might be a clash with how LazyArrays work. It would be nice if the grid functions and lazy arrays that actually are arrays can be AbstractArray and things can be relaxed for nested index types. + +## Vector valued grid functions ### Test-applikationer -div och grad operationer +div- och grad-operationer -Enligt Wikipedia verkar det som att `∇⋅` agerar på första dimensionen av ett tensor fält och `div()` på sista. +Enligt Wikipedia verkar det som att `∇⋅` agerar på första dimensionen av ett tensorfält och `div()` på sista. Om man generaliserar kanske `∇` i så fall bara lägger till en dimension i början. Kan vi implementera `⋅`(\cdot) så att de fungerar som man vill för både tensor-fält och tensor-operatorer? @@ -287,20 +289,8 @@ Är `∇` ett tensor-fält av tensor-operatorer? Vad är ett tensor-fält i vår kod? Är det en special-fall av en tensor-mapping? ### Grid-funktionen -Grid-funktionon har typen `AbstractArray{T,2} where T`. -`T` kan vara lite vad som helst, tillexemel en SVector eller Array, eller tuple. TensorOperatorerna bryr sig inte om exakt vad det är, mer än att typen måste stödja de operationer som operatorn använder. - -En nackdel kan vara hur man ska få ut gridfunktionen för tex andra komponenten. - -Syntax: -``` -f(x̄) = x̄ -gf = evalOn(g, f) -gf[2,3] # x̄ för en viss gridpunkt -gf[2,3][2] # x̄[2] för en viss gridpunkt -``` - -Note: Behöver bestämma om eval on skickar in `x̄` eller `x̄...` till `f`. Eller om man kan stödja båda. +Grid-funktioner har typen `AbstractArray{T,2} where T`. +`T` kan vara lite vad som helst, tillexemel en SVector eller Array, eller Tuple. Tensoroperatorerna bryr sig inte om exakt vad det är, mer än att typen måste stödja de operationer som operatorn använder. ### Tensor operatorer Vi kan ha tensor-operatorer som agerar på ett skalärt fält och ger ett vektorfält eller tensorfält. @@ -317,63 +307,25 @@ Kanske kan man implementera `⋅(tm::LazyTensor{R,D}, v::AbstractArray{T,D})` där T är en AbstractArray, tm på något sätt har komponenter, lika många som T har element. -### Ratade alternativ: +### Prestanda-aspekter +[Vidar, Discord, 2023-03-03] +Typiskt sett finns det två sätt att representera vektorvärda gridfunktioner AbstractArray{T,Dim} där T är en vektor över komponenterna. Man skulle alltså i 1D ha +u = [ [u1[x1], u2[x1]] , [u1[x2], u2[x2]], ... [u1[xN], u2[xN]]]. Detta brukar kallas array of structs (AoS). Alternativet är struct of arrays (SoA), där man har alla gridpunkter för en given komponent u = [[u1[x1], u1[x2]],... u1[xN]], [u2[x1], u2[x2], ... u2[xN]]]. + +Personligen tycker jag att AoS känns som den mer naturliga representationen? Det skulle göra det enklarare att parallelisera en vektorvärd gridfunktion över gridpunkterna, och om man opererar på olika komponenter i samma funktion så är det också bra ur en minnesaccess-synpunkt då dessa kommer ligga nära vandra i minnet. Problemet är att AoS sabbar vektorisering på CPU då två gridpunkter i en komponent ligger långt bort från varandra. Efter lite eftersökningar (och efter att snackat lite med Ossian) så verkar det ändå som att AoS är dåligt på GPU, där man vill att trådar typiskt sett utföra samma operation på närliggande minne. + +Vad tänker du kring detta ur ett interface-perspektiv? Jag hittade paketet https://github.com/JuliaArrays/StructArrays.jl som verkar erbjuda AoS-interface men SoA-minneslayout så det kanske kan vara något vi kan använda? Inte native-stödd på samma sätt som SVector, men verkar iaf utvecklas aktivt. + +[Efter telefonsamtal] För optimal prestanda behöver vi antagligen se till att man kan räkna ut varje komponent i en punkt individuellt. Detta så att man har frihet att till exempel låta den innersta loopen hålla komponentindexet konstant för att underlätta intruktionsvektorisering. -#### 2.AbstractArray{T,2+1} where T (NOPE!) -Blir inte den här. Bryter mot alla tankar om hur grid funktioner ska fungera. Om de tillåts ha en annan dimension än nätet blir allt hemskt. - -Man låter helt enkelt arrayen ha en extra dimension. En fördel är att man har en väldigt "native" typ. En nackdel kan vara att det eventuellt blir rörigt vilken dimension olika operatorer ska agera på. I värsta fall behöver vi "kroneckra in" de tillagda dimensionerna. Vektorfältets index kommer också att bli det första eftersom vi vill att de ska lagras kontinuerligt i minnet pga chachen. (Går kanske att lösa med en custom typ men då krånglar man till det för sig). En fördel skulle vara att man enkelt får ut olika komponenter. - -Syntax: -``` -gf = eval_on_grid(g,f) -gf[:,2,3] # Hela vektorn för en gridpunkt -gf[2,2,3] # Andra komponenten av vektor fältet i en punkt. -gf[2,:,:] # -``` - -### Evaluering av funktioner på nät -Hur ska man skriva funktioner som evalueras på nätet? `f(x,y) = ...` eller `f(x̄) = ...`? Eller båda? Kan eval_on_grid se skillnad eller får användaren specificera? - -``` -f(x,y) = [x^2, y^2] -f(x̄) = [x̄[1]^2, x̄[2]^2] -``` - -Påverkas detta av hur vi förväntar oss kunna skapa lata gridfunktioner? +[Vidare tankar] + * Det borde bara vara output-gridfunktionen som behöver special-indexeras? Det viktiga på inputsidan är att den är lagrad på rätt sätt i minnet. + * Det borde inte vara några problem att behålla det "optimala" interfacet (gf[1,1,1][2]) till gridfunktionerna. Om man verkligen behöver kan skapa parallella indexeringsmetoder som gör det man behöver, i.e, "deep indexing". + * Det är inte säkert att vi behöver göra något speciellt på outputsidan överhuvudtaget. Det känns inte orimligt att kompilatorn skulle kunna optimera bort den koden som räknar ut onödiga komponenter. + * Om vi behöver special-indexering kommer till exempel LazyTensorApplication att behöva implementera det. + * För att komma vidare med något mer avancerat behöver vi antagligen implementera några operatorer som ger och agerar på vektorvärda funktioner. Tex grad, elastiska operatorn, andra? -### Komponenter som gridfunktioner -En viktig operation för vektor fält är att kunna få ut komponenter som grid-funktioner. Detta behöver antagligen kunna ske lazy. -Det finns ett par olika lösningar: -* Implementera en egen typ av view som tar hand om detta. Eller Accessors.jl? -* Använda en LazyTensor -* Någon typ av lazy-broadcast -* En lazy array som applicerar en funktion för varje element. - -Skulle vara en fördel om det är hyffsat generiskt så att en eventuell användare kan utöka det enkelt om de har någon egen exotisk typ. Eller ska man vila helt på - -Syntax: -``` -gf = eval(...) -component(gf,2) # Andra komponenten av en vektor -component(gf,2,3) # (2,3) elementet av en matris -component(gf,:,2) # Andra kolumnen av en matris -@ourview gf[:,:][2] -``` - -## Grids embedded in higher dimensions - -For grids generated by asking for boundary grids for a regular grid, it would -make sense if these grids knew they were embedded in a higher dimension. They -would return coordinates in the full room. This would make sense when -drawing points for example, or when evaluating functions on the boundary. - -Implementation of this is an issue that requires some thought. Adding an extra -"Embedded" type for each grid would make it easy to understand each type but -contribute to "type bloat". On the other hand adapting existing types to -handle embeddedness would complicate the now very simple grid types. Are there -other ways of doing the implentation? ## Performance measuring We should be measuring performance early. How does our effective cpu and memory bandwidth utilization compare to peak performance? @@ -395,3 +347,64 @@ ## 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 `LazyTensor` specialized for the grid. A suggetion for a better name is `ConstantStencilVolumeOperator` + + +## Implementation of LazyOuterProduct +Could the implementation of LazyOuterProduct be simplified by making it a +struct containing two or more LazyTensors? (using split_tuple in a similar way +as TensorGrid) + +## Implementation of boundary_indices for more complex grids +To represent boundaries of for example tet-elements we can use a type `IndexCollection` to index a grid function directly. + +```julia +I = IndexCollection(...) +v[I] +``` + +* This would impact how tensor grid works. +* To make things homogenous maybe these index collections should be used for the more simple grids too. +* The function `to_indices` from Base could be useful to implement for `IndexCollection` + + +## Stencil application pipeline +We should make sure that `@inbounds` and `Base.@propagate_inbounds` are +applied correctly throughout the stack. When testing the performance of +stencil application on the bugfix/sbp_operators/stencil_return_type branch +there seemed to be some strange results where such errors could be the +culprit. + + +## Tiled loops and regions in apply +There should be easy ways to use functionalty splitting the application of a lazy array into regions and using tiled iteration. This could make the application more efficient by reducing branching and improving cache usage in the tight loop. On commit f215ac2a5c66 and before there were some early tests regarding this in a DiffOp submodule. + +The main ideas were: +```julia +function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T + apply_region!(D, u, v, Lower, Lower) + apply_region!(D, u, v, Lower, Interior) + apply_region!(D, u, v, Lower, Upper) + apply_region!(D, u, v, Interior, Lower) + apply_region!(D, u, v, Interior, Interior) + apply_region!(D, u, v, Interior, Upper) + apply_region!(D, u, v, Upper, Lower) + apply_region!(D, u, v, Upper, Interior) + apply_region!(D, u, v, Upper, Upper) + return nothing +end +``` + +```julia +using TiledIteration +function apply_region_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T + ri = regionindices(D.grid.size, closuresize(D.op), (r1,r2)) + # TODO: Pass Tilesize to function + for tileaxs ∈ TileIterator(axes(ri), padded_tilesize(T, (5,5), 2)) + for j ∈ tileaxs[2], i ∈ tileaxs[1] + I = ri[i,j] + u[I] = apply(D, v, (Index{r1}(I[1]), Index{r2}(I[2]))) + end + end + return nothing +end +```
--- a/Project.toml Mon May 23 07:20:27 2022 +0200 +++ b/Project.toml Tue Feb 10 22:41:19 2026 +0100 @@ -1,11 +1,36 @@ -name = "Sbplib" +name = "Diffinitive" uuid = "5a373a26-915f-4769-bcab-bf03835de17b" -authors = ["Jonatan Werpers <jonatan@werpers.com>", "Vidar Stiernström <vidar.stiernstrom@it.uu.se>, and contributors"] -version = "0.1.0" +authors = ["Jonatan Werpers <jonatan@werpers.com>", "Vidar Stiernström <vidar.stiernstrom@gmail.com>, and contributors"] +version = "0.1.5" + +[workspace] +projects = ["test", "docs", "benchmark"] [deps] +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -TiledIteration = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" + +[weakdeps] +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +Tokens = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" + +[extensions] +DiffinitiveMakieExt = "Makie" +DiffinitivePlotsExt = "Plots" +DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] +DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] [compat] julia = "1.5" +StaticArrays = "1.0" +TOML = "1.0" +Makie = "0" +Plots = "1.40" +SparseArrayKit = "0.4" +Tokens = "0.1.1" +SparseArrays = "1.10" +LinearAlgebra = "1.5"
--- a/README.md Mon May 23 07:20:27 2022 +0200 +++ b/README.md Tue Feb 10 22:41:19 2026 +0100 @@ -1,10 +1,10 @@ -# Sbplib +# Diffinitive ## Running tests To run all tests simply run ``` (@v1.5) pkg> activate . -(Sbplib) pkg> test +(Diffinitive) pkg> test ``` If you want to run tests from a specific file in `test/`, you can do @@ -28,22 +28,64 @@ ``` will run any file named `lazy_tensor_operations_test.jl` and all the files in the `Grids` folder. +## Running benchmarks +Benchmarks are defined in `benchmark/` and use the tools for benchmark suites +in BenchmarkTools.jl. The format is compatible with PkgBenchmark.jl which +helps with running the suite, comparing results and presenting the results in +a readable way. There are custom functions included for running the benchmarks +in this Mercurial repository. + +`benchmark/` contains a julia environment with the necessary packages for +working with the benchmarks. + +To run the benchmarks, either use `make` or run them manually from the REPL, as +explained further below. + +Using `make` there are four targets for benchmarks +```shell +make benchmark # Runs the suite for the current working directory +make benchmarkrev REV=rev # Runs the suite at the specified revision +make benchmarkcmp TARGET=target BASELINE=baseline # Compares two revisions +make cleanbenchmark # Cleans up benchmark tunings and results +``` +Here `rev`, `target` and `baseline` are any valid Mercurial revision +specifiers. + +Alternatively, the benchmarks can be run from the REPL. To do this, first +activate the environment in `benchmark/` then include the file +`benchmark_utils.jl`. The suite can then be run using the function `main` in +one of the following ways + +```julia +main() # Runs the suite for the current working directory +main(rev="...") # Runs the suite at the specified revision +main(target="...", baseline="...") # Compares two revisions +``` + +Again, `rev`, `target` and `baseline` are any valid Mercurial revision +specifiers. + +PkgBenchmark can also be used directly. + +```julia +using PkgBenchmark +import Diffinitive +r = benchmarkpkg(Diffinitive) + +export_markdown(stdout, r) +``` ## 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 +Using `make` there are four targets for documentation ```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 +make docs # generates files suitable for webserver deployment, i.e with `prettyurls=true` +make localdocs # generates files suitable for local viewing in a web browser, i.e `prettyurls=false` +make opendocs # build and view documentation locally +make cleandocs # cleans up generated files ``` -When including the `docs/make.jl` script `prettyurls` is set to `false` by default. +Alternatively, to view the documentation locally simply open `docs/build/index.html` in your web browser. 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. +Including `docs/make.jl` from the REPL may be preferable when repeatedly building the documentation since this avoids compilation latency.
--- a/TODO.md Mon May 23 07:20:27 2022 +0200 +++ b/TODO.md Tue Feb 10 22:41:19 2026 +0100 @@ -1,13 +1,10 @@ # TODO +## Organization + - [ ] Split up Notes.md in several files ## Coding - - [ ] Ändra namn på variabler och funktioner så att det följer style-guide - - [ ] Add new Laplace operator to DiffOps, probably named WaveEqOp(?!!?) - [ ] Create a struct that bundles the necessary Tensor operators for solving the wave equation. - - [ ] Replace getindex hack for flattening tuples with flatten_tuple. (eg. `getindex.(range_size.(L.D2),1)`) - - [ ] Use `@inferred` in a lot of tests. - - [ ] Replace `@inferred` tests with a benchmark suite that automatically tests for regressions. - [ ] Make sure we are setting tolerances in tests in a consistent way - [ ] Write down some coding guideline or checklist for code conventions. For example i,j,... for indices and I for multi-index - [ ] Clean up RegionIndices @@ -15,11 +12,14 @@ 2. [ ] Update RegionIndices accordingly 3. [ ] Fix the rest of the library Should getregion also work for getregion(::Colon,...) - - [ ] Add possibility to create tensor mapping application with `()`, e.g `D1(v) <=> D1*v`? - [ ] Add custom pretty printing to LazyTensors/SbpOperators to enhance readability of e.g error messages. See (https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing) - [ ] Samla noggrannhets- och SBP-ness-tester för alla operatorer på ett ställe - [ ] Move export statements to top of each module + - [ ] Implement apply_transpose for + - [ ] ElementwiseTensorOperation + - [ ] VolumeOperator + - [ ] Laplace - [ ] Gå igenom alla typ parametrar och kolla om de är motiverade. Både i signaturer och typer, tex D i VariableSecondDerivative. Kan vi använda promote istället? @@ -27,3 +27,18 @@ - [ ] Kolla att vi gör boundschecks överallt och att de är markerade med @boundscheck - [ ] Kolla att vi har @inline på rätt ställen - [ ] Profilera + + - [ ] Keep a lookout for allowing dependencies of package extensions (https://github.com/JuliaLang/Pkg.jl/issues/3641) This should be used to simplify the matrix extensions so that you don't have to load Tokens which is only used internally to the extension + +### Grids + + - [ ] Multiblock grids + - [ ] Periodic grids + - [ ] Grids with modified boundary closures + - [ ] Support indexing with `:`. + + +### Benchmarks + - [ ] Benchmarks for all grid indexing (focused on allocation) + - [ ] Benchmarks for indexing of lazy grid functions + - [ ] Add benchmarks for range type in EquidistantGrid. (LinRange vs StepRange)
--- a/TimeStepper.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -abstract type TimeStepper end - -# Returns v and t -function getState(ts::TimeStepper) - error("not implemented") -end - - -function step!(ts::TimeStepper) - error("not implemented") -end - -function stepN(ts::TimeStepper,N::Int) - for i ∈ 1:N - ts.step() - end -end - -function stepTo(ts::TimeStepper) - error("Not yet implemented") -end - -function evolve(ts::TimeStepper) - error("Not yet implemented") -end - - -mutable struct Rk4 <: TimeStepper - F::Function - k::Real - v::Vector - t::Real - n::UInt - - function Rk4(F::Function,k::Real,v0::Vector,t0::Real) - # TODO: Check that F has two inputs and one output - v = v0 - t = t0 - n = 0 - return new(F,k,v,t,n) - end -end - -function getState(ts::Rk4) - return ts.t, ts.v -end - -function step!(ts::Rk4) - k1 = ts.F(ts.v,ts.t) - k2 = ts.F(ts.v+0.5*ts.k*k1,ts.t+0.5*ts.k) - k3 = ts.F(ts.v+0.5*ts.k*k2,ts.t+0.5*ts.k) - k4 = ts.F(ts.v+ ts.k*k3,ts.t+ ts.k) - ts.v = ts.v + (1/6)*(k1+2*(k2+k3)+k4)*ts.k - - ts.n = ts.n + 1 - ts.t = ts.t + ts.k - - return nothing -end -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WORKFLOW.md Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,46 @@ +# Branch model + +The `default` branch contains stable code and the goal is that all tests should be passing on this branch. Version of the code are marked with mercurial tags. Changes in the code are developed in named branches which are closed and merged into `default` when the code is ready. During development merging `default` into the development branch is encouraged to avoid complicated conflicts. + +Branches are named using slash separated keywords. The first keyword describes the type of change being pursued in the branch. Important type keywords are + * feature + * bugfix + * refactor +Further keywords may describe where, e.g. what sub package, the change happens. The last keyword should describe the change. + +Some examples: + * refactor/grids: Branch to refactor the grids module + * bugfix/lazy_tensors/lazyfunctionarray: Branch to fix a bug in LazyFunctionArray + +## Merging a branch into `default` +The changes in a branch has been reviewed and deemed ready to merge the branch is closed and then merged. + +Before merging a development branch, `default` should be merge into the development branch to make sure the whole state of the code is reviewed and tested before it ends up on `default`. + +With the development branch active the following commands can be used to complete the merging of a development branch. +```shell +hg merge default +hg commit --close-branch -m "Close before merge" +hg update default +hg merge development/branch +hg commit -m "Merge development/branch" +``` + +# Review + +## Checklist for review +* Push and pull new changes +* Search and check TODOs +* Search and check TBDs +* Search and check REVIEWs +* Review code +* Review tests +* Review docstrings +* Render Documenter and check docstrings in browser +* Run full tests + +# Special comments +The following special comments are used: +* `# TODO: `: Something that should be done at some point. +* `# TBD: `: "To be determined", i.e a decision that has to be made. +* `# REVIEW: `: A review comment. Should only exist on development branches.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/Project.toml Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,5 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Diffinitive = "5a373a26-915f-4769-bcab-bf03835de17b" +Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" +PkgBenchmark = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d" \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/benchmark_laplace.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,220 @@ +using Diffinitive +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.RegionIndices +using BenchmarkTools + +# TODO: Move the below benchmarks into the benchmark suite + +const operator_path = sbp_operators_path()*"standard_diagonal.toml" + +function benchmark_const_coeff_1d(;N = 100, order = 4) + stencil_set = read_stencil_set(operator_path; order=order) + g = equidistant_grid(0., 1., N) + D = second_derivative(g, stencil_set) + u = rand(size(g)...) + u_xx = rand(size(g)...) + + b_naive = @benchmark $u_xx .= $D*$u + b_reg = @benchmark $apply_region_1d!($u_xx,$u,$D) + b_thrd = @benchmark $apply_region_threaded_1d!($u_xx,$u,$D) + print_benchmark_result("Naive apply",b_naive) + print_benchmark_result("Region apply",b_reg) + print_benchmark_result("Threaded region apply",b_thrd) +end + +function benchmark_var_coeff_1d(;N = 100, order = 4) + stencil_set = read_stencil_set(operator_path; order=order) + g = equidistant_grid(0., 1., N) + c = rand(size(g)...) + c_lz = eval_on(g, x -> 0.5) + D = second_derivative_variable(g, c, stencil_set) + D_lz = second_derivative_variable(g, c_lz, stencil_set) + u = rand(size(g)...) + u_xx = rand(size(g)...) + + b_naive = @benchmark $u_xx .= $D*$u + b_naive_lz = @benchmark $u_xx .= $D_lz*$u + b_reg = @benchmark $apply_region_1d!($u_xx,$u,$D) + b_reg_lz = @benchmark $apply_region_1d!($u_xx,$u,$D_lz) + b_thrd = @benchmark $apply_region_threaded_1d!($u_xx,$u,$D) + b_thrd_lz = @benchmark $apply_region_threaded_1d!($u_xx,$u,$D_lz) + print_benchmark_result("Naive apply",b_naive) + print_benchmark_result("Naive apply lazy coeff",b_naive_lz) + print_benchmark_result("Region apply",b_reg) + print_benchmark_result("Region apply lazy coeff",b_reg_lz) + print_benchmark_result("Threaded region apply",b_thrd) + print_benchmark_result("Threaded region apply lazy coeff",b_thrd_lz) +end + +function benchmark_const_coeff_2d(;N = 100, order = 4) + stencil_set = read_stencil_set(operator_path; order=order) + g = equidistant_grid((0.,0.,),(1.,1.), N, N) + D = Laplace(g, stencil_set) + u = rand(size(g)...) + u_xx = rand(size(g)...) + if order == 2 + clz_sz = 1 + elseif order == 4 + clz_sz = 4 + else + error() + end + + b_naive = @benchmark $u_xx .= $D*$u + b_reg = @benchmark $apply_region_2d!($u_xx,$u,$D,$clz_sz) + b_thrd = @benchmark $apply_region_threaded_2d!($u_xx,$u,$D,$clz_sz) + print_benchmark_result("Naive apply",b_naive) + print_benchmark_result("Region apply",b_reg) + print_benchmark_result("Threaded region apply",b_thrd) +end + +function benchmark_var_coeff_2d(;N = 100, order = 4) + stencil_set = read_stencil_set(operator_path; order=order) + g = equidistant_grid((0.,0.,),(1.,1.), N, N) + c = rand(size(g)...) + c_lz = eval_on(g, x-> 0.5) + D = second_derivative_variable(g, c, stencil_set, 1) + second_derivative_variable(g, c, stencil_set, 2) + D_lz = second_derivative_variable(g, c_lz, stencil_set, 1) + second_derivative_variable(g, c_lz, stencil_set, 2) + u = rand(size(g)...) + u_xx = rand(size(g)...) + + if order == 2 + clz_sz = 1 + elseif order == 4 + clz_sz = 6 + else + error() + end + + # Check correctnesss + # u_xx .= D*u + # u_xx_tmp = zeros(size(u_xx)...) + # u_xx_tmp .= u_xx + # apply_region_threaded_2d!(u_xx, u, D, clz_sz) + + # @show sum(abs.(u_xx_tmp .- u_xx)) + # @show pointer(u_xx_tmp) == pointer(u_xxs + + + b_naive = @benchmark $u_xx .= $D*$u + b_naive_lz = @benchmark $u_xx .= $D_lz*$u + b_reg = @benchmark $apply_region_2d!($u_xx,$u,$D, $clz_sz) + b_reg_lz = @benchmark $apply_region_2d!($u_xx,$u,$D_lz, $clz_sz) + b_thrd = @benchmark $apply_region_threaded_2d!($u_xx,$u,$D, $clz_sz) + b_thrd_lz = @benchmark $apply_region_threaded_2d!($u_xx,$u,$D_lz, $clz_sz) + print_benchmark_result("Naive apply",b_naive) + print_benchmark_result("Naive apply lazy coeff",b_naive_lz) + print_benchmark_result("Region apply",b_reg) + print_benchmark_result("Region apply lazy coeff",b_reg_lz) + print_benchmark_result("Threaded region apply",b_thrd) + print_benchmark_result("Threaded region apply lazy coeff",b_thrd_lz) +end + +function print_benchmark_result(title_str,res) + if title_str[1] != ' ' + title_str = lpad(title_str,length(title_str)+1, " ") + end + if title_str[end] != ' ' + title_str = rpad(title_str,length(title_str)+1, " ") + end + tot_len = 76 + pad_len = Int(tot_len/2) + header = lpad(title_str,pad_len,"*") + header = rpad(header,tot_len,"*") + bottom = repeat("*",tot_len) + println(header) + display(res) + println(bottom) + return +end + +function apply_region_1d!(u_xx, u, D) + clz_sz = SbpOperators.closure_size(D) + tm = D*u + for i ∈ @view eachindex(u)[1:clz_sz] + u_xx[i] = tm[Index{Lower}(i)] + end + for i ∈ @view eachindex(u)[clz_sz+1:end-clz_sz] + u_xx[i] = tm[Index{Interior}(i)] + end + for i ∈ @view eachindex(u)[end-clz_sz+1:end] + u_xx[i] = tm[Index{Upper}(i)] + end +end + +function apply_region_threaded_1d!(u_xx, u, D) + clz_sz = SbpOperators.closure_size(D) + tm = D*u + for i ∈ @view eachindex(u)[1:clz_sz] + u_xx[i] = tm[Index{Lower}(i)] + end + Threads.@threads for i ∈ @view eachindex(u)[clz_sz+1:end-clz_sz] + u_xx[i] = tm[Index{Interior}(i)] + end + for i ∈ @view eachindex(u)[end-clz_sz+1:end] + u_xx[i] = tm[Index{Upper}(i)] + end +end + +function apply_region_2d!(u_xx, u, D, clz_sz) + tm = D*u + for I ∈ @view CartesianIndices(u)[1:clz_sz,1:clz_sz] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Lower}(I[2])] + end + for I ∈ @view CartesianIndices(u)[1:clz_sz,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[1:clz_sz,end-clz_sz+1:end] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Upper}(I[2])] + end + for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,1:clz_sz] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Lower}(I[2])] + end + for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,end-clz_sz+1:end] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Upper}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,1:clz_sz] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Lower}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,end-clz_sz+1:end] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Upper}(I[2])] + end +end + +function apply_region_threaded_2d!(u_xx, u, D, clz_sz) + tm = D*u + for I ∈ @view CartesianIndices(u)[1:clz_sz,1:clz_sz] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Lower}(I[2])] + end + for I ∈ @view CartesianIndices(u)[1:clz_sz,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[1:clz_sz,end-clz_sz+1:end] + u_xx[I] = tm[Index{Lower}(I[1]),Index{Upper}(I[2])] + end + for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,1:clz_sz] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Lower}(I[2])] + end + Threads.@threads for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[clz_sz+1:end-clz_sz,end-clz_sz+1:end] + u_xx[I] = tm[Index{Interior}(I[1]),Index{Upper}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,1:clz_sz] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Lower}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,clz_sz+1:end-clz_sz] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Interior}(I[2])] + end + for I ∈ @view CartesianIndices(u)[end-clz_sz+1:end,end-clz_sz+1:end] + u_xx[I] = tm[Index{Upper}(I[1]),Index{Upper}(I[2])] + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/benchmark_utils.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,321 @@ +import PkgBenchmark +import Markdown +import Mustache +import Dates + +import Diffinitive + +const diffinitive_root = splitpath(pathof(Diffinitive))[1:end-2] |> joinpath +const results_dir = mkpath(joinpath(diffinitive_root, "benchmark/results")) +const template_path = joinpath(diffinitive_root, "benchmark/result.tmpl") + +""" + main(;rev=nothing, target=nothing, baseline=nothing , kwargs...) + +Calls `run_benchmark(args...; kwargs...)` and writes the results as an HTML +file in `benchmark/results`. + + * If `rev` is set, the benchmarks are run for the given mercurial revision. + * If only `baseline` is set, the current working directory is compared with + the revision given in `baseline`. + * If both `target` and `baseline` is set those revision are compared. + +For control over what happens to the benchmark result datastructure see the +different methods of [`run_benchmark`](@ref) +""" +function main(;rev=nothing, target=nothing, baseline=nothing, name=nothing, kwargs...) + if !isnothing(rev) + r = run_benchmark(rev; kwargs...) + elseif !isnothing(baseline) + if isnothing(target) + r = compare_benchmarks(baseline; kwargs...) + else + r = compare_benchmarks(target, baseline; kwargs...) + end + else + # Neither rev, or baseline were set => Run on current working directory. + r = run_benchmark(;kwargs...) + end + + file_path = write_result_html(r; name) + open_in_default_browser(file_path) +end + + +""" + run_benchmark() + +Run the benchmark suite for the current working directory and return a +`PkgBenchmark.BenchmarkResult` +""" +function run_benchmark(;kwargs...) + r = PkgBenchmark.benchmarkpkg(Diffinitive; kwargs...) + + rev = hg_rev() # Should be changed to hg_id() when the html can handle it. + + return add_rev_info(r, rev) +end + +""" + run_benchmark(rev) + +Updates the repository to the given revison and runs the benchmark suite. When +done, reverts the repository to the original state. `rev` can be any +identifier compatible with `hg update`. + +Returns a `PkgBenchmark.BenchmarkResult` +""" +function run_benchmark(rev; kwargs...) + return hg_at_revision(rev) do + run_benchmark(; kwargs...) + end +end + +""" + compare_benchmarks(target, baseline, f=minimum; judgekwargs=Dict()) + +Runs the benchmark at revisions `target` and `baseline` and compares them +using `PkgBenchmark.judge`. `f` is the function used to compare. `judgekwargs` +are keyword arguments passed to `judge`. + +`target` and `baseline` can be any identifier compatible with `hg update`. + +Returns a `PkgBenchmark.BenchmarkJudgement` +""" +function compare_benchmarks(target, baseline; f=minimum, judgekwargs=Dict(), kwargs...) + t = run_benchmark(target; kwargs...) + b = run_benchmark(baseline; kwargs...) + + return PkgBenchmark.judge(t,b,f; judgekwargs...) +end + +""" + compare_benchmarks(baseline, ...) + +Compare the results at the current working directory with the revision +specified in `baseline`. + +Accepts the same arguments as the two revision version. +""" +function compare_benchmarks(baseline; f=minimum, judgekwargs=Dict(), kwargs...) + t = run_benchmark(;kwargs...) + b = run_benchmark(baseline; kwargs...) + + return PkgBenchmark.judge(t,b,f; judgekwargs...) +end + + +function add_rev_info(benchmarkresult, rev) + if endswith(rev,"+") + revstr = "+$rev" # Workaround for the bad presentation of BenchmarkResults. + else + revstr = rev + end + + return PkgBenchmark.BenchmarkResults( + benchmarkresult.name, + revstr, + benchmarkresult.benchmarkgroup, + benchmarkresult.date, + benchmarkresult.julia_commit, + benchmarkresult.vinfo, + benchmarkresult.benchmarkconfig, + ) +end + + +function write_result_html(io, r) + iobuffer = IOBuffer() + PkgBenchmark.export_markdown(iobuffer, r) + + parsed_md = Markdown.parse(String(take!(iobuffer))) + content = Markdown.html(parsed_md) + + template = Mustache.load(template_path) + + dt = Dates.format(PkgBenchmark.date(r), "yyyy-mm-dd HH:MM:SS") + Mustache.render(io, template, Dict("title"=>dt, "content"=>content)) +end + +function write_result_html(r; name=nothing) + dt = Dates.format(PkgBenchmark.date(r), "yyyy-mm-dd HHMMSS") + + if isnothing(name) + file_path = joinpath(results_dir, dt*".html") + else + file_path = joinpath(results_dir, dt*" "*name*".html") + end + + open(file_path, "w") do io + write_result_html(io, r) + end + + return file_path +end + + +PkgBenchmark.date(j::PkgBenchmark.BenchmarkJudgement) = PkgBenchmark.date(PkgBenchmark.target_result(j)) + + +function hg_id() + cmd = Cmd(`hg id`, dir=diffinitive_root) + return readchomp(addenv(cmd, "HGPLAIN"=>"")) +end + +function hg_rev() + cmd = Cmd(`hg id -i`, dir=diffinitive_root) + return readchomp(addenv(cmd, "HGPLAIN"=>"")) +end + +function hg_update(rev) + cmd = Cmd(`hg update --check -r $rev`, dir=diffinitive_root) + run(addenv(cmd, "HGPLAIN"=>"")) + + return nothing +end + +""" + hg_commit(msg; secret=false) + +Make a hg commit with the provided message. If `secret` is true the commit is +in the secret phase stopping it from being pushed. +""" +function hg_commit(msg; secret=false) + if secret + cmd = Cmd(`hg commit --verbose --secret --message $msg`, dir=diffinitive_root) + else + cmd = Cmd(`hg commit --verbose --message $msg`, dir=diffinitive_root) + end + + out = readchomp(addenv(cmd, "HGPLAIN"=>"")) + + return only(match(r"committed changeset \d+:([0-9a-z]+)", out)) +end + +""" + hg_strip(rev; keep=false) + +Strips the given commit from the repo. If `keep` is true, the changes of the +commit are kept in the working directory. +""" +function hg_strip(rev; keep=false) + if keep + cmd = Cmd(`hg --config extensions.strip= strip --keep -r $rev`, dir=diffinitive_root) + else + cmd = Cmd(`hg --config extensions.strip= strip -r $rev`, dir=diffinitive_root) + end + + run(addenv(cmd, "HGPLAIN"=>"")) + + return nothing +end + +""" + hg_is_dirty() + +Return true if the repositopry has uncommited changes. +""" +function hg_is_dirty() + cmd = Cmd(`hg identify --id`, dir=diffinitive_root) + out = readchomp(addenv(cmd, "HGPLAIN"=>"")) + + return endswith(out, "+") +end + +""" + hg_at_revision(f, rev) + +Update the repository to the given revision and run the function `f`. After +`f` is run the working directory is restored. If there are uncommited changes +a temporary commit will be used to save the state of the working directory. +""" +function hg_at_revision(f, rev) + if hg_is_dirty() + hg_with_temporary_commit() do + return _hg_at_revision(f, rev) + end + else + return _hg_at_revision(f, rev) + end +end + +function _hg_at_revision(f, rev) + @assert !hg_is_dirty() + + origin_rev = hg_rev() + + hg_update(rev) + try + return f() + finally + hg_update(origin_rev) + end +end + +""" + hg_with_temporary_commit(f) + +Run the function `f` after making a temporary commit with the current working +directory. After `f` has finished the working directory is restored to its +original state and the temporary commit stripped. +""" +function hg_with_temporary_commit(f) + @assert hg_is_dirty() + + origin_rev = hg_commit("[Automatic commit by julia]",secret=true) + + try + return f() + finally + hg_update(origin_rev) + hg_strip(origin_rev; keep=true) + end +end + + +# From Pluto.jl/src/webserver/WebServer.jl (2023-01-24) +function open_in_default_browser(url::AbstractString)::Bool + try + if Sys.isapple() + Base.run(`open $url`) + true + elseif Sys.iswindows() || detectwsl() + Base.run(`powershell.exe Start "'$url'"`) + true + elseif Sys.islinux() + Base.run(`xdg-open $url`) + true + else + false + end + catch ex + false + end +end + + +main + +# TODO: Better logging of what is happening +# TODO: Improve the workflow? How? + +# TODO: Clean up the HTML output? + # TODO: Make the codeblocks in the table look nicer + # TODO: Change width of tables and code blocks so everything is visible + # TODO: Fix the commit id, it chops off all the important info + # TODO: Make title less verbose + # TBD: Do we have to replace export_markdown? Could use a template instead. + +# Should be able to run the current benchmark script at a different revision. +# Should have a way to filter the benchmark suite + +# TBD: What parts are PkgBenchmark contributing? Can it be stripped out? + + +## Catching the exit code and errors from a command can be done with code similar to + # proc = open(cmd) + # if success(proc) + + # else + + # end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/benchmarks.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,206 @@ +using BenchmarkTools + +using Diffinitive +using Diffinitive.Grids +using Diffinitive.SbpOperators +using Diffinitive.LazyTensors + +using LinearAlgebra + +const SUITE = BenchmarkGroup() + + +sz(d) = ntuple(i->100, d) +ll(d) = ntuple(i->0., d) +lu(d) = ntuple(i->1., d) + +g1 = equidistant_grid(ll(1)[1], lu(1)[1], sz(1)...) +g2 = equidistant_grid(ll(2), lu(2), sz(2)...) +g3 = equidistant_grid(ll(3), lu(3), sz(3)...) + +v1 = rand(sz(1)...) +v2 = rand(sz(2)...) +v3 = rand(sz(3)...) + +u1 = rand(sz(1)...) +u2 = rand(sz(2)...) +u3 = rand(sz(3)...) + +stencil_set = read_stencil_set(joinpath(sbp_operators_path(),"standard_diagonal.toml"); order=4) + +SUITE["derivatives"] = BenchmarkGroup() + + +SUITE["derivatives"]["first_derivative"] = BenchmarkGroup() + +D₁ = first_derivative(g1,stencil_set) +SUITE["derivatives"]["first_derivative"]["1D"] = @benchmarkable $u1 .= $D₁*$v1 + +Dx = first_derivative(g2,stencil_set,1) +Dy = first_derivative(g2,stencil_set,2) +SUITE["derivatives"]["first_derivative"]["2D"] = BenchmarkGroup() +SUITE["derivatives"]["first_derivative"]["2D"]["x"] = @benchmarkable $u2 .= $Dx*$v2 +SUITE["derivatives"]["first_derivative"]["2D"]["y"] = @benchmarkable $u2 .= $Dy*$v2 + +Dx = first_derivative(g3,stencil_set,1) +Dy = first_derivative(g3,stencil_set,2) +Dz = first_derivative(g3,stencil_set,3) +SUITE["derivatives"]["first_derivative"]["3D"] = BenchmarkGroup() +SUITE["derivatives"]["first_derivative"]["3D"]["x"] = @benchmarkable $u3 .= $Dx*$v3 +SUITE["derivatives"]["first_derivative"]["3D"]["y"] = @benchmarkable $u3 .= $Dy*$v3 +SUITE["derivatives"]["first_derivative"]["3D"]["z"] = @benchmarkable $u3 .= $Dz*$v3 + + +SUITE["derivatives"]["second_derivative"] = BenchmarkGroup() + +D₂ = second_derivative(g1,stencil_set) +SUITE["derivatives"]["second_derivative"]["1D"] = @benchmarkable $u1 .= $D₂*$v1 + +Dx = second_derivative(g2,stencil_set,1) +Dy = second_derivative(g2,stencil_set,2) +SUITE["derivatives"]["second_derivative"]["2D"] = BenchmarkGroup() +SUITE["derivatives"]["second_derivative"]["2D"]["x"] = @benchmarkable $u2 .= $Dx*$v2 +SUITE["derivatives"]["second_derivative"]["2D"]["y"] = @benchmarkable $u2 .= $Dy*$v2 + +Dx = second_derivative(g3,stencil_set,1) +Dy = second_derivative(g3,stencil_set,2) +Dz = second_derivative(g3,stencil_set,3) +SUITE["derivatives"]["second_derivative"]["3D"] = BenchmarkGroup() +SUITE["derivatives"]["second_derivative"]["3D"]["x"] = @benchmarkable $u3 .= $Dx*$v3 +SUITE["derivatives"]["second_derivative"]["3D"]["y"] = @benchmarkable $u3 .= $Dy*$v3 +SUITE["derivatives"]["second_derivative"]["3D"]["z"] = @benchmarkable $u3 .= $Dz*$v3 + + +SUITE["derivatives"]["second_derivative_variable"] = BenchmarkGroup() + +c1 = map(x->sin(x)+2, g1) +D₂ = second_derivative_variable(g1, c1, stencil_set) +SUITE["derivatives"]["second_derivative_variable"]["1D"] = @benchmarkable $u1 .= $D₂*$v1 + +c2 = map(x->sin(x[1] + x[2])+2, g2) +Dx = second_derivative_variable(g2, c2, stencil_set, 1) +Dy = second_derivative_variable(g2, c2, stencil_set, 2) +SUITE["derivatives"]["second_derivative_variable"]["2D"] = BenchmarkGroup() +SUITE["derivatives"]["second_derivative_variable"]["2D"]["x"] = @benchmarkable $u2 .= $Dx*$v2 +SUITE["derivatives"]["second_derivative_variable"]["2D"]["y"] = @benchmarkable $u2 .= $Dy*$v2 + +c3 = map(x->sin(norm(x))+2, g3) +Dx = second_derivative_variable(g3, c3, stencil_set, 1) +Dy = second_derivative_variable(g3, c3, stencil_set, 2) +Dz = second_derivative_variable(g3, c3, stencil_set, 3) +SUITE["derivatives"]["second_derivative_variable"]["3D"] = BenchmarkGroup() +SUITE["derivatives"]["second_derivative_variable"]["3D"]["x"] = @benchmarkable $u3 .= $Dx*$v3 +SUITE["derivatives"]["second_derivative_variable"]["3D"]["y"] = @benchmarkable $u3 .= $Dy*$v3 +SUITE["derivatives"]["second_derivative_variable"]["3D"]["z"] = @benchmarkable $u3 .= $Dz*$v3 + + + + +SUITE["derivatives"]["addition"] = BenchmarkGroup() + +D₁ = first_derivative(g1,stencil_set) +D₂ = second_derivative(g1,stencil_set) +SUITE["derivatives"]["addition"]["1D"] = BenchmarkGroup() +SUITE["derivatives"]["addition"]["1D"]["apply,add"] = @benchmarkable $u1 .= $D₁*$v1 + $D₂*$v1 +SUITE["derivatives"]["addition"]["1D"]["add,apply"] = @benchmarkable $u1 .= ($D₁ + $D₂)*$v1 + +Dxx = second_derivative(g2,stencil_set,1) +Dyy = second_derivative(g2,stencil_set,2) +SUITE["derivatives"]["addition"]["2D"] = BenchmarkGroup() +SUITE["derivatives"]["addition"]["2D"]["apply,add"] = @benchmarkable $u2 .= $Dxx*$v2 + $Dyy*$v2 +SUITE["derivatives"]["addition"]["2D"]["add,apply"] = @benchmarkable $u2 .= ($Dxx + $Dyy)*$v2 + +Dxx = second_derivative(g3,stencil_set,1) +Dyy = second_derivative(g3,stencil_set,2) +Dzz = second_derivative(g3,stencil_set,3) +SUITE["derivatives"]["addition"]["3D"] = BenchmarkGroup() +SUITE["derivatives"]["addition"]["3D"]["apply,add"] = @benchmarkable $u3 .= $Dxx*$v3 + $Dyy*$v3 + $Dzz*$v3 +SUITE["derivatives"]["addition"]["3D"]["add,apply"] = @benchmarkable $u3 .= ($Dxx + $Dyy + $Dzz)*$v3 + + +SUITE["derivatives"]["composition"] = BenchmarkGroup() + +Dx = first_derivative(g1,stencil_set) +SUITE["derivatives"]["composition"]["1D"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["1D"]["apply,apply"] = @benchmarkable $u1 .= $Dx*($Dx*$v1) +SUITE["derivatives"]["composition"]["1D"]["compose,apply"] = @benchmarkable $u1 .= ($Dx∘$Dx)*$v1 + +Dx = first_derivative(g2,stencil_set,1) +Dy = first_derivative(g2,stencil_set,2) +SUITE["derivatives"]["composition"]["2D"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["2D"]["apply,apply"] = @benchmarkable $u2 .= $Dy*($Dx*$v2) +SUITE["derivatives"]["composition"]["2D"]["compose,apply"] = @benchmarkable $u2 .= ($Dy∘$Dx)*$v2 + +Dx = first_derivative(g3,stencil_set,1) +Dy = first_derivative(g3,stencil_set,2) +Dz = first_derivative(g3,stencil_set,3) +SUITE["derivatives"]["composition"]["3D"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["xy"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["xy"]["apply,apply"] = @benchmarkable $u3 .= $Dx*($Dy*$v3) +SUITE["derivatives"]["composition"]["3D"]["xy"]["compose,apply"] = @benchmarkable $u3 .= ($Dx∘$Dy)*$v3 + +SUITE["derivatives"]["composition"]["3D"]["yz"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["yz"]["apply,apply"] = @benchmarkable $u3 .= $Dy*($Dz*$v3) +SUITE["derivatives"]["composition"]["3D"]["yz"]["compose,apply"] = @benchmarkable $u3 .= ($Dy∘$Dz)*$v3 + +SUITE["derivatives"]["composition"]["3D"]["xz"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["xz"]["apply,apply"] = @benchmarkable $u3 .= $Dx*($Dz*$v3) +SUITE["derivatives"]["composition"]["3D"]["xz"]["compose,apply"] = @benchmarkable $u3 .= ($Dx∘$Dz)*$v3 + +SUITE["derivatives"]["composition"]["3D"]["xx"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["xx"]["apply,apply"] = @benchmarkable $u3 .= $Dx*($Dx*$v3) +SUITE["derivatives"]["composition"]["3D"]["xx"]["compose,apply"] = @benchmarkable $u3 .= ($Dx∘$Dx)*$v3 + +SUITE["derivatives"]["composition"]["3D"]["yy"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["yy"]["apply,apply"] = @benchmarkable $u3 .= $Dy*($Dy*$v3) +SUITE["derivatives"]["composition"]["3D"]["yy"]["compose,apply"] = @benchmarkable $u3 .= ($Dy∘$Dy)*$v3 + +SUITE["derivatives"]["composition"]["3D"]["zz"] = BenchmarkGroup() +SUITE["derivatives"]["composition"]["3D"]["zz"]["apply,apply"] = @benchmarkable $u3 .= $Dz*($Dz*$v3) +SUITE["derivatives"]["composition"]["3D"]["zz"]["compose,apply"] = @benchmarkable $u3 .= ($Dz∘$Dz)*$v3 + + +SUITE["boundary_terms"] = BenchmarkGroup() + +H = inner_product(g2, stencil_set) +H⁻¹ = inverse_inner_product(g2, stencil_set) +Dxx = second_derivative(g2, stencil_set, 1) +Dyy = second_derivative(g2, stencil_set, 2) + +e₁ₗ = boundary_restriction(g2, stencil_set, CartesianBoundary{1,LowerBoundary}()) +e₁ᵤ = boundary_restriction(g2, stencil_set, CartesianBoundary{1,UpperBoundary}()) +e₂ₗ = boundary_restriction(g2, stencil_set, CartesianBoundary{2,LowerBoundary}()) +e₂ᵤ = boundary_restriction(g2, stencil_set, CartesianBoundary{2,UpperBoundary}()) + +d₁ₗ = normal_derivative(g2, stencil_set, CartesianBoundary{1,LowerBoundary}()) +d₁ᵤ = normal_derivative(g2, stencil_set, CartesianBoundary{1,UpperBoundary}()) +d₂ₗ = normal_derivative(g2, stencil_set, CartesianBoundary{2,LowerBoundary}()) +d₂ᵤ = normal_derivative(g2, stencil_set, CartesianBoundary{2,UpperBoundary}()) + +H₁ₗ = inner_product(boundary_grid(g2, CartesianBoundary{1,LowerBoundary}()), stencil_set) +H₁ᵤ = inner_product(boundary_grid(g2, CartesianBoundary{1,UpperBoundary}()), stencil_set) +H₂ₗ = inner_product(boundary_grid(g2, CartesianBoundary{2,LowerBoundary}()), stencil_set) +H₂ᵤ = inner_product(boundary_grid(g2, CartesianBoundary{2,UpperBoundary}()), stencil_set) + +SUITE["boundary_terms"]["pre_composition"] = @benchmarkable $u2 .= $(H⁻¹∘e₁ₗ'∘H₁ₗ∘d₁ₗ)*$v2 +SUITE["boundary_terms"]["composition"] = @benchmarkable $u2 .= ($H⁻¹∘$e₁ₗ'∘$H₁ₗ∘$d₁ₗ)*$v2 +SUITE["boundary_terms"]["application"] = @benchmarkable $u2 .= $H⁻¹*$e₁ₗ'*$H₁ₗ* $d₁ₗ*$v2 +# An investigation of these allocations can be found in the branch `allocation_test` + +#TODO: Reorg with dimension as first level? To reduce operator creation? + + + +SUITE["lazy_tensors"] = BenchmarkGroup() + +SUITE["lazy_tensors"]["compositions"] = BenchmarkGroup() +s = ScalingTensor(1.,(10,)) +u = rand(10) +v = similar(u) +s3 = s∘s∘s +s4 = s∘s∘s∘s +SUITE["lazy_tensors"]["compositions"]["s∘s∘s"] = @benchmarkable $v .= $s3*$u +SUITE["lazy_tensors"]["compositions"]["s∘s∘s∘s"] = @benchmarkable $v .= $s4*$u + + +SUITE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/make.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,21 @@ +rev = nothing +baseline = nothing +target = nothing + +if "--rev" ∈ ARGS + i = findlast(==("--rev"), ARGS) + rev = ARGS[i+1] +end + +if "--target" ∈ ARGS + i = findlast(==("--target"), ARGS) + target = ARGS[i+1] +end + +if "--baseline" ∈ ARGS + i = findlast(==("--baseline"), ARGS) + baseline = ARGS[i+1] +end + +include("benchmark_utils.jl") +main(;rev, target, baseline)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/result.tmpl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,211 @@ +<html> +<head> + <title>Benchmark result {{title}}</title> + <meta charset="UTF-8"> + + <style> + @charset "UTF-8"; + body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + line-height: 1.4; + max-width: 800px; + margin: 20px auto; + padding: 0 10px; + color: #363636; + background: #fff; + text-rendering:optimizeLegibility + } + + button, input, textarea { + transition:background-color .1s linear, border-color .1s linear, color .1s linear, box-shadow .1s linear, transform .1s ease + } + + h1 { + font-size: 2.2em; + margin-top:0 + } + + h1, h2, h3, h4, h5, h6 { + margin-bottom:12px + } + + h1, h2, h3, h4, h5, h6, strong { + color:#000 + } + + b, h1, h2, h3, h4, h5, h6, strong, th { + font-weight:600 + } + + blockquote { + border-left: 4px solid rgba(0, 150, 191, .67); + margin: 1.5em 0; + padding: .5em 1em; + font-style:italic + } + + blockquote > footer { + margin-top: 10px; + font-style:normal + } + + address, blockquote cite { + font-style:normal + } + + a[href^=mailto]:before { + content: "📧 " + } + + a[href^=tel]:before { + content: "📞 " + } + + a[href^=sms]:before { + content: "💬 " + } + + button, input[type=button], input[type=checkbox], input[type=submit] { + cursor:pointer + } + + input:not([type=checkbox]):not([type=radio]), select { + display:block + } + + button, input, select, textarea { + color: #000; + background-color: #efefef; + font-family: inherit; + font-size: inherit; + margin-right: 6px; + margin-bottom: 6px; + padding: 10px; + border: none; + border-radius: 6px; + outline:none + } + + button, input:not([type=checkbox]):not([type=radio]), select, textarea { + -webkit-appearance:none + } + + textarea { + margin-right: 0; + width: 100%; + box-sizing: border-box; + resize:vertical + } + + button, input[type=button], input[type=submit] { + padding-right: 30px; + padding-left:30px + } + + button:hover, input[type=button]:hover, input[type=submit]:hover { + background:#ddd + } + + button:focus, input:focus, select:focus, textarea:focus { + box-shadow:0 0 0 2px rgba(0, 150, 191, .67) + } + + button:active, input[type=button]:active, input[type=checkbox]:active, input[type=radio]:active, input[type=submit]:active { + transform:translateY(2px) + } + + button:disabled, input:disabled, select:disabled, textarea:disabled { + cursor: not-allowed; + opacity:.5 + } + + ::-webkit-input-placeholder { + color:#949494 + } + + :-ms-input-placeholder { + color:#949494 + } + + ::-ms-input-placeholder { + color:#949494 + } + + ::placeholder { + color:#949494 + } + + a { + text-decoration: none; + color:#0076d1 + } + + a:hover { + text-decoration:underline + } + + code, kbd { + background: #efefef; + color: #000; + padding: 5px; + border-radius:6px + } + + pre > code { + padding: 10px; + display: block; + overflow-x:auto + } + + img { + max-width:100% + } + + hr { + border: none; + border-top:1px solid #dbdbdb + } + + table { + border-collapse: collapse; + margin-bottom: 10px; + width:100% + } + + td, th { + padding: 6px; + text-align:left + } + + th { + border-bottom:1px solid #dbdbdb + } + + tbody tr:nth-child(2n) { + background-color:#efefef + } + + ::-webkit-scrollbar { + height: 10px; + width:10px + } + + ::-webkit-scrollbar-track { + background: #efefef; + border-radius:6px + } + + ::-webkit-scrollbar-thumb { + background: #d5d5d5; + border-radius:6px + } + + ::-webkit-scrollbar-thumb:hover { + background: #c4c4c4 + } + </style> +</head> +<body> + {{{content}}} +</body> +</html>
--- a/docs/Manifest.toml Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,145 +0,0 @@ -# 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"
--- a/docs/Project.toml Mon May 23 07:20:27 2022 +0200 +++ b/docs/Project.toml Tue Feb 10 22:41:19 2026 +0100 @@ -1,3 +1,3 @@ [deps] +Diffinitive = "5a373a26-915f-4769-bcab-bf03835de17b" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -Sbplib = "5a373a26-915f-4769-bcab-bf03835de17b"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/logo_generation.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,1877 @@ +### A Pluto.jl notebook ### +# v0.20.3 + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ e2c959c4-d2b7-11ef-0138-9761d4b6a434 +begin + using CairoMakie + using WGLMakie + using Colors + using PlutoUI + + WGLMakie.activate!() +end + +# ╔═╡ 61d6e5d8-ced5-4fe9-a375-bda329304a43 +md""" +# Logo generation +This notebook generates the .svg containing the logo for the documentation of Diffinitive.jl. +""" + +# ╔═╡ 86bde2a1-8d55-47a7-bd8a-69d74e6a2958 +md""" +## Parameters +""" + +# ╔═╡ 8cb161ab-8b04-4744-86f6-b61f169e1368 +begin + const R = 0.6 # Length of spokes + const d = 0.5 # Diameter of balls + + const spokewidth = 15 + const strokewidth = 12 +end; + +# ╔═╡ 17e62a16-580b-4ec5-88f8-9b73049f6ff9 +logocolors = Colors.JULIA_LOGO_COLORS + +# ╔═╡ 24b4144b-4ee4-4285-9926-217964084fcf +md""" +## The logo +""" + +# ╔═╡ 292bb633-8409-4bb9-be69-fa2a5c34f441 +begin + debug_checkbox = @bind debug CheckBox() + + + md""" + Debug mode: $debug_checkbox + """ +end + +# ╔═╡ 4821f7a0-d4fd-4b28-88cb-fa6745aeccc3 +md""" +### Saving to file +""" + +# ╔═╡ ddb3dc44-5e7b-4b36-878b-cfc7eba8f18b +pwd() + +# ╔═╡ e7ae1f66-06bd-4cc6-be23-2932383dd579 +md""" +## Specification +""" + +# ╔═╡ d6077592-27b7-48ab-bb76-c0d9b5244253 +md""" +### Figure setup +""" + +# ╔═╡ 8c50ead2-0fd1-4190-9776-d233bef513e0 +function deactivate_all_interaction!(ax) + deactivate_interaction!(ax, :dragpan) + deactivate_interaction!(ax, :scrollzoom) + deactivate_interaction!(ax, :rectanglezoom) +end + +# ╔═╡ 3c620b15-7342-458f-a175-a060976b1ceb +function hide_coordinate_system!(ax) + hidespines!(ax) + hidedecorations!(ax) +end + +# ╔═╡ 376b5f3c-508e-4677-8b96-3c75b8cd3443 +md""" +## Misc. functions +""" + +# ╔═╡ d810c823-b111-46ab-84fe-83e5b738d18d +polar(r,θ) = (r*cos(θ), r*sin(θ)) + +# ╔═╡ 233d0556-1cae-4156-8239-2f7e01ac32c6 +function limits(R,d) + θ = π/6 + + Δx_spoke = R*cos(π/6) + Δy_spoke = R*sin(π/6) + xlim = (-(Δx_spoke+d/2), Δx_spoke+d/2) + ylim = (-(Δy_spoke+d/2), R + d/2) + + return (xlim, ylim) +end + +# ╔═╡ 7a580af5-a466-4d6e-bdb3-b0ebcecac802 +function draw_ball!(ax, p; color, d, strokewidth) + scatter!(ax, p; + markersize = d, + color, + markerspace = :data, + strokewidth, + strokecolor = colorant"#000", + ) +end + +# ╔═╡ 50aff181-0513-4b47-a501-1bcd9614dea6 +function draw_logo!(ax;R,d,strokewidth, spokewidth) + origo = (0,0); + θs = π/2 .+ 2π/3*(0:2) + ## Spokes + for θ ∈ θs + lines!([origo, polar(R,θ)]; + color = colorant"#ccc", + linewidth=20, + ) + end + + ## Center + draw_ball!(ax, origo; + color=logocolors.blue, + d, + strokewidth, + ) + + ## Non-center + colors = [logocolors.green, logocolors.red, logocolors.purple] + + for (i,θ) ∈ enumerate(θs) + draw_ball!(ax, polar(R,θ); + color = colors[i], + d, + strokewidth, + ) + end +end + +# ╔═╡ 57811601-acf2-475d-bbe2-bbed4e9f46ba +function logo_figure(; debug = false, transparent = true) + # A bug in WGSLMakie makes it not work with transparent background + if transparent + bgspec = (;backgroundcolor=:transparent) + else + bgspec = (;) + end + + fig = Figure(;bgspec...) + + ax = Axis(fig[1,1]; + aspect=DataAspect(), + limits = limits(R,d), + bgspec..., + ) + + if !debug + hide_coordinate_system!(ax) + deactivate_all_interaction!(ax) + end + + draw_logo!(ax; R,d,strokewidth, spokewidth) + + fig +end + +# ╔═╡ 4d78323f-7126-46a7-b8d7-760bd2d06ab3 +logo_figure(;debug, transparent=false) +# logo_figure(;debug=true, transparent=false) + +# ╔═╡ 38ed8b50-de68-4df3-affe-c09382fd2ec3 +save("src/assets/logo.svg",logo_figure(); backend=CairoMakie) + +# ╔═╡ 798aa3a9-3ef5-48ed-9e8f-eb23403bfc49 +md""" +## Appendix +""" + +# ╔═╡ fca0bd98-c9cb-4423-89b0-9d988ffb105d +PlutoUI.TableOfContents() + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008" + +[compat] +CairoMakie = "~0.12.18" +Colors = "~0.12.11" +PlutoUI = "~0.7.60" +WGLMakie = "~0.10.18" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.11.2" +manifest_format = "2.0" +project_hash = "a086985124f354d2bdad8066fd4f55286038c1ef" + +[[deps.AbstractFFTs]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "d92ad398961a3ed262d8bf04a1a2b8340f915fef" +uuid = "621f4979-c628-5d54-868e-fcf4e3e8185c" +version = "1.5.0" +weakdeps = ["ChainRulesCore", "Test"] + + [deps.AbstractFFTs.extensions] + AbstractFFTsChainRulesCoreExt = "ChainRulesCore" + AbstractFFTsTestExt = "Test" + +[[deps.AbstractPlutoDingetjes]] +deps = ["Pkg"] +git-tree-sha1 = "6e1d2a35f2f90a4bc7c2ed98079b2ba09c35b83a" +uuid = "6e696c72-6542-2067-7265-42206c756150" +version = "1.3.2" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.Adapt]] +deps = ["LinearAlgebra", "Requires"] +git-tree-sha1 = "50c3c56a52972d78e8be9fd135bfb91c9574c140" +uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +version = "4.1.1" +weakdeps = ["StaticArrays"] + + [deps.Adapt.extensions] + AdaptStaticArraysExt = "StaticArrays" + +[[deps.AdaptivePredicates]] +git-tree-sha1 = "7e651ea8d262d2d74ce75fdf47c4d63c07dba7a6" +uuid = "35492f91-a3bd-45ad-95db-fcad7dcfedb7" +version = "1.2.0" + +[[deps.AliasTables]] +deps = ["PtrArrays", "Random"] +git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" +uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" +version = "1.1.3" + +[[deps.Animations]] +deps = ["Colors"] +git-tree-sha1 = "e092fa223bf66a3c41f9c022bd074d916dc303e7" +uuid = "27a7e980-b3e6-11e9-2bcd-0b925532e340" +version = "0.4.2" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Automa]] +deps = ["PrecompileTools", "SIMD", "TranscodingStreams"] +git-tree-sha1 = "a8f503e8e1a5f583fbef15a8440c8c7e32185df2" +uuid = "67c07d97-cdcb-5c2c-af73-a7f9c32a568b" +version = "1.1.0" + +[[deps.AxisAlgorithms]] +deps = ["LinearAlgebra", "Random", "SparseArrays", "WoodburyMatrices"] +git-tree-sha1 = "01b8ccb13d68535d73d2b0c23e39bd23155fb712" +uuid = "13072b0f-2c55-5437-9ae7-d433b7a33950" +version = "1.1.0" + +[[deps.AxisArrays]] +deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"] +git-tree-sha1 = "16351be62963a67ac4083f748fdb3cca58bfd52f" +uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9" +version = "0.4.7" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BitFlags]] +git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" +uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" +version = "0.1.9" + +[[deps.Bonito]] +deps = ["Base64", "CodecZlib", "Colors", "Dates", "Deno_jll", "HTTP", "Hyperscript", "LinearAlgebra", "Markdown", "MsgPack", "Observables", "RelocatableFolders", "SHA", "Sockets", "Tables", "ThreadPools", "URIs", "UUIDs", "WidgetsBase"] +git-tree-sha1 = "534820940e4359c09adc615f8bd06ca90d508ba6" +uuid = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" +version = "4.0.1" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "8873e196c2eb87962a2048b3b8e08946535864a1" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+4" + +[[deps.CEnum]] +git-tree-sha1 = "389ad5c84de1ae7cf0e28e381131c98ea87d54fc" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.5.0" + +[[deps.CRC32c]] +uuid = "8bf52ea8-c179-5cab-976a-9e18b702a9bc" +version = "1.11.0" + +[[deps.CRlibm_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e329286945d0cfc04456972ea732551869af1cfc" +uuid = "4e9b3aee-d8a1-5a3d-ad8b-7d824db253f0" +version = "1.0.1+0" + +[[deps.Cairo]] +deps = ["Cairo_jll", "Colors", "Glib_jll", "Graphics", "Libdl", "Pango_jll"] +git-tree-sha1 = "71aa551c5c33f1a4415867fe06b7844faadb0ae9" +uuid = "159f3aea-2a34-519c-b102-8c37f9878175" +version = "1.1.1" + +[[deps.CairoMakie]] +deps = ["CRC32c", "Cairo", "Cairo_jll", "Colors", "FileIO", "FreeType", "GeometryBasics", "LinearAlgebra", "Makie", "PrecompileTools"] +git-tree-sha1 = "0afa2b4ac444b9412130d68493941e1af462e26a" +uuid = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +version = "0.12.18" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "009060c9a6168704143100f36ab08f06c2af4642" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.2+1" + +[[deps.ChainRulesCore]] +deps = ["Compat", "LinearAlgebra"] +git-tree-sha1 = "1713c74e00545bfe14605d2a2be1712de8fbcb58" +uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" +version = "1.25.1" +weakdeps = ["SparseArrays"] + + [deps.ChainRulesCore.extensions] + ChainRulesCoreSparseArraysExt = "SparseArrays" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.6" + +[[deps.ColorBrewer]] +deps = ["Colors", "JSON", "Test"] +git-tree-sha1 = "61c5334f33d91e570e1d0c3eb5465835242582c4" +uuid = "a2cac450-b92f-5266-8821-25eda20663c8" +version = "0.4.0" + +[[deps.ColorSchemes]] +deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] +git-tree-sha1 = "c785dfb1b3bfddd1da557e861b919819b82bbe5b" +uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" +version = "3.27.1" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.5" + +[[deps.ColorVectorSpace]] +deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] +git-tree-sha1 = "a1f44953f2382ebb937d60dafbe2deea4bd23249" +uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" +version = "0.10.0" +weakdeps = ["SpecialFunctions"] + + [deps.ColorVectorSpace.extensions] + SpecialFunctionsExt = "SpecialFunctions" + +[[deps.Colors]] +deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] +git-tree-sha1 = "362a287c3aa50601b0bc359053d5c2468f0e7ce0" +uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" +version = "0.12.11" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.16.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "f36e5e8fdffcb5646ea5da81495a5a7566005127" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.4.3" + +[[deps.ConstructionBase]] +git-tree-sha1 = "76219f1ed5771adbb096743bff43fb5fdd4c1157" +uuid = "187b0558-2788-49d3-abe0-74a17ed4e7c9" +version = "1.5.8" +weakdeps = ["IntervalSets", "LinearAlgebra", "StaticArrays"] + + [deps.ConstructionBase.extensions] + ConstructionBaseIntervalSetsExt = "IntervalSets" + ConstructionBaseLinearAlgebraExt = "LinearAlgebra" + ConstructionBaseStaticArraysExt = "StaticArrays" + +[[deps.Contour]] +git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" +uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" +version = "0.6.3" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DelaunayTriangulation]] +deps = ["AdaptivePredicates", "EnumX", "ExactPredicates", "Random"] +git-tree-sha1 = "e1371a23fd9816080c828d0ce04373857fe73d33" +uuid = "927a84f5-c5f4-47a5-9785-b46e178433df" +version = "1.6.3" + +[[deps.Deno_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "cd6756e833c377e0ce9cd63fb97689a255f12323" +uuid = "04572ae6-984a-583e-9378-9577a1c2574d" +version = "1.33.4+0" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" + +[[deps.Distributions]] +deps = ["AliasTables", "FillArrays", "LinearAlgebra", "PDMats", "Printf", "QuadGK", "Random", "SpecialFunctions", "Statistics", "StatsAPI", "StatsBase", "StatsFuns"] +git-tree-sha1 = "7901a6117656e29fa2c74a58adb682f380922c47" +uuid = "31c24e10-a181-5473-b8eb-7969acd0382f" +version = "0.25.116" + + [deps.Distributions.extensions] + DistributionsChainRulesCoreExt = "ChainRulesCore" + DistributionsDensityInterfaceExt = "DensityInterface" + DistributionsTestExt = "Test" + + [deps.Distributions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + DensityInterface = "b429d917-457f-4dbc-8f4c-0cc954292b1d" + Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.EarCut_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e3290f2d49e661fbd94046d7e3726ffcb2d41053" +uuid = "5ae413db-bbd1-5e63-b57d-d24a61df00f5" +version = "2.2.4+0" + +[[deps.EnumX]] +git-tree-sha1 = "bdb1942cd4c45e3c678fd11569d5cccd80976237" +uuid = "4e289a0a-7415-4d19-859d-a7e5c4648b56" +version = "1.0.4" + +[[deps.ExactPredicates]] +deps = ["IntervalArithmetic", "Random", "StaticArrays"] +git-tree-sha1 = "b3f2ff58735b5f024c392fde763f29b057e4b025" +uuid = "429591f6-91af-11e9-00e2-59fbe8cec110" +version = "2.2.8" + +[[deps.ExceptionUnwrapping]] +deps = ["Test"] +git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" +uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +version = "0.1.11" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e51db81749b0777b2147fbe7b783ee79045b8e99" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.4+3" + +[[deps.Extents]] +git-tree-sha1 = "063512a13dbe9c40d999c439268539aa552d1ae6" +uuid = "411431e0-e8b7-467b-b5e0-f676ba4f2910" +version = "0.1.5" + +[[deps.FFMPEG_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "8cc47f299902e13f90405ddb5bf87e5d474c0d38" +uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" +version = "6.1.2+0" + +[[deps.FFTW]] +deps = ["AbstractFFTs", "FFTW_jll", "LinearAlgebra", "MKL_jll", "Preferences", "Reexport"] +git-tree-sha1 = "4820348781ae578893311153d69049a93d05f39d" +uuid = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341" +version = "1.8.0" + +[[deps.FFTW_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "4d81ed14783ec49ce9f2e168208a12ce1815aa25" +uuid = "f5851436-0d7a-5f13-b9de-f02708fd171a" +version = "3.3.10+3" + +[[deps.FileIO]] +deps = ["Pkg", "Requires", "UUIDs"] +git-tree-sha1 = "2dd20384bf8c6d411b5c7370865b1e9b26cb2ea3" +uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +version = "1.16.6" +weakdeps = ["HTTP"] + + [deps.FileIO.extensions] + HTTPExt = "HTTP" + +[[deps.FilePaths]] +deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"] +git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629" +uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824" +version = "0.8.3" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates"] +git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.22" +weakdeps = ["Mmap", "Test"] + + [deps.FilePathsBase.extensions] + FilePathsBaseMmapExt = "Mmap" + FilePathsBaseTestExt = "Test" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.FillArrays]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "6a70198746448456524cb442b8af316927ff3e1a" +uuid = "1a297f60-69ca-5386-bcde-b61e274b549b" +version = "1.13.0" +weakdeps = ["PDMats", "SparseArrays", "Statistics"] + + [deps.FillArrays.extensions] + FillArraysPDMatsExt = "PDMats" + FillArraysSparseArraysExt = "SparseArrays" + FillArraysStatisticsExt = "Statistics" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.5" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "21fac3c77d7b5a9fc03b0ec503aa1a6392c34d2b" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.15.0+0" + +[[deps.Format]] +git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" +uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" +version = "1.3.7" + +[[deps.FreeType]] +deps = ["CEnum", "FreeType2_jll"] +git-tree-sha1 = "907369da0f8e80728ab49c1c7e09327bf0d6d999" +uuid = "b38be410-82b0-50bf-ab77-7b57e271db43" +version = "4.1.1" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "786e968a8d2fb167f2e4880baba62e0e26bd8e4e" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.3+1" + +[[deps.FreeTypeAbstraction]] +deps = ["ColorVectorSpace", "Colors", "FreeType", "GeometryBasics"] +git-tree-sha1 = "d52e255138ac21be31fa633200b65e4e71d26802" +uuid = "663a7486-cb36-511b-a19d-713bb74d65c9" +version = "0.10.6" + +[[deps.FriBidi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "846f7026a9decf3679419122b49f8a1fdb48d2d5" +uuid = "559328eb-81f9-559d-9380-de523a88c83c" +version = "1.0.16+0" + +[[deps.GeoFormatTypes]] +git-tree-sha1 = "ce573eab15760315756de2c82df7406c870c7187" +uuid = "68eda718-8dee-11e9-39e7-89f7f65f511f" +version = "0.4.3" + +[[deps.GeoInterface]] +deps = ["DataAPI", "Extents", "GeoFormatTypes"] +git-tree-sha1 = "f4ee66b6b1872a4ca53303fbb51d158af1bf88d4" +uuid = "cf35fbd7-0cd7-5166-be24-54bfbe79505f" +version = "1.4.0" + +[[deps.GeometryBasics]] +deps = ["EarCut_jll", "Extents", "GeoInterface", "IterTools", "LinearAlgebra", "StaticArrays", "StructArrays", "Tables"] +git-tree-sha1 = "b62f2b2d76cee0d61a2ef2b3118cd2a3215d3134" +uuid = "5c1252a2-5f33-56bf-86c9-59e7332b4326" +version = "0.4.11" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Giflib_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6570366d757b50fabae9f4315ad74d2e40c0560a" +uuid = "59f7168a-df46-5410-90c8-f2779963d0ec" +version = "5.2.3+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "b0036b392358c80d2d2124746c2bf3d48d457938" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.82.4+0" + +[[deps.Graphics]] +deps = ["Colors", "LinearAlgebra", "NaNMath"] +git-tree-sha1 = "a641238db938fff9b2f60d08ed9030387daf428c" +uuid = "a2bd30eb-e257-5431-a919-1863eab51364" +version = "1.1.3" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "01979f9b37367603e2848ea225918a3b3861b606" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+1" + +[[deps.GridLayoutBase]] +deps = ["GeometryBasics", "InteractiveUtils", "Observables"] +git-tree-sha1 = "dc6bed05c15523624909b3953686c5f5ffa10adc" +uuid = "3955a311-db13-416c-9275-1d80ed98e5e9" +version = "0.11.1" + +[[deps.Grisu]] +git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" +uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" +version = "1.0.2" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "c67b33b085f6e2faf8bf79a61962e7339a81129c" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "1.10.15" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] +git-tree-sha1 = "55c53be97790242c29031e5cd45e8ac296dadda3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "8.5.0+0" + +[[deps.HypergeometricFunctions]] +deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] +git-tree-sha1 = "b1c2585431c382e3fe5805874bda6aea90a95de9" +uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" +version = "0.3.25" + +[[deps.Hyperscript]] +deps = ["Test"] +git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4" +uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91" +version = "0.0.5" + +[[deps.HypertextLiteral]] +deps = ["Tricks"] +git-tree-sha1 = "7134810b1afce04bbc1045ca1985fbe81ce17653" +uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +version = "0.9.5" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.5" + +[[deps.ImageAxes]] +deps = ["AxisArrays", "ImageBase", "ImageCore", "Reexport", "SimpleTraits"] +git-tree-sha1 = "e12629406c6c4442539436581041d372d69c55ba" +uuid = "2803e5a7-5153-5ecf-9a86-9b4c37f5f5ac" +version = "0.6.12" + +[[deps.ImageBase]] +deps = ["ImageCore", "Reexport"] +git-tree-sha1 = "eb49b82c172811fd2c86759fa0553a2221feb909" +uuid = "c817782e-172a-44cc-b673-b171935fbb9e" +version = "0.1.7" + +[[deps.ImageCore]] +deps = ["ColorVectorSpace", "Colors", "FixedPointNumbers", "MappedArrays", "MosaicViews", "OffsetArrays", "PaddedViews", "PrecompileTools", "Reexport"] +git-tree-sha1 = "8c193230235bbcee22c8066b0374f63b5683c2d3" +uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534" +version = "0.10.5" + +[[deps.ImageIO]] +deps = ["FileIO", "IndirectArrays", "JpegTurbo", "LazyModules", "Netpbm", "OpenEXR", "PNGFiles", "QOI", "Sixel", "TiffImages", "UUIDs", "WebP"] +git-tree-sha1 = "696144904b76e1ca433b886b4e7edd067d76cbf7" +uuid = "82e4d734-157c-48bb-816b-45c225c6df19" +version = "0.6.9" + +[[deps.ImageMetadata]] +deps = ["AxisArrays", "ImageAxes", "ImageBase", "ImageCore"] +git-tree-sha1 = "2a81c3897be6fbcde0802a0ebe6796d0562f63ec" +uuid = "bc367c6b-8a6b-528e-b4bd-a4b897500b49" +version = "0.9.10" + +[[deps.Imath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0936ba688c6d201805a83da835b55c61a180db52" +uuid = "905a6f67-0a94-5f89-b386-d35d92009cd1" +version = "3.1.11+0" + +[[deps.IndirectArrays]] +git-tree-sha1 = "012e604e1c7458645cb8b436f8fba789a51b257f" +uuid = "9b13fd28-a010-5f03-acff-a1bbcff69959" +version = "1.0.0" + +[[deps.Inflate]] +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.5" + +[[deps.IntelOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "LazyArtifacts", "Libdl"] +git-tree-sha1 = "10bd689145d2c3b2a9844005d01087cc1194e79e" +uuid = "1d5cc7b8-4909-519e-a0f8-d0f5ad9712d0" +version = "2024.2.1+0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.Interpolations]] +deps = ["Adapt", "AxisAlgorithms", "ChainRulesCore", "LinearAlgebra", "OffsetArrays", "Random", "Ratios", "Requires", "SharedArrays", "SparseArrays", "StaticArrays", "WoodburyMatrices"] +git-tree-sha1 = "88a101217d7cb38a7b481ccd50d21876e1d1b0e0" +uuid = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" +version = "0.15.1" +weakdeps = ["Unitful"] + + [deps.Interpolations.extensions] + InterpolationsUnitfulExt = "Unitful" + +[[deps.IntervalArithmetic]] +deps = ["CRlibm_jll", "LinearAlgebra", "MacroTools", "RoundingEmulator"] +git-tree-sha1 = "ffb76d09ab0dc9f5a27edac2acec13c74a876cc6" +uuid = "d1acc4aa-44c8-5952-acd4-ba5d80a2a253" +version = "0.22.21" + + [deps.IntervalArithmetic.extensions] + IntervalArithmeticDiffRulesExt = "DiffRules" + IntervalArithmeticForwardDiffExt = "ForwardDiff" + IntervalArithmeticIntervalSetsExt = "IntervalSets" + IntervalArithmeticRecipesBaseExt = "RecipesBase" + + [deps.IntervalArithmetic.weakdeps] + DiffRules = "b552c78f-8df3-52c6-915a-8e097449b14b" + ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" + IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" + RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + +[[deps.IntervalSets]] +git-tree-sha1 = "dba9ddf07f77f60450fe5d2e2beb9854d9a49bd0" +uuid = "8197267c-284f-5f27-9208-e0e47529a953" +version = "0.7.10" +weakdeps = ["Random", "RecipesBase", "Statistics"] + + [deps.IntervalSets.extensions] + IntervalSetsRandomExt = "Random" + IntervalSetsRecipesBaseExt = "RecipesBase" + IntervalSetsStatisticsExt = "Statistics" + +[[deps.InverseFunctions]] +git-tree-sha1 = "a779299d77cd080bf77b97535acecd73e1c5e5cb" +uuid = "3587e190-3f89-42d0-90ee-14403ec27112" +version = "0.1.17" +weakdeps = ["Dates", "Test"] + + [deps.InverseFunctions.extensions] + InverseFunctionsDatesExt = "Dates" + InverseFunctionsTestExt = "Test" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.Isoband]] +deps = ["isoband_jll"] +git-tree-sha1 = "f9b6d97355599074dc867318950adaa6f9946137" +uuid = "f1662d9f-8043-43de-a69a-05efc1cc6ff4" +version = "0.1.1" + +[[deps.IterTools]] +git-tree-sha1 = "42d5f897009e7ff2cf88db414a389e5ed1bdd023" +uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +version = "1.10.0" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JpegTurbo]] +deps = ["CEnum", "FileIO", "ImageCore", "JpegTurbo_jll", "TOML"] +git-tree-sha1 = "fa6d0bcff8583bac20f1ffa708c3913ca605c611" +uuid = "b835a17e-a41a-41e7-81f0-2f016b05efe0" +version = "0.1.5" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "eac1206917768cb54957c65a615460d87b455fc1" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.1.1+0" + +[[deps.KernelDensity]] +deps = ["Distributions", "DocStringExtensions", "FFTW", "Interpolations", "StatsBase"] +git-tree-sha1 = "7d703202e65efa1369de1279c162b915e245eed1" +uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" +version = "0.6.9" + +[[deps.LAME_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "170b660facf5df5de098d866564877e119141cbd" +uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" +version = "3.100.2+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "4.0.1+0" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "78211fb6cbc872f77cad3fc0b6cf647d923f4929" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "18.1.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.3+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.4.0" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +version = "1.11.0" + +[[deps.LazyModules]] +git-tree-sha1 = "a560dd966b386ac9ae60bdd3a3d3a326062d3c3e" +uuid = "8cdb02fc-e678-4876-92c5-9defec4f444e" +version = "0.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.6.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.7.2+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "27ecae93dd25ee0909666e6835051dd684cc035e" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+2" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "8be878062e0ffa2c3f67bb58a595375eda5de80b" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.11.0+0" + +[[deps.Libglvnd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] +git-tree-sha1 = "ff3b4b9d35de638936a525ecd36e86a8bb919d11" +uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" +version = "1.7.0+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "df37206100d39f79b3376afb6b9cee4970041c61" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.51.1+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "89211ea35d9df5831fca5d33552c02bd33878419" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.3+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "4ab7581296671007fc33f07a721631b8855f4b1d" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.7.1+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e888ad02ce716b319e6bdb985d2ef300e7089889" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.3+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.29" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "f02b56007b064fbfddb4c9cd60161b6dd0f40df3" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.1.0" + +[[deps.MIMEs]] +git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb" +uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65" +version = "0.1.4" + +[[deps.MKL_jll]] +deps = ["Artifacts", "IntelOpenMP_jll", "JLLWrappers", "LazyArtifacts", "Libdl", "oneTBB_jll"] +git-tree-sha1 = "f046ccd0c6db2832a9f639e2c669c6fe867e5f4f" +uuid = "856f044c-d86e-5d09-b602-aeab76dc8ba7" +version = "2024.2.0+0" + +[[deps.MacroTools]] +git-tree-sha1 = "72aebe0b5051e5143a079a4685a46da330a40472" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.15" + +[[deps.Makie]] +deps = ["Animations", "Base64", "CRC32c", "ColorBrewer", "ColorSchemes", "ColorTypes", "Colors", "Contour", "Dates", "DelaunayTriangulation", "Distributions", "DocStringExtensions", "Downloads", "FFMPEG_jll", "FileIO", "FilePaths", "FixedPointNumbers", "Format", "FreeType", "FreeTypeAbstraction", "GeometryBasics", "GridLayoutBase", "ImageBase", "ImageIO", "InteractiveUtils", "Interpolations", "IntervalSets", "InverseFunctions", "Isoband", "KernelDensity", "LaTeXStrings", "LinearAlgebra", "MacroTools", "MakieCore", "Markdown", "MathTeXEngine", "Observables", "OffsetArrays", "Packing", "PlotUtils", "PolygonOps", "PrecompileTools", "Printf", "REPL", "Random", "RelocatableFolders", "Scratch", "ShaderAbstractions", "Showoff", "SignedDistanceFields", "SparseArrays", "Statistics", "StatsBase", "StatsFuns", "StructArrays", "TriplotBase", "UnicodeFun", "Unitful"] +git-tree-sha1 = "be3051d08b78206fb5e688e8d70c9e84d0264117" +uuid = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" +version = "0.21.18" + +[[deps.MakieCore]] +deps = ["ColorTypes", "GeometryBasics", "IntervalSets", "Observables"] +git-tree-sha1 = "9019b391d7d086e841cbeadc13511224bd029ab3" +uuid = "20f20a25-4f0e-4fdf-b5d1-57303727442b" +version = "0.8.12" + +[[deps.MappedArrays]] +git-tree-sha1 = "2dab0221fe2b0f2cb6754eaa743cc266339f527e" +uuid = "dbb5928d-eab1-5f90-85c2-b9b0edb7c900" +version = "0.4.2" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MathTeXEngine]] +deps = ["AbstractTrees", "Automa", "DataStructures", "FreeTypeAbstraction", "GeometryBasics", "LaTeXStrings", "REPL", "RelocatableFolders", "UnicodeFun"] +git-tree-sha1 = "f45c8916e8385976e1ccd055c9874560c257ab13" +uuid = "0a4f8689-d25c-4efe-a92b-7142dfc1aa53" +version = "0.6.2" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] +git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.9" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MosaicViews]] +deps = ["MappedArrays", "OffsetArrays", "PaddedViews", "StackViews"] +git-tree-sha1 = "7b86a5d4d70a9f5cdf2dacb3cbe6d251d1a61dbe" +uuid = "e94cdb99-869f-56ef-bcf0-1ae2bcbe0389" +version = "0.3.4" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" + +[[deps.MsgPack]] +deps = ["Serialization"] +git-tree-sha1 = "f5db02ae992c260e4826fe78c942954b48e1d9c2" +uuid = "99f44e22-a591-53d1-9472-aa23ef4bd671" +version = "1.2.1" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "030ea22804ef91648f29b7ad3fc15fa49d0e6e71" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.0.3" + +[[deps.Netpbm]] +deps = ["FileIO", "ImageCore", "ImageMetadata"] +git-tree-sha1 = "d92b107dbb887293622df7697a2223f9f8176fcd" +uuid = "f09324ee-3d7c-5217-9330-fc30815ba969" +version = "1.1.1" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.Observables]] +git-tree-sha1 = "7438a59546cf62428fc9d1bc94729146d37a7225" +uuid = "510215fc-4207-5dde-b226-833fc4488ee2" +version = "0.5.5" + +[[deps.OffsetArrays]] +git-tree-sha1 = "5e1897147d1ff8d98883cda2be2187dcf57d8f0c" +uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" +version = "1.15.0" +weakdeps = ["Adapt"] + + [deps.OffsetArrays.extensions] + OffsetArraysAdaptExt = "Adapt" + +[[deps.Ogg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "887579a3eb005446d514ab7aeac5d1d027658b8f" +uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" +version = "1.3.5+1" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.27+1" + +[[deps.OpenEXR]] +deps = ["Colors", "FileIO", "OpenEXR_jll"] +git-tree-sha1 = "97db9e07fe2091882c765380ef58ec553074e9c7" +uuid = "52e1d378-f018-4a11-a4be-720524705ac7" +version = "0.3.3" + +[[deps.OpenEXR_jll]] +deps = ["Artifacts", "Imath_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "8292dd5c8a38257111ada2174000a33745b06d4e" +uuid = "18a262bb-aa17-5467-a713-aee519bc75cb" +version = "3.2.4+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+2" + +[[deps.OpenSSL]] +deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] +git-tree-sha1 = "38cb508d080d21dc1128f7fb04f20387ed4c0af4" +uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" +version = "1.4.3" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.15+3" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.6+0" + +[[deps.Opus_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6703a85cb3781bd5909d48730a67205f3f31a575" +uuid = "91d4177d-7536-5919-b921-800302f37372" +version = "1.3.3+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "12f1439c4f986bb868acda6ea33ebc78e19b95ad" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.7.0" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.PDMats]] +deps = ["LinearAlgebra", "SparseArrays", "SuiteSparse"] +git-tree-sha1 = "949347156c25054de2db3b166c52ac4728cbad65" +uuid = "90014a1f-27ba-587c-ab20-58faa44d9150" +version = "0.11.31" + +[[deps.PNGFiles]] +deps = ["Base64", "CEnum", "ImageCore", "IndirectArrays", "OffsetArrays", "libpng_jll"] +git-tree-sha1 = "67186a2bc9a90f9f85ff3cc8277868961fb57cbd" +uuid = "f57f5aa1-a3ce-4bc8-8ab9-96f992907883" +version = "0.4.3" + +[[deps.Packing]] +deps = ["GeometryBasics"] +git-tree-sha1 = "bc5bf2ea3d5351edf285a06b0016788a121ce92c" +uuid = "19eb6ba3-879d-56ad-ad62-d5c202156566" +version = "0.5.1" + +[[deps.PaddedViews]] +deps = ["OffsetArrays"] +git-tree-sha1 = "0fac6313486baae819364c52b4f483450a9d793f" +uuid = "5432bcbf-9aad-5242-b902-cca2824c8663" +version = "0.5.12" + +[[deps.Pango_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ed6834e95bd326c52d5675b4181386dfbe885afb" +uuid = "36c8627f-9965-5494-a995-c6b170f724f3" +version = "1.55.5+0" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.11.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PkgVersion]] +deps = ["Pkg"] +git-tree-sha1 = "f9501cc0430a26bc3d156ae1b5b0c1b47af4d6da" +uuid = "eebad327-c553-4316-9ea0-9fa01ccd7688" +version = "0.3.3" + +[[deps.PlotUtils]] +deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"] +git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18" +uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" +version = "1.4.3" + +[[deps.PlutoUI]] +deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"] +git-tree-sha1 = "eba4810d5e6a01f612b948c9fa94f905b49087b0" +uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +version = "0.7.60" + +[[deps.PolygonOps]] +git-tree-sha1 = "77b3d3605fc1cd0b42d95eba87dfcd2bf67d5ff6" +uuid = "647866c9-e3ac-4575-94e7-e3d426903924" +version = "0.1.2" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "8f6bc219586aef8baf0ff9a5fe16ee9c70cb65e4" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.10.2" + +[[deps.PtrArrays]] +git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.2.1" + +[[deps.QOI]] +deps = ["ColorTypes", "FileIO", "FixedPointNumbers"] +git-tree-sha1 = "8b3fc30bc0390abdce15f8822c889f669baed73d" +uuid = "4b34888f-f399-49d4-9bb3-47ed5cae4e65" +version = "1.0.1" + +[[deps.QuadGK]] +deps = ["DataStructures", "LinearAlgebra"] +git-tree-sha1 = "cda3b045cf9ef07a08ad46731f5a3165e56cf3da" +uuid = "1fd47b50-473d-5c70-9696-f719f8f3bcdc" +version = "2.11.1" + + [deps.QuadGK.extensions] + QuadGKEnzymeExt = "Enzyme" + + [deps.QuadGK.weakdeps] + Enzyme = "7da242da-08ed-463a-9acd-ee780be4f1d9" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RangeArrays]] +git-tree-sha1 = "b9039e93773ddcfc828f12aadf7115b4b4d225f5" +uuid = "b3c3ace0-ae52-54e7-9d0b-2c1406fd6b9d" +version = "0.3.2" + +[[deps.Ratios]] +deps = ["Requires"] +git-tree-sha1 = "1342a47bf3260ee108163042310d26f2be5ec90b" +uuid = "c84ed2f1-dad5-54f0-aa8e-dbefe2724439" +version = "0.4.5" +weakdeps = ["FixedPointNumbers"] + + [deps.Ratios.extensions] + RatiosFixedPointNumbersExt = "FixedPointNumbers" + +[[deps.RecipesBase]] +deps = ["PrecompileTools"] +git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.3.4" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.RelocatableFolders]] +deps = ["SHA", "Scratch"] +git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" +uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" +version = "1.0.1" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.Rmath]] +deps = ["Random", "Rmath_jll"] +git-tree-sha1 = "852bd0f55565a9e973fcfee83a84413270224dc4" +uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" +version = "0.8.0" + +[[deps.Rmath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8" +uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" +version = "0.5.1+0" + +[[deps.RoundingEmulator]] +git-tree-sha1 = "40b9edad2e5287e05bd413a38f61a8ff55b9557b" +uuid = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705" +version = "0.2.1" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.SIMD]] +deps = ["PrecompileTools"] +git-tree-sha1 = "fea870727142270bdf7624ad675901a1ee3b4c87" +uuid = "fdea26ae-647d-5447-a871-4b548cad5224" +version = "3.7.1" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.2.1" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.ShaderAbstractions]] +deps = ["ColorTypes", "FixedPointNumbers", "GeometryBasics", "LinearAlgebra", "Observables", "StaticArrays", "StructArrays", "Tables"] +git-tree-sha1 = "79123bc60c5507f035e6d1d9e563bb2971954ec8" +uuid = "65257c39-d410-5151-9873-9b3e5be5013e" +version = "0.4.1" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +version = "1.11.0" + +[[deps.Showoff]] +deps = ["Dates", "Grisu"] +git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" +uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" +version = "1.0.3" + +[[deps.SignedDistanceFields]] +deps = ["Random", "Statistics", "Test"] +git-tree-sha1 = "d263a08ec505853a5ff1c1ebde2070419e3f28e9" +uuid = "73760f76-fbc4-59ce-8f25-708e95d2df96" +version = "0.4.0" + +[[deps.SimpleBufferStream]] +git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.2.0" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sixel]] +deps = ["Dates", "FileIO", "ImageCore", "IndirectArrays", "OffsetArrays", "REPL", "libsixel_jll"] +git-tree-sha1 = "2da10356e31327c7096832eb9cd86307a50b1eb6" +uuid = "45858cf5-a6b0-47a3-bbea-62219f50df47" +version = "0.1.3" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.11.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "64cca0c26b4f31ba18f13f6c12af7c85f478cfde" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.5.0" +weakdeps = ["ChainRulesCore"] + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + +[[deps.StableRNGs]] +deps = ["Random"] +git-tree-sha1 = "83e6cce8324d49dfaf9ef059227f91ed4441a8e5" +uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" +version = "1.0.2" + +[[deps.StackViews]] +deps = ["OffsetArrays"] +git-tree-sha1 = "46e589465204cd0c08b4bd97385e4fa79a0c770c" +uuid = "cae243ae-269e-4f55-b966-ac2d0dc13c15" +version = "0.1.1" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "47091a0340a675c738b1304b58161f3b0839d454" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.10" +weakdeps = ["ChainRulesCore", "Statistics"] + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "29321314c920c26684834965ec2ce0dacc9cf8e5" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.4" + +[[deps.StatsFuns]] +deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] +git-tree-sha1 = "b423576adc27097764a90e163157bcfc9acf0f46" +uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +version = "1.3.2" +weakdeps = ["ChainRulesCore", "InverseFunctions"] + + [deps.StatsFuns.extensions] + StatsFunsChainRulesCoreExt = "ChainRulesCore" + StatsFunsInverseFunctionsExt = "InverseFunctions" + +[[deps.StructArrays]] +deps = ["ConstructionBase", "DataAPI", "Tables"] +git-tree-sha1 = "9537ef82c42cdd8c5d443cbc359110cbb36bae10" +uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" +version = "0.6.21" + + [deps.StructArrays.extensions] + StructArraysAdaptExt = "Adapt" + StructArraysGPUArraysCoreExt = ["GPUArraysCore", "KernelAbstractions"] + StructArraysLinearAlgebraExt = "LinearAlgebra" + StructArraysSparseArraysExt = "SparseArrays" + StructArraysStaticArraysExt = "StaticArrays" + + [deps.StructArrays.weakdeps] + Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" + GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" + KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" + LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse]] +deps = ["Libdl", "LinearAlgebra", "Serialization", "SparseArrays"] +uuid = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.7.0+0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "598cd7c1f68d1e205689b1c2fe65a9f85846f297" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.12.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TensorCore]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" +uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" +version = "0.1.1" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.ThreadPools]] +deps = ["Printf", "RecipesBase", "Statistics"] +git-tree-sha1 = "50cb5f85d5646bc1422aa0238aa5bfca99ca9ae7" +uuid = "b189fb0b-2eb5-4ed4-bc0c-d34c51242431" +version = "2.1.1" + +[[deps.TiffImages]] +deps = ["ColorTypes", "DataStructures", "DocStringExtensions", "FileIO", "FixedPointNumbers", "IndirectArrays", "Inflate", "Mmap", "OffsetArrays", "PkgVersion", "ProgressMeter", "SIMD", "UUIDs"] +git-tree-sha1 = "3c0faa42f2bd3c6d994b06286bba2328eae34027" +uuid = "731e570b-9d59-4bfa-96dc-6df516fadf69" +version = "0.11.2" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.Tricks]] +git-tree-sha1 = "7822b97e99a1672bfb1b49b668a6d46d58d8cbcb" +uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775" +version = "0.1.9" + +[[deps.TriplotBase]] +git-tree-sha1 = "4d4ed7f294cda19382ff7de4c137d24d16adc89b" +uuid = "981d1d27-644d-49a2-9326-4793e63143c3" +version = "0.1.0" + +[[deps.URIs]] +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.UnicodeFun]] +deps = ["REPL"] +git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" +uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" +version = "0.4.1" + +[[deps.Unitful]] +deps = ["Dates", "LinearAlgebra", "Random"] +git-tree-sha1 = "c0667a8e676c53d390a09dc6870b3d8d6650e2bf" +uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" +version = "1.22.0" +weakdeps = ["ConstructionBase", "InverseFunctions"] + + [deps.Unitful.extensions] + ConstructionBaseUnitfulExt = "ConstructionBase" + InverseFunctionsUnitfulExt = "InverseFunctions" + +[[deps.WGLMakie]] +deps = ["Bonito", "Colors", "FileIO", "FreeTypeAbstraction", "GeometryBasics", "Hyperscript", "LinearAlgebra", "Makie", "Observables", "PNGFiles", "PrecompileTools", "RelocatableFolders", "ShaderAbstractions", "StaticArrays"] +git-tree-sha1 = "676bd14390033825be847e138108a1c53701407d" +uuid = "276b4fcb-3e11-5398-bf8b-a0c2d153d008" +version = "0.10.18" + +[[deps.WebP]] +deps = ["CEnum", "ColorTypes", "FileIO", "FixedPointNumbers", "ImageCore", "libwebp_jll"] +git-tree-sha1 = "aa1ca3c47f119fbdae8770c29820e5e6119b83f2" +uuid = "e3aaa7dc-3e4b-44e0-be63-ffb868ccd7c1" +version = "0.1.3" + +[[deps.WidgetsBase]] +deps = ["Observables"] +git-tree-sha1 = "30a1d631eb06e8c868c559599f915a62d55c2601" +uuid = "eead4739-05f7-45a1-878c-cee36b57321c" +version = "0.1.4" + +[[deps.WoodburyMatrices]] +deps = ["LinearAlgebra", "SparseArrays"] +git-tree-sha1 = "c1a7aa6219628fcd757dede0ca95e245c5cd9511" +uuid = "efce3f68-66dc-5838-9240-27a6d6f5f9b6" +version = "1.0.0" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "a2fccc6559132927d4c5dc183e3e01048c6dcbd6" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.13.5+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "7d1671acbe47ac88e981868a078bd6b4e27c5191" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.42+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "beef98d5aad604d9e7d60b2ece5181f7888e2fd6" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.6.4+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "9dafcee1d24c4f024e7edc92603cedba72118283" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+3" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e9216fdcd8514b7072b43653874fd688e4c6c003" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.12+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "89799ae67c17caa5b3b5a19b8469eeee474377db" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.5+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d7155fea91a4123ef59f42c4afb5ab3b4ca95058" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+3" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "a490c6212a0e90d2d55111ac956f7c4fa9c277a6" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+1" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c57201109a9e4c0585b208bb408bc41d205ac4e9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.2+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "1a74296303b6524a0472a8cb12d3d87a78eb3612" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.17.0+3" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6dba04dbfb72ae3ebe5418ba33d087ba8aa8cb00" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.1+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "622cf78670d067c738667aaa96c553430b65e269" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.7+0" + +[[deps.isoband_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "51b5eeb3f98367157a7a12a1fb0aa5328946c03c" +uuid = "9a68df92-36a6-505f-a73e-abb412b6bfb4" +version = "0.2.3+0" + +[[deps.libaom_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "522c1df09d05a71785765d19c9524661234738e9" +uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" +version = "3.11.0+0" + +[[deps.libass_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "e17c115d55c5fbb7e52ebedb427a0dca79d4484e" +uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" +version = "0.15.2+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.11.0+0" + +[[deps.libfdk_aac_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8a22cf860a7d27e4f3498a0fe0811a7957badb38" +uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" +version = "2.0.3+0" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7b5bbf1efbafb5eca466700949625e07533aff2" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.45+1" + +[[deps.libsixel_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "libpng_jll"] +git-tree-sha1 = "bf6bb896bd59692d1074fd69af0e5a1b64e64d5e" +uuid = "075b6546-f08a-558a-be8f-8157d0f608a5" +version = "1.10.4+1" + +[[deps.libvorbis_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll", "Pkg"] +git-tree-sha1 = "490376214c4721cdaca654041f635213c6165cb3" +uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" +version = "1.3.7+2" + +[[deps.libwebp_jll]] +deps = ["Artifacts", "Giflib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libglvnd_jll", "Libtiff_jll", "libpng_jll"] +git-tree-sha1 = "d2408cac540942921e7bd77272c32e58c33d8a77" +uuid = "c5f90fcd-3b7e-5836-afba-fc50a0988cb2" +version = "1.5.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.59.0+0" + +[[deps.oneTBB_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7d0ea0f4895ef2f5cb83645fa689e52cb55cf493" +uuid = "1317d2d5-d96f-522e-a858-c73665f53c3e" +version = "2021.12.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.x264_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" +uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" +version = "10164.0.1+0" + +[[deps.x265_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "dcc541bb19ed5b0ede95581fb2e41ecf179527d2" +uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" +version = "3.6.0+0" +""" + +# ╔═╡ Cell order: +# ╟─61d6e5d8-ced5-4fe9-a375-bda329304a43 +# ╟─86bde2a1-8d55-47a7-bd8a-69d74e6a2958 +# ╠═8cb161ab-8b04-4744-86f6-b61f169e1368 +# ╠═17e62a16-580b-4ec5-88f8-9b73049f6ff9 +# ╟─24b4144b-4ee4-4285-9926-217964084fcf +# ╟─292bb633-8409-4bb9-be69-fa2a5c34f441 +# ╟─4d78323f-7126-46a7-b8d7-760bd2d06ab3 +# ╟─4821f7a0-d4fd-4b28-88cb-fa6745aeccc3 +# ╠═ddb3dc44-5e7b-4b36-878b-cfc7eba8f18b +# ╠═38ed8b50-de68-4df3-affe-c09382fd2ec3 +# ╟─e7ae1f66-06bd-4cc6-be23-2932383dd579 +# ╠═50aff181-0513-4b47-a501-1bcd9614dea6 +# ╟─d6077592-27b7-48ab-bb76-c0d9b5244253 +# ╠═57811601-acf2-475d-bbe2-bbed4e9f46ba +# ╠═8c50ead2-0fd1-4190-9776-d233bef513e0 +# ╠═3c620b15-7342-458f-a175-a060976b1ceb +# ╟─376b5f3c-508e-4677-8b96-3c75b8cd3443 +# ╠═d810c823-b111-46ab-84fe-83e5b738d18d +# ╠═233d0556-1cae-4156-8239-2f7e01ac32c6 +# ╠═7a580af5-a466-4d6e-bdb3-b0ebcecac802 +# ╟─798aa3a9-3ef5-48ed-9e8f-eb23403bfc49 +# ╠═e2c959c4-d2b7-11ef-0138-9761d4b6a434 +# ╠═fca0bd98-c9cb-4423-89b0-9d988ffb105d +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002
--- a/docs/make.jl Mon May 23 07:20:27 2022 +0200 +++ b/docs/make.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,14 +1,16 @@ using Documenter -using Sbplib +using Diffinitive -using Sbplib.DiffOps -using Sbplib.Grids -using Sbplib.LazyTensors -using Sbplib.RegionIndices -using Sbplib.SbpOperators -using Sbplib.StaticDicts +using Diffinitive.Grids +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices +using Diffinitive.SbpOperators -sitename = "Sbplib.jl" +sitename = "Diffinitive.jl" + +remotes = nothing +edit_link = nothing +repolink = nothing if "--prettyurls" ∈ ARGS prettyurls = true @@ -26,17 +28,19 @@ pages = [ "Home" => "index.md", "operator_file_format.md", + "grids_and_grid_functions.md", + "matrix_and_tensor_representations.md", + "manifolds_charts_atlases.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) +format=Documenter.HTML(;prettyurls, edit_link, repolink) + +makedocs(;sitename, pages, format, build, remotes)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/assets/logo.svg Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="450" viewBox="0 0 600 450"> +<defs> +<clipPath id="clip-0-66c03fc8"> +<path clip-rule="nonzero" d="M 213 16 L 387 16 L 387 178 L 213 178 Z M 213 16 "/> +</clipPath> +<clipPath id="clip-1-66c03fc8"> +<path clip-rule="nonzero" d="M 70 272 L 232 272 L 232 434 L 70 434 Z M 70 272 "/> +</clipPath> +<clipPath id="clip-2-66c03fc8"> +<path clip-rule="nonzero" d="M 368 272 L 530 272 L 530 434 L 368 434 Z M 368 272 "/> +</clipPath> +</defs> +<path fill="none" stroke-width="20" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(80.000001%, 80.000001%, 80.000001%)" stroke-opacity="1" stroke-miterlimit="2" d="M 300 269.785156 L 300 90.644531 "/> +<path fill="none" stroke-width="20" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(80.000001%, 80.000001%, 80.000001%)" stroke-opacity="1" stroke-miterlimit="2" d="M 300 269.785156 L 144.710938 359.355469 "/> +<path fill="none" stroke-width="20" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(80.000001%, 80.000001%, 80.000001%)" stroke-opacity="1" stroke-miterlimit="2" d="M 300 269.785156 L 455.289062 359.355469 "/> +<path fill-rule="nonzero" fill="rgb(25.098041%, 38.82353%, 84.705883%)" fill-opacity="1" stroke-width="12" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="2" d="M 352.671875 269.785156 C 352.671875 240.722656 329.089844 217.164062 300 217.164062 C 270.910156 217.164062 247.328125 240.722656 247.328125 269.785156 C 247.328125 298.847656 270.910156 322.410156 300 322.410156 C 329.089844 322.410156 352.671875 298.847656 352.671875 269.785156 Z M 352.671875 269.785156 "/> +<path fill-rule="nonzero" fill="rgb(21.960784%, 59.607846%, 14.901961%)" fill-opacity="1" d="M 352.671875 90.644531 C 352.671875 61.578125 329.089844 38.019531 300 38.019531 C 270.910156 38.019531 247.328125 61.578125 247.328125 90.644531 C 247.328125 119.707031 270.910156 143.265625 300 143.265625 C 329.089844 143.265625 352.671875 119.707031 352.671875 90.644531 Z M 352.671875 90.644531 "/> +<g clip-path="url(#clip-0-66c03fc8)"> +<path fill="none" stroke-width="12" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="2" d="M 352.671875 90.644531 C 352.671875 61.578125 329.089844 38.019531 300 38.019531 C 270.910156 38.019531 247.328125 61.578125 247.328125 90.644531 C 247.328125 119.707031 270.910156 143.265625 300 143.265625 C 329.089844 143.265625 352.671875 119.707031 352.671875 90.644531 Z M 352.671875 90.644531 "/> +</g> +<path fill-rule="nonzero" fill="rgb(79.607844%, 23.529412%, 20%)" fill-opacity="1" d="M 197.386719 359.355469 C 197.386719 330.292969 173.804688 306.734375 144.710938 306.734375 C 115.621094 306.734375 92.039062 330.292969 92.039062 359.355469 C 92.039062 388.421875 115.621094 411.980469 144.710938 411.980469 C 173.804688 411.980469 197.386719 388.421875 197.386719 359.355469 Z M 197.386719 359.355469 "/> +<g clip-path="url(#clip-1-66c03fc8)"> +<path fill="none" stroke-width="12" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="2" d="M 197.386719 359.355469 C 197.386719 330.292969 173.804688 306.734375 144.710938 306.734375 C 115.621094 306.734375 92.039062 330.292969 92.039062 359.355469 C 92.039062 388.421875 115.621094 411.980469 144.710938 411.980469 C 173.804688 411.980469 197.386719 388.421875 197.386719 359.355469 Z M 197.386719 359.355469 "/> +</g> +<path fill-rule="nonzero" fill="rgb(58.431375%, 34.509805%, 69.803923%)" fill-opacity="1" d="M 507.960938 359.355469 C 507.960938 330.292969 484.378906 306.734375 455.289062 306.734375 C 426.195312 306.734375 402.613281 330.292969 402.613281 359.355469 C 402.613281 388.421875 426.195312 411.980469 455.289062 411.980469 C 484.378906 411.980469 507.960938 388.421875 507.960938 359.355469 Z M 507.960938 359.355469 "/> +<g clip-path="url(#clip-2-66c03fc8)"> +<path fill="none" stroke-width="12" stroke-linecap="butt" stroke-linejoin="miter" stroke="rgb(0%, 0%, 0%)" stroke-opacity="1" stroke-miterlimit="2" d="M 507.960938 359.355469 C 507.960938 330.292969 484.378906 306.734375 455.289062 306.734375 C 426.195312 306.734375 402.613281 330.292969 402.613281 359.355469 C 402.613281 388.421875 426.195312 411.980469 455.289062 411.980469 C 484.378906 411.980469 507.960938 388.421875 507.960938 359.355469 Z M 507.960938 359.355469 "/> +</g> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/assets/logo_big.svg Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="-0.44657375 -0.4920675 0.8931475 0.984135"> + <polyline points="0,0 0,-0.3395675" stroke="#cccccc" stroke-width="0.02" stroke-opacity="1" /> + <polyline points="0,0 -0.29407375,0.16978375" stroke="#cccccc" stroke-width="0.02" stroke-opacity="1" /> + <polyline points="0,0 0.29407375,0.16978375" stroke="#cccccc" stroke-width="0.02" stroke-opacity="1" /> + + <circle cx="0" cy="0" r="0.1" fill="#4063d8" stroke="#333333" stroke-width="0.0075"/> + <circle cx="0" cy="-0.3395675" r="0.1" fill="#389826" stroke="#333333" stroke-width="0.0075"/> + <circle cx="-0.29407375" cy="0.16978375" r="0.1" fill="#cb3c33" stroke="#333333" stroke-width="0.0075"/> + <circle cx="0.29407375" cy="0.16978375" r="0.1" fill="#9558b2" stroke="#333333" stroke-width="0.0075"/> +</svg>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/grids_and_grid_functions.md Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,35 @@ +# Grids and grid functions + +The submodule `Grids` aims to provide types and logic for all types of grids that are useful for implementing summation-by-parts difference methods. It provides an abstract top level type `Grid` which defines a broad interface for how a general grid is supposed to work. Currently only equidistant grids are supported, but the basic structure supports implementations of curvilinear grids, multi-block grids, periodic grids and much more. + +The module also has functionality for creating and working with grid functions. + +## Interface for grids +All grids are expected to work as a grid function for the coordinate function, and thus implements Julia's Indexing- and Iteration-interfaces. Notably they are *not* abstract arrays because that inteface is too restrictive for the types of grids we wish to implement. + + +## Plotting +Plotting of grids and grid functions is supported through a package extension with Makie.jl. + +For grids we have: +* `plot(::Grid{<:Any,2})` (same as `lines`) +* `lines(::Grid{<:Any,2})` +* `scatter(::Grid{<:Any,2})` + +For 1D grid functions we have: +* `plot(::Grid{<:Any,1}, ::AbstractVector)` (same as `lines`) +* `lines(::Grid{<:Any,1}, ::AbstractVector)` +* `scatter(::Grid{<:Any,1}, ::AbstractVector)` + +For 2D grid functions we have: +* `plot(::Grid{<:Any,2}, ::AbstractArray{<:Any,2})` (constructs a 2d mesh) +* `surface(::Grid{<:Any,2}, ::AbstractArray{<:Any,2})` + +## To write about +<!-- # TODO: --> +* Grid functions + * Basic structure + * Indexing + * Curvilinear + * Multiblock + * Vector valued grid functions
--- a/docs/src/index.md Mon May 23 07:20:27 2022 +0200 +++ b/docs/src/index.md Tue Feb 10 22:41:19 2026 +0100 @@ -1,4 +1,4 @@ -# Sbplib.jl +# Diffinitive.jl ```@contents Depth = 1
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/manifolds_charts_atlases.md Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,41 @@ +# Manifolds, Charts, and Atlases + +To construct grids on more complicated geometries we use manifolds described +by one or more charts. The charts describe a mapping from a logical parameter +space to the geometry that we are interested in. If there are more than one +chart for a given geometry this collection of charts and how they are +connected is described by an atlas. + +We consider a mapping from the logical coordidinates ``\xi \in \Xi`` to the +physical coordinates ``x \in \Omega``. A `Chart` describes the mapping by a +`ParameterSpace` respresenting ``\Xi`` and some mapping object that takes +arguments ``\xi \in \Xi`` and returns coordinates ``x\in\Omega``. The mapping +object can either be a function or some other callable object. + +For the construction of differential and difference operators on a manifold +with a chart the library needs to know the Jacobian, +``\frac{\partial x}{\partial \xi}``, of the mapping as a function of +coordinates in the logical parameter space. Internally, Diffinitive.jl uses a +local Jacobian function, `Grids.jacobian(f, ξ)`. For geometry objects provided +by the library this function should have fast and efficient implementations. +If you are creating your own mapping functions you must implement +`Grids.jacobian` for your function or type, for example + +```julia +f(x) = 2x +Grids.jacobian(::typeof(f), x) = fill(2, length(x)) +``` + +```julia +struct F end +(::F)(x) = 2x +Grids.jacobian(::F, x) = fill(2,length(x)) +``` + +You can also provide a fallback function using one of the many automatic +differentiation packages, for example + +```julia +using ForwardDiff +Grids.jacobian(f,x) = ForwardDiff.jacobian(f,x) +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/matrix_and_tensor_representations.md Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,5 @@ +# Matrix and tensor representations + +Sparse matrix and sparse tensor representations of lazy tensors can be constructed by loading [Tokens.jl](http://) and one of SparseArrays.jl or [SparseArrayKit.jl](http://). Through package extensions the following methods `sparse(::LazyTensor)` and `SparseArray(::LazyTensor)` are provided. + +<!-- TODO figure out how to add the docstrings here --/>
--- a/docs/src/operator_file_format.md Mon May 23 07:20:27 2022 +0200 +++ b/docs/src/operator_file_format.md Tue Feb 10 22:41:19 2026 +0100 @@ -1,6 +1,6 @@ # Operator file format -The intention is that Sbplib.jl should be a general and extensible framework +The intention is that Diffinitive.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.
--- a/docs/src/submodules/diff_ops.md Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# 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 -```
--- a/docs/src/submodules/static_dicts.md Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# 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 -```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/DiffinitiveMakieExt.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,88 @@ +module DiffinitiveMakieExt + +using Diffinitive.Grids +using Makie +using StaticArrays + + +function verticies_and_faces_and_values(g::Grid{<:Any,2}, gf::AbstractArray{<:Any, 2}) + ps = map(Tuple, g)[:] + values = gf[:] + faces = Vector{NTuple{3,Int}}() + + n,m = size(g) + Li = LinearIndices((1:n, 1:m)) + for i ∈ 1:n-1, j = 1:m-1 + + # Add point in the middle of the patch to preserve symmetries + push!(ps, Tuple((g[i,j] + g[i+1,j] + g[i+1,j+1] + g[i,j+1])/4)) + push!(values, (gf[i,j] + gf[i+1,j] + gf[i+1,j+1] + gf[i,j+1])/4) + + push!(faces, (Li[i,j], Li[i+1,j], length(ps))) + push!(faces, (Li[i+1,j], Li[i+1,j+1], length(ps))) + push!(faces, (Li[i+1,j+1], Li[i,j+1], length(ps))) + push!(faces, (Li[i,j+1], Li[i,j], length(ps))) + end + + verticies = permutedims(reinterpret(reshape,eltype(eltype(ps)), ps)) + faces = permutedims(reinterpret(reshape,Int, faces)) + + return verticies, faces, values +end + + +## Grids + +Makie.convert_arguments(::Type{<:Scatter}, g::Grid) = (reshape(map(Point,g),:),) +function Makie.convert_arguments(::Type{<:Lines}, g::Grid{<:AbstractVector}) + M = collect(g) + + function cat_with_NaN(a,b) + vcat(a,[@SVector fill(NaN, coordinate_size(g))],b) + end + + xlines = reduce(cat_with_NaN, eachrow(M)) + ylines = reduce(cat_with_NaN, eachcol(M)) + + return (cat_with_NaN(xlines,ylines),) +end + +Makie.plot!(plot::Plot(Grid)) = lines!(plot, plot.attributes, plot[1]) + + +## Grid functions + +### 1D +function Makie.convert_arguments(::Type{<:Lines}, g::Grid{<:Any,1}, gf::AbstractArray{<:Any, 1}) + (collect(g), gf) +end + +function Makie.convert_arguments(::Type{<:Scatter}, g::Grid{<:Any,1}, gf::AbstractArray{<:Any, 1}) + (collect(g), gf) +end + +Makie.plot!(plot::Plot(Grid{<:Any,1}, AbstractArray{<:Any,1})) = lines!(plot, plot.attributes, plot[1], plot[2]) + +### 2D +function Makie.convert_arguments(::Type{<:Surface}, g::Grid{<:Any,2}, gf::AbstractArray{<:Any, 2}) + (getindex.(g,1), getindex.(g,2), gf) +end + +function Makie.plot!(plot::Plot(Grid{<:Any,2},AbstractArray{<:Any, 2})) + r = @lift verticies_and_faces_and_values($(plot[1]), $(plot[2])) + v,f,c = (@lift $r[1]), (@lift $r[2]), (@lift $r[3]) + mesh!(plot, plot.attributes, v, f; + color=c, + shading = NoShading, + ) +end +# TBD: Can we define `mesh` instead of the above function and then forward plot! to that? + +function Makie.convert_arguments(::Type{<:Scatter}, g::Grid{<:Any,2}, gf::AbstractArray{<:Any, 2}) + ps = map(g,gf) do (x,y), z + @SVector[x,y,z] + end + (reshape(ps,:),) +end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/DiffinitivePlotsExt.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,60 @@ +module DiffinitivePlotsExt + +using Diffinitive.Grids +using Plots + +@recipe f(::Type{<:Grid}, g::Grid) = map(Tuple,g)[:] + +@recipe function f(c::Chart{2,<:Rectangle}, n=5, m=n; draw_border=true, bordercolor=1) + Ξ = parameterspace(c) + ξs = range(limits(Ξ,1)..., n) + ηs = range(limits(Ξ,2)..., m) + + label := false + seriescolor --> 2 + for ξ ∈ ξs + @series adapted_curve_grid(η->c((ξ,η)),limits(Ξ,1)) + end + + for η ∈ ηs + @series adapted_curve_grid(ξ->c((ξ,η)),limits(Ξ,2)) + end + + if ~draw_border + return + end + + for ξ ∈ limits(Ξ,1) + @series begin + linewidth --> 3 + seriescolor := bordercolor + adapted_curve_grid(η->c((ξ,η)),limits(Ξ,1)) + end + end + + for η ∈ limits(Ξ,2) + @series begin + linewidth --> 3 + seriescolor := bordercolor + adapted_curve_grid(ξ->c((ξ,η)),limits(Ξ,2)) + end + end +end + +function adapted_curve_grid(g, minmax) + t1, _ = PlotUtils.adapted_grid(t->g(t)[1], minmax) + t2, _ = PlotUtils.adapted_grid(t->g(t)[2], minmax) + + ts = sort(vcat(t1,t2)) + + x = map(ts) do t + g(t)[1] + end + y = map(ts) do t + g(t)[2] + end + + return x, y +end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/DiffinitiveSparseArrayKitExt.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,22 @@ +module DiffinitiveSparseArrayKitExt + +using Diffinitive +using Diffinitive.LazyTensors + +using SparseArrayKit +using Tokens + +""" + SparseArray(t::LazyTensor) + +The sparse tensor representation of `t` with range dimensions to the left and +domain dimensions to the right. If `L` is a `LazyTensor` with range and +domain dimension 2 and `v` a 2-tensor, then `A = SparseArray(t)` is +constructed so that `∑ₖ∑ₗA[i,j,k,l]*v[k,l] == L*v`. +""" +function SparseArrayKit.SparseArray(t::LazyTensor) + v = ArrayToken(:v, domain_size(t)...) + return Tokens._to_tensor(t*v, range_size(t), domain_size(t)) +end + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/DiffinitiveSparseArraysExt.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,25 @@ +module DiffinitiveSparseArraysExt + +using Diffinitive +using Diffinitive.LazyTensors + +using SparseArrays +using Tokens + +""" + sparse(t::LazyTensor) + +The sparse matrix representation of `t`. + +If `L` is a `LazyTensor` and `v` a tensor, then `A = sparse(L)` is constructed +so that `A*reshape(v,:) == reshape(L*v,:)`. +""" +function SparseArrays.sparse(t::LazyTensor) + v = ArrayToken(:v, prod(domain_size(t))) + + v̄ = reshape(v,domain_size(t)...) + tv = reshape(t*v̄, :) + return Tokens._to_matrix(tv, prod(range_size(t)), prod(domain_size(t))) +end + +end
--- a/plotDerivative.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -g = sbp.Grid.EquidistantGrid((200,), (0.0,), (2pi,)) -op =sbp.readOperator("d2_4th.txt","h_4th.txt") -Laplace = sbp.Laplace(g,1.0,op) - -init(x) = cos(x) -v = sbp.Grid.evalOn(g,init) -u = zeros(length(v)) - -sbp.apply!(Laplace,u,v) - -@show u -sbp.Grid.plotgridfunction(g,u)
--- a/plotDerivative2d.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -include("sbpPlot.jl") - -g = sbp.Grid.EquidistantGrid((100,75), (0.0, 0.0), (2pi, 3/2*pi)) -op = sbp.readOperator("d2_4th.txt","h_4th.txt") -Laplace = sbp.Laplace(g, 1.0, op) - -init(x,y) = sin(x) + cos(y) -v = sbp.Grid.evalOn(g,init) -u = zero(v) - -sbp.apply!(Laplace,u,v) - -#@show u -#@show u'*u - -plotgridfunction(g,u)
--- a/sbp.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -module sbp - -using Sbplib.Grids -using Sbplib.RegionIndices -using Sbplib.SbpOperators -using Sbplib.DiffOps - -include("TimeStepper.jl") -end # module
--- a/sbpPlot.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -include("sbp.jl") -using Makie -import .sbp.Grid -function plotgridfunction(grid::sbp.Grid.EquidistantGrid, gridfunction::AbstractArray) - if sbp.Grid.dimension(grid) == 1 - plot(sbp.Grid.pointsalongdim(grid,1), gridfunction) - elseif sbp.Grid.dimension(grid) == 2 - scene = surface(sbp.Grid.pointsalongdim(grid,1),sbp.Grid.pointsalongdim(grid,2), gridfunction) - else - error(string("Plot not implemented for dimension ", string(dimension(grid)))) - end -end
--- a/src/DiffOps/DiffOps.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -module DiffOps - -using Sbplib.RegionIndices -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors - -""" - DiffOp - -Supertype of differential operator discretisations. -The action of the DiffOp is defined in the method - apply(D::DiffOp, v::AbstractVector, I...) -""" -abstract type DiffOp end - -function apply end - -function matrixRepresentation(D::DiffOp) - error("not implemented") -end - -abstract type DiffOpCartesian{Dim} <: DiffOp end - -# DiffOp must have a grid of dimension Dim!!! -function apply!(D::DiffOpCartesian{Dim}, u::AbstractArray{T,Dim}, v::AbstractArray{T,Dim}) where {T,Dim} - for I ∈ eachindex(D.grid) - u[I] = apply(D, v, I) - end - - return nothing -end -export apply! - -function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T - apply_region!(D, u, v, Lower, Lower) - apply_region!(D, u, v, Lower, Interior) - apply_region!(D, u, v, Lower, Upper) - apply_region!(D, u, v, Interior, Lower) - apply_region!(D, u, v, Interior, Interior) - apply_region!(D, u, v, Interior, Upper) - apply_region!(D, u, v, Upper, Lower) - apply_region!(D, u, v, Upper, Interior) - apply_region!(D, u, v, Upper, Upper) - return nothing -end - -# Maybe this should be split according to b3fbef345810 after all?! Seems like it makes performance more predictable -function apply_region!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T - for I ∈ regionindices(D.grid.size, closuresize(D.op), (r1,r2)) - @inbounds indextuple = (Index{r1}(I[1]), Index{r2}(I[2])) - @inbounds u[I] = apply(D, v, indextuple) - end - return nothing -end -export apply_region! - -function apply_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}) where T - apply_region_tiled!(D, u, v, Lower, Lower) - apply_region_tiled!(D, u, v, Lower, Interior) - apply_region_tiled!(D, u, v, Lower, Upper) - apply_region_tiled!(D, u, v, Interior, Lower) - apply_region_tiled!(D, u, v, Interior, Interior) - apply_region_tiled!(D, u, v, Interior, Upper) - apply_region_tiled!(D, u, v, Upper, Lower) - apply_region_tiled!(D, u, v, Upper, Interior) - apply_region_tiled!(D, u, v, Upper, Upper) - return nothing -end - -using TiledIteration -function apply_region_tiled!(D::DiffOpCartesian{2}, u::AbstractArray{T,2}, v::AbstractArray{T,2}, r1::Type{<:Region}, r2::Type{<:Region}) where T - ri = regionindices(D.grid.size, closuresize(D.op), (r1,r2)) - # TODO: Pass Tilesize to function - for tileaxs ∈ TileIterator(axes(ri), padded_tilesize(T, (5,5), 2)) - for j ∈ tileaxs[2], i ∈ tileaxs[1] - I = ri[i,j] - u[I] = apply(D, v, (Index{r1}(I[1]), Index{r2}(I[2]))) - end - end - return nothing -end -export apply_region_tiled! - -function apply(D::DiffOp, v::AbstractVector)::AbstractVector - u = zeros(eltype(v), size(v)) - apply!(D,v,u) - return u -end - -# TODO: This conflicts with LazyTensors. Shouldn't DiffOps be LazyTensorOperators and use that apply? -# export apply - - -""" - BoundaryCondition -A BoundaryCondition should implement the method - sat(::DiffOp, v::AbstractArray, data::AbstractArray, ...) -""" -abstract type BoundaryCondition end - - -end # module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Diffinitive.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,22 @@ +module Diffinitive + +include("RegionIndices/RegionIndices.jl") +include("LazyTensors/LazyTensors.jl") +include("Grids/Grids.jl") +include("SbpOperators/SbpOperators.jl") + +export RegionIndices +export LazyTensors +export Grids +export SbpOperators + + +# Aqua.jl fixes +using StaticArrays +using .LazyTensors +Base.:+(a::StaticArray, b::LazyArray) = a +̃ b +Base.:+(a::LazyArray, b::StaticArray) = a +̃ b +Base.:-(a::StaticArray, b::LazyArray) = a -̃ b +Base.:-(a::LazyArray, b::StaticArray) = a -̃ b + +end
--- a/src/Grids/AbstractGrid.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -""" - AbstractGrid - -Should implement - dimension(grid::AbstractGrid) - points(grid::AbstractGrid) - -""" -abstract type AbstractGrid end -export AbstractGrid -function dimension end -function points end -export dimension, points - -""" - evalOn(g::AbstractGrid, f::Function) - -Evaluate function f on the grid g -""" -function evalOn(g::AbstractGrid, f::Function) - F(x) = f(x...) - return F.(points(g)) -end -export evalOn
--- a/src/Grids/EquidistantGrid.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,183 +0,0 @@ -export EquidistantGrid -export spacing -export inverse_spacing -export restrict -export boundary_identifiers -export boundary_grid -export refine -export coarsen - -""" - EquidistantGrid{Dim,T<:Real} <: AbstractGrid - -`Dim`-dimensional equidistant grid with coordinates of type `T`. -""" -struct EquidistantGrid{Dim,T<:Real} <: AbstractGrid - size::NTuple{Dim, Int} - limit_lower::NTuple{Dim, T} - limit_upper::NTuple{Dim, T} - - function EquidistantGrid{Dim,T}(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) where {Dim,T} - if any(size .<= 0) - throw(DomainError("all components of size must be postive")) - end - if any(limit_upper.-limit_lower .<= 0) - throw(DomainError("all side lengths must be postive")) - end - return new{Dim,T}(size, limit_lower, limit_upper) - end -end - -""" - EquidistantGrid(size, limit_lower, limit_upper) - -Construct an equidistant grid with corners at the coordinates `limit_lower` and -`limit_upper`. - -The length of the domain sides are given by the components of -`limit_upper-limit_lower`. E.g for a 2D grid with `limit_lower=(-1,0)` and `limit_upper=(1,2)` the domain is defined -as `(-1,1)x(0,2)`. The side lengths of the grid are not allowed to be negative. - -The number of equidistantly spaced points in each coordinate direction are given -by the tuple `size`. -""" -function EquidistantGrid(size, limit_lower, limit_upper) - return EquidistantGrid{length(size), eltype(limit_lower)}(size, limit_lower, limit_upper) -end - -""" - EquidistantGrid{T}() - -Constructs a 0-dimensional grid. -""" -EquidistantGrid{T}() where T = EquidistantGrid{0,T}((),(),()) # Convenience constructor for 0-dim grid - -""" - EquidistantGrid(size::Int, limit_lower::T, limit_upper::T) - -Convenience constructor for 1D grids. -""" -function EquidistantGrid(size::Int, limit_lower::T, limit_upper::T) where T - return EquidistantGrid((size,),(limit_lower,),(limit_upper,)) -end - -Base.eltype(grid::EquidistantGrid{Dim,T}) where {Dim,T} = T - -Base.eachindex(grid::EquidistantGrid) = CartesianIndices(grid.size) - -Base.size(g::EquidistantGrid) = g.size - -""" - dimension(grid::EquidistantGrid) - -The dimension of the grid. -""" -dimension(grid::EquidistantGrid{Dim}) where Dim = Dim - -""" - spacing(grid::EquidistantGrid) - -The spacing between grid points. -""" -spacing(grid::EquidistantGrid) = (grid.limit_upper.-grid.limit_lower)./(grid.size.-1) - -""" - inverse_spacing(grid::EquidistantGrid) - -The reciprocal of the spacing between grid points. -""" -inverse_spacing(grid::EquidistantGrid) = 1 ./ spacing(grid) - -""" - points(grid::EquidistantGrid) - -The point of the grid as an array of tuples with the same dimension as the grid. -The points are stored as [(x1,y1), (x1,y2), … (x1,yn); - (x2,y1), (x2,y2), … (x2,yn); - ⋮ ⋮ ⋮ - (xm,y1), (xm,y2), … (xm,yn)] -""" -function points(grid::EquidistantGrid) - indices = Tuple.(CartesianIndices(grid.size)) - h = spacing(grid) - return broadcast(I -> grid.limit_lower .+ (I.-1).*h, indices) -end - -""" - restrict(::EquidistantGrid, dim) - -Pick out given dimensions from the grid and return a grid for them. -""" -function restrict(grid::EquidistantGrid, dim) - size = grid.size[dim] - limit_lower = grid.limit_lower[dim] - limit_upper = grid.limit_upper[dim] - - return EquidistantGrid(size, limit_lower, limit_upper) -end - -""" - boundary_identifiers(::EquidistantGrid) - -Returns a tuple containing the boundary identifiers for the grid, stored as - (CartesianBoundary(1,Lower), - CartesianBoundary(1,Upper), - CartesianBoundary(2,Lower), - ...) -""" -boundary_identifiers(g::EquidistantGrid) = (((ntuple(i->(CartesianBoundary{i,Lower}(),CartesianBoundary{i,Upper}()),dimension(g)))...)...,) - - -""" - boundary_grid(grid::EquidistantGrid,id::CartesianBoundary) - boundary_grid(::EquidistantGrid{1},::CartesianBoundary{1}) - -Creates the lower-dimensional restriciton of `grid` spanned by the dimensions -orthogonal to the boundary specified by `id`. The boundary grid of a 1-dimensional -grid is a zero-dimensional grid. -""" -function boundary_grid(grid::EquidistantGrid,id::CartesianBoundary) - dims = collect(1:dimension(grid)) - orth_dims = dims[dims .!= dim(id)] - if orth_dims == dims - throw(DomainError("boundary identifier not matching grid")) - end - return restrict(grid,orth_dims) -end -boundary_grid(::EquidistantGrid{1,T},::CartesianBoundary{1}) where T = EquidistantGrid{T}() - - -""" - refine(grid::EquidistantGrid, r::Int) - -Refines `grid` by a factor `r`. The factor is applied to the number of -intervals which is 1 less than the size of the grid. - -See also: [`coarsen`](@ref) -""" -function refine(grid::EquidistantGrid, r::Int) - sz = size(grid) - new_sz = (sz .- 1).*r .+ 1 - return EquidistantGrid{dimension(grid), eltype(grid)}(new_sz, grid.limit_lower, grid.limit_upper) -end - -""" - coarsen(grid::EquidistantGrid, r::Int) - -Coarsens `grid` by a factor `r`. The factor is applied to the number of -intervals which is 1 less than the size of the grid. If the number of -intervals are not divisible by `r` an error is raised. - -See also: [`refine`](@ref) -""" -function coarsen(grid::EquidistantGrid, r::Int) - sz = size(grid) - - if !all(n -> (n % r == 0), sz.-1) - throw(DomainError(r, "Size minus 1 must be divisible by the ratio.")) - end - - new_sz = (sz .- 1).÷r .+ 1 - - return EquidistantGrid{dimension(grid), eltype(grid)}(new_sz, grid.limit_lower, grid.limit_upper) -end
--- a/src/Grids/Grids.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/Grids/Grids.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,19 +1,99 @@ module Grids -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using StaticArrays +using LinearAlgebra + +export ParameterSpace +export HyperBox +export Simplex +export Interval +export Rectangle +export Box +export Triangle +export Tetrahedron -export BoundaryIdentifier, CartesianBoundary +export limits +export unitinterval +export unitsquare +export unitcube +export unithyperbox + +export verticies +export unittriangle +export unittetrahedron +export unitsimplex + +export Chart + +export Atlas +export charts +export connections +export CartesianAtlas +export UnstructuredAtlas + +export parameterspace -abstract type BoundaryIdentifier end -struct CartesianBoundary{Dim, R<:Region} <: BoundaryIdentifier end -dim(::CartesianBoundary{Dim, R}) where {Dim, R} = Dim -region(::CartesianBoundary{Dim, R}) where {Dim, R} = R() +# Grid +export Grid +export coordinate_size +export component_type +export grid_id +export boundary_id +export boundary_indices +export boundary_identifiers +export boundary_grid +export min_spacing +export coarsen +export refine +export eval_on +export componentview +export ArrayComponentView +export normal + +export BoundaryIdentifier +export TensorGridBoundary +export CartesianBoundary +export LowerBoundary +export UpperBoundary + +export TensorGrid +export ZeroDimGrid + +export EquidistantGrid +export inverse_spacing +export spacing +export equidistant_grid -export dim, region +export MultiBlockBoundary + +# MappedGrid +export MappedGrid +export jacobian +export logical_grid +export mapped_grid +export metric_tensor -include("AbstractGrid.jl") -include("EquidistantGrid.jl") +include("parameter_space.jl") +include("grid.jl") +include("multiblockgrids.jl") +include("manifolds.jl") +include("tensor_grid.jl") +include("equidistant_grid.jl") +include("zero_dim_grid.jl") +include("mapped_grid.jl") -# TODO: Rename AbstractGrid to Grid and move definition here. +function __init__() + if !isdefined(Base.Experimental, :register_error_hint) + return + end + + Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs + if exc.f == Grids.jacobian + print(io, "\nThis possibly means that a function used to define a coordinate mapping is missing a method for `Grids.jacobian`.\n") + print(io, "Provide one by for exmple implementing `Grids.jacobian(::$(typeof(exc.args[1])), x) = ...` or `Grids.jacobian(f, x) = ForwardDiff.jacobian(f,x)`") + end + end +end end # module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/equidistant_grid.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,175 @@ +""" + EquidistantGrid{T,R<:AbstractRange{T}} <: Grid{T,1} + +A one-dimensional equidistant grid. Most users are expected to use +[`equidistant_grid`](@ref) for constructing equidistant grids. + +See also: [`equidistant_grid`](@ref) + + +## Note +The type of range used for the points can likely impact performance. +""" +struct EquidistantGrid{T,R<:AbstractRange{T}} <: Grid{T,1} + points::R +end + +# Indexing interface +Base.getindex(g::EquidistantGrid, i::Int) = g.points[i] +Base.eachindex(g::EquidistantGrid) = eachindex(g.points) +Base.firstindex(g::EquidistantGrid) = firstindex(g.points) +Base.lastindex(g::EquidistantGrid) = lastindex(g.points) + +Base.axes(g::EquidistantGrid, d) = axes(g.points, d) + +# Iteration interface +Base.iterate(g::EquidistantGrid) = iterate(g.points) +Base.iterate(g::EquidistantGrid, state) = iterate(g.points, state) + +Base.IteratorSize(::Type{<:EquidistantGrid}) = Base.HasShape{1}() +Base.length(g::EquidistantGrid) = length(g.points) +Base.size(g::EquidistantGrid) = size(g.points) +Base.size(g::EquidistantGrid, d) = size(g.points)[d] + + +""" + spacing(grid::EquidistantGrid) + +The spacing between grid points. +""" +spacing(g::EquidistantGrid) = step(g.points) + + +""" + inverse_spacing(grid::EquidistantGrid) + +The reciprocal of the spacing between grid points. +""" +inverse_spacing(g::EquidistantGrid) = 1/step(g.points) + +min_spacing(g::EquidistantGrid) = spacing(g) + +""" + LowerBoundary <: BoundaryIdentifier + +Boundary identifier for the the lower (left) boundary of a one-dimensional grid. + +See also: [`BoundaryIdentifier`](@ref) +""" +struct LowerBoundary <: BoundaryIdentifier end + +""" + UpperBoundary <: BoundaryIdentifier + +Boundary identifier for the the upper (right) boundary of a one-dimensional grid. + +See also: [`BoundaryIdentifier`](@ref) +""" +struct UpperBoundary <: BoundaryIdentifier end + + +boundary_identifiers(::EquidistantGrid) = (LowerBoundary(), UpperBoundary()) +boundary_grid(g::EquidistantGrid, id::LowerBoundary) = ZeroDimGrid(g[begin]) +boundary_grid(g::EquidistantGrid, id::UpperBoundary) = ZeroDimGrid(g[end]) +boundary_indices(g::EquidistantGrid, id::LowerBoundary) = firstindex(g) +boundary_indices(g::EquidistantGrid, id::UpperBoundary) = lastindex(g) + +""" + refine(g::EquidistantGrid, r::Int) + +The grid where `g` is refined by the factor `r`. The factor is applied to the number of +intervals, i.e., 1 less than the size of `g`. + +See also: [`coarsen`](@ref) +""" +function refine(g::EquidistantGrid, r::Int) + new_sz = (length(g) - 1)*r + 1 + return EquidistantGrid(change_length(g.points, new_sz)) +end + +""" + coarsen(g::EquidistantGrid, r::Int) + +The grid where `g` is coarsened by the factor `r`. The factor is applied to the number of +intervals, i.e., 1 less than the size of `g`. If the number of +intervals are not divisible by `r` an error is raised. + +See also: [`refine`](@ref) +""" +function coarsen(g::EquidistantGrid, r::Int) + if (length(g)-1)%r != 0 + throw(DomainError(r, "Size minus 1 must be divisible by the ratio.")) + end + + new_sz = (length(g) - 1)÷r + 1 + + return EquidistantGrid(change_length(g.points, new_sz)) +end + + +""" + equidistant_grid(limit_lower, limit_upper, dims...) + +Construct an equidistant grid with corners at the coordinates `limit_lower` and +`limit_upper`. + +The length of the domain sides are given by the components of +`limit_upper-limit_lower`. E.g for a 2D grid with `limit_lower=(-1,0)` and +`limit_upper=(1,2)` the domain is defined as `(-1,1)x(0,2)`. The side lengths +of the grid are not allowed to be negative. + +The number of equispaced points in each coordinate direction are given +by the tuple `dims`. + +Note: If `limit_lower` and `limit_upper` are integers and `dims` would allow a +completely integer grid, `equidistant_grid` will still return a floating point +grid. This simplifies the implementation and avoids certain surprise +behaviors. +""" +function equidistant_grid(limit_lower, limit_upper, dims::Vararg{Int}) + if !(length(limit_lower) == length(limit_upper) == length(dims)) + throw(ArgumentError("All arguments must be of the same length")) + end + gs = map(equidistant_grid, limit_lower, limit_upper, dims) + return TensorGrid(gs...) +end + +""" + equidistant_grid(limit_lower::Number, limit_upper::Number, size::Int) + +Constructs a 1D equidistant grid. +""" +function equidistant_grid(limit_lower::Number, limit_upper::Number, size::Int) + if size <= 0 + throw(DomainError("size must be postive")) + end + + if limit_upper-limit_lower <= 0 + throw(DomainError("side length must be postive")) + end + + return EquidistantGrid(range(limit_lower, limit_upper, length=size)) # TBD: Should it use LinRange instead? +end + +equidistant_grid(d::Interval, size::Int) = equidistant_grid(limits(d)..., size) +equidistant_grid(hb::HyperBox, dims::Vararg{Int}) = equidistant_grid(limits(hb)..., dims...) + +function equidistant_grid(c::Chart, dims::Vararg{Int}) + mapped_grid(c, ξ->jacobian(c,ξ), parameterspace(c), dims...) +end + + +CartesianBoundary{D,BID} = TensorGridBoundary{D,BID} # TBD: What should we do about the naming of this boundary? + + +""" + change_length(r::AbstractRange, n) + +Change the length of `r` to `n`, keeping the same start and stop. +""" +function change_length end + +change_length(r::UnitRange, n) = StepRange{Int,Int}(range(r[begin], r[end], n)) +change_length(r::StepRange, n) = StepRange{Int,Int}(range(r[begin], r[end], n)) +change_length(r::StepRangeLen, n) = range(r[begin], r[end], n) +change_length(r::LinRange, n) = LinRange(r[begin], r[end], n)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/grid.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,178 @@ +""" + Grid{T,D} + +A grid with coordinates of type `T`, e.g. `SVector{3,Float64}`, and dimension +`D`. The grid can be embedded in a higher dimension in which case the number +of indices and the number of components of the coordinate vectors will be +different. + +All grids are expected to behave as a grid function for the coordinates. + +`Grids` is top level abstract type for grids. A grid should implement Julia's interfaces for +indexing and iteration. + +## Note + +Importantly a grid does not have to be an `AbstractArray`. The reason is to +allow flexible handling of special types of grids like multi-block grids, or +grids with special indexing. +""" +abstract type Grid{T,D} end + +Base.ndims(::Grid{T,D}) where {T,D} = D +Base.eltype(::Type{<:Grid{T}}) where T = T + +Base.getindex(g::Grid, I::CartesianIndex) = g[Tuple(I)...] + +""" + coordinate_size(g) + +The length of the coordinate vector of `Grid` `g`. +""" +coordinate_size(::Type{<:Grid{T}}) where T = _ncomponents(T) +coordinate_size(g::Grid) = coordinate_size(typeof(g)) # TBD: Name of this function?! + +""" + component_type(gf) + +The type of the components of the elements of `gf`. I.e if `gf` is a vector +valued grid function, `component_view(gf)` is the element type of the vectors +at each grid point. + +# Examples +```julia-repl +julia> component_type([[1,2], [2,3], [3,4]]) +Int64 +``` +""" +component_type(T::Type) = eltype(eltype(T)) +component_type(t) = component_type(typeof(t)) + +""" + componentview(gf, component_index...) + +A view of `gf` with only the components specified by `component_index...`. + +# Examples +```julia-repl +julia> componentview([[1,2], [2,3], [3,4]],2) +3-element ArrayComponentView{Int64, Vector{Int64}, 1, Vector{Vector{Int64}}, Tuple{Int64}}: + 2 + 3 + 4 +``` +""" +componentview(gf, component_index...) = ArrayComponentView(gf, component_index) + +struct ArrayComponentView{CT,T,D,AT <: AbstractArray{T,D}, IT} <: AbstractArray{CT,D} + v::AT + component_index::IT + + function ArrayComponentView(v, component_index) + CT = typeof(first(v)[component_index...]) + return new{CT, eltype(v), ndims(v), typeof(v), typeof(component_index)}(v,component_index) + end +end + +_array_type(v::ArrayComponentView) = _array_type(typeof(v)) +_array_type(::Type{ArrayComponentView{CT,T,D,AT,IT}}) where {CT,T,D,AT,IT} = AT + +Base.size(cv::ArrayComponentView) = size(cv.v) +Base.getindex(cv::ArrayComponentView, i::Int) = cv.v[i][cv.component_index...] +Base.getindex(cv::ArrayComponentView, I::Vararg{Int}) = cv.v[I...][cv.component_index...] +Base.IndexStyle(ACT::Type{<:ArrayComponentView}) = IndexStyle(_array_type(ACT)) + +# TODO: Implement `setindex!`? +# TODO: Implement a more general ComponentView that can handle non-AbstractArrays. + + +""" + min_spacing(g::Grid) + +The smallest distance between any pair of grid points in `g`. +""" +function min_spacing end + +""" + refine(g::Grid, r) + +The grid where `g` is refined by the factor `r`. + +See also: [`coarsen`](@ref). +""" +function refine end + +""" + coarsen(g::Grid, r) + +The grid where `g` is coarsened by the factor `r`. + +See also: [`refine`](@ref). +""" +function coarsen end + +""" + BoundaryIdentifier + +An identifier for a boundary of a grid. +""" +abstract type BoundaryIdentifier end + +""" + boundary_identifiers(g::Grid) + +Identifiers for all the boundaries of `g`. +""" +function boundary_identifiers end + +""" + boundary_grid(g::Grid, id::BoundaryIdentifier) + +The grid for the boundary specified by `id`. +""" +function boundary_grid end +# TBD: Can we implement a version here that accepts multiple ids and grouped boundaries? Maybe we need multiblock stuff? + +""" + boundary_indices(g::Grid, id::BoundaryIdentifier) + +A collection of indices corresponding to the boundary with given id. For grids +with Cartesian indexing these collections will be tuples with elements of type +``Union{Int,Colon}``. + +When implementing this method it is expected that the returned collection can +be used to index grid functions to obtain grid functions on the boundary grid. +""" +function boundary_indices end + +""" + eval_on(g::Grid, f) + +Lazy evaluation of `f` on the grid. `f` can either be on the form `f(x,y,...)` +with each coordinate as an argument, or on the form `f(x̄)` taking a +coordinate vector. + +For concrete array grid functions `map(f,g)` can be used instead. +""" +eval_on(g::Grid, f) = eval_on(g, f, Base.IteratorSize(g)) +function eval_on(g::Grid, f, ::Base.HasShape) + if hasmethod(f, (Any,)) + return LazyTensors.LazyFunctionArray((I...)->f(g[I...]), size(g)) + else + # TBD This branch can be removed if we accept the trade off that we define f with the syntax f((x,y)) instead if we don't want to handle the vector in the body of f. (Add an example in the docs) + # Also see Notes.md + return LazyTensors.LazyFunctionArray((I...)->f(g[I...]...), size(g)) + end +end + +""" + eval_on(g::Grid, f::Number) + +Lazy evaluation of a scalar `f` on the grid. +""" +eval_on(g::Grid, f::Number) = return LazyTensors.LazyConstantArray(f, size(g)) + +_ncomponents(::Type{<:Number}) = 1 +_ncomponents(T::Type{<:SVector}) = length(T) + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/manifolds.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,162 @@ +""" + Chart{D} + +A parametrized description of a manifold or part of a manifold. +""" +struct Chart{D, PST<:ParameterSpace{D}, MT} + mapping::MT + parameterspace::PST +end + +Base.ndims(::Chart{D}) where D = D +parameterspace(c::Chart) = c.parameterspace + +function (c::Chart)(ξ) + if ξ ∉ parameterspace(c) + throw(DomainError(ξ, "chart was called logical coordinates outside the parameterspace. If this was inteded, use the `mapping` field from the Chart struct instead.")) + end + return c.mapping(ξ) +end + +""" + jacobian(c::Chart, ξ) + +The jacobian of the mapping evaluated at `ξ`. This defers to the +implementation of `jacobian` for the mapping itself. If no implementation is +available one can easily be specified for either the mapping function or the +chart itself. +```julia +c = Chart(f, ps) +jacobian(f::typeof(f), ξ) = f′(ξ) +``` +or +```julia +c = Chart(f, ps) +jacobian(c::typeof(c),ξ) = f′(ξ) +``` +which will both allow calling `jacobian(c,ξ)`. +""" +function jacobian(c::Chart, ξ) + if ξ ∉ parameterspace(c) + throw(DomainError(ξ, "jacobian was called with logical coordinates outside the parameterspace of the chart. If this was inteded, use the `mapping` field from the Chart struct instead.")) + end + return jacobian(c.mapping, ξ) +end + +boundary_identifiers(c::Chart) = boundary_identifiers(parameterspace(c)) + + +""" + Atlas + +A collection of charts and their connections. +Should implement methods for `charts` and `connections`. +""" +abstract type Atlas end + +""" + charts(::Atlas) + +The colloction of charts in the atlas. +""" +function charts end + +""" + connections(::Atlas) + +Collection of 2-tuples of multiblock boundary identifiers. +""" +function connections end + + +""" + CartesianAtlas{D,C<:Chart,AT<:AbstractArray{C,D}} <: Atlas + +An atlas where the charts are arranged and connected like an array. +""" +struct CartesianAtlas{D,C<:Chart,AT<:AbstractArray{C,D}} <: Atlas + charts::AT +end + +charts(a::CartesianAtlas) = a.charts + +function connections(a::CartesianAtlas) + c = Tuple{MultiBlockBoundary, MultiBlockBoundary}[] + + for d ∈ 1:ndims(charts(a)) + Is = eachslice(CartesianIndices(charts(a)); dims=d) + for i ∈ 1:length(Is)-1 # For each interface between slices + for jk ∈ eachindex(Is[i]) # For each block in slice + Iᵢⱼₖ = Tuple(Is[i][jk]) + Iᵢ₊₁ⱼₖ = Tuple(Is[i+1][jk]) + push!(c, + ( + MultiBlockBoundary{Iᵢⱼₖ, CartesianBoundary{d,UpperBoundary}}(), + MultiBlockBoundary{Iᵢ₊₁ⱼₖ, CartesianBoundary{d,LowerBoundary}}(), + ) + ) + end + end + end + + return c +end + +""" + boundary_identifiers(a::CartesianAtlas) + +All non-connected boundaries of the charts of `a`. +""" +function boundary_identifiers(a::CartesianAtlas) + bs = MultiBlockBoundary[] + + for d ∈ 1:ndims(charts(a)) + Is = eachslice(CartesianIndices(charts(a)); dims=d) + + for (i,b) ∈ ((1,LowerBoundary),(length(Is),UpperBoundary)) # For first and last slice + for jk ∈ eachindex(Is[i]) # For each block in slice + Iᵢⱼₖ = Tuple(Is[i][jk]) + push!(bs, + MultiBlockBoundary{Iᵢⱼₖ, CartesianBoundary{d,b}}(), + ) + end + end + end + + return bs +end + + +""" + UnstructuredAtlas{C<:Chart, CN<:Tuple{MultiBlockBoundary,MultiBlockBoundary}, ...} <: Atlas + +An atlas with connections determined by a vector `MultiBlockBoundary` pairs. +""" +struct UnstructuredAtlas{C<:Chart, CN<:Tuple{MultiBlockBoundary,MultiBlockBoundary}, CV<:AbstractVector{C}, CNV<:AbstractVector{CN}} <: Atlas + charts::CV + connections::CNV +end + +charts(a::UnstructuredAtlas) = a.charts +connections(a::UnstructuredAtlas) = a.connections + +""" + boundary_identifiers(a::UnstructuredAtlas) + +All non-connected boundaries of the charts of `a`. +""" +function boundary_identifiers(a::UnstructuredAtlas) + bs = MultiBlockBoundary[] + + for (i,c) ∈ enumerate(charts(a)) + for b ∈ boundary_identifiers(c) + mbb = MultiBlockBoundary{i,typeof(b)}() + + if !any(cn->mbb∈cn, connections(a)) + push!(bs, mbb) + end + end + end + + return bs +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/mapped_grid.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,210 @@ +""" + MappedGrid{T,D} <: Grid{T,D} + +A grid defined by a coordinate mapping from a logical grid to some physical +coordinates. The physical coordinates and the Jacobian are stored as grid +functions corresponding to the logical grid. + +See also: [`logical_grid`](@ref), [`jacobian`](@ref), [`metric_tensor`](@ref). +""" +struct MappedGrid{T,D, GT<:Grid{<:Any,D}, CT<:AbstractArray{T,D}, JT<:AbstractArray{<:AbstractMatrix{<:Any}, D}} <: Grid{T,D} + logical_grid::GT + physicalcoordinates::CT + jacobian::JT + + """ + MappedGrid(logical_grid, physicalcoordinates, jacobian) + + A MappedGrid with the given physical coordinates and jacobian. + """ + function MappedGrid(logical_grid::GT, physicalcoordinates::CT, jacobian::JT) where {T,D, GT<:Grid{<:Any,D}, CT<:AbstractArray{T,D}, JT<:AbstractArray{<:AbstractMatrix{<:Any}, D}} + if !(size(logical_grid) == size(physicalcoordinates) == size(jacobian)) + throw(ArgumentError("Sizes must match")) + end + + if size(first(jacobian)) != (length(first(physicalcoordinates)),D) + throw(ArgumentError("The size of the jacobian must match the dimensions of the grid and coordinates")) + end + + return new{T,D,GT,CT,JT}(logical_grid, physicalcoordinates, jacobian) + end +end + +function Base.:(==)(a::MappedGrid, b::MappedGrid) + same_logical_grid = logical_grid(a) == logical_grid(b) + same_coordinates = collect(a) == collect(b) + same_jacobian = jacobian(a) == jacobian(b) + + return same_logical_grid && same_coordinates && same_jacobian +end + +""" + logical_grid(g::MappedGrid) + +The logical grid of `g`. +""" +logical_grid(g::MappedGrid) = g.logical_grid + +""" + jacobian(g::MappedGrid) + +The Jacobian matrix of `g` as a grid function. +""" +jacobian(g::MappedGrid) = g.jacobian + + +# Indexing interface +Base.getindex(g::MappedGrid, I::Vararg{Int}) = g.physicalcoordinates[I...] +Base.eachindex(g::MappedGrid) = eachindex(g.logical_grid) + +Base.firstindex(g::MappedGrid, d) = firstindex(g.logical_grid, d) +Base.lastindex(g::MappedGrid, d) = lastindex(g.logical_grid, d) + +# Iteration interface +Base.iterate(g::MappedGrid) = iterate(g.physicalcoordinates) +Base.iterate(g::MappedGrid, state) = iterate(g.physicalcoordinates, state) + +Base.IteratorSize(::Type{<:MappedGrid{<:Any, D}}) where D = Base.HasShape{D}() +Base.length(g::MappedGrid) = length(g.logical_grid) +Base.size(g::MappedGrid) = size(g.logical_grid) +Base.size(g::MappedGrid, d) = size(g.logical_grid, d) + +boundary_identifiers(g::MappedGrid) = boundary_identifiers(g.logical_grid) +boundary_indices(g::MappedGrid, id::TensorGridBoundary) = boundary_indices(g.logical_grid, id) + +function boundary_grid(g::MappedGrid, id::TensorGridBoundary) + b_indices = boundary_indices(g.logical_grid, id) + + # Calculate indices of needed jacobian components + D = ndims(g) + all_indices = SVector{D}(1:D) + free_variable_indices = deleteat(all_indices, grid_id(id)) + jacobian_components = (:, free_variable_indices) + + # Create grid function for boundary grid jacobian + boundary_jacobian = componentview((@view g.jacobian[b_indices]) , jacobian_components...) + boundary_physicalcoordinates = @view g.physicalcoordinates[b_indices] + + return MappedGrid( + boundary_grid(g.logical_grid, id), + boundary_physicalcoordinates, + boundary_jacobian, + ) +end + +""" + mapped_grid(x, J, size...) + +A `MappedGrid` with a default logical grid on the D-dimensional unit hyper +box [0,1]ᴰ. `x` and `J` are functions to be evaluated on the logical grid +and `size` determines the size of the logical grid. +""" +function mapped_grid(x, J, size::Vararg{Int}) + D = length(size) + return mapped_grid(x, J, unithyperbox(D), size...) +end + +""" + mapped_grid(x, J, lg::Grid) + +A `MappedGrid` with logical grid `lg`. Physical coordinates and Jacobian are +determined by the functions `x` and `J`. +""" +function mapped_grid(x, J, lg::Grid) + return MappedGrid( + lg, + map(x,lg), + map(J,lg), + ) +end + +""" + mapped_grid(x, J, ps::ParameterSpace, size...) + +A `MappedGrid` with logical grid `lg`. Physical coordinates and Jacobian are +determined by the functions `x` and `J`. +""" +function mapped_grid(x, J, ps::ParameterSpace, size::Vararg{Int}) + lg = equidistant_grid(ps, size...) + return mapped_grid(x, J, lg) +end + +""" + metric_tensor(g::MappedGrid) + +The metric tensor of `g` as a grid function. +""" +function metric_tensor(g::MappedGrid) + return map(jacobian(g)) do ∂x∂ξ + ∂x∂ξ'*∂x∂ξ + end +end + +function min_spacing(g::MappedGrid{T,1} where T) + n, = size(g) + + ms = Inf + for i ∈ 1:n-1 + ms = min(ms, norm(g[i+1]-g[i])) + end + + return ms +end + +function min_spacing(g::MappedGrid{T,2} where T) + n, m = size(g) + + ms = Inf + for i ∈ 1:n-1, j ∈ 1:m-1 # loop over each cell of the grid + + ms = min( + ms, + norm(g[i+1,j]-g[i,j]), + norm(g[i,j+1]-g[i,j]), + + norm(g[i+1,j]-g[i+1,j+1]), + norm(g[i,j+1]-g[i+1,j+1]), + + norm(g[i+1,j+1]-g[i,j]), + norm(g[i+1,j]-g[i,j+1]), + ) + # NOTE: This could be optimized to avoid checking all interior edges twice. + end + + return ms +end + +""" + normal(g::MappedGrid, boundary) + +The outward pointing normal as a grid function on the corresponding boundary grid. +""" +function normal(g::MappedGrid, boundary) + return map(boundary_indices(g, boundary)) do I + normal(g, boundary, Tuple(I)...) + end +end + +""" + normal(g::MappedGrid, boundary, i...) + +The outward pointing normal to the specified boundary in grid point `i`. +""" +function normal(g::MappedGrid, boundary, i...) + σ = _boundary_sign(component_type(g), boundary) + ∂ξ∂x = inv(jacobian(g)[i...]) + + k = grid_id(boundary) + return σ*∂ξ∂x[k,:]/norm(∂ξ∂x[k,:]) +end + + +function _boundary_sign(T, boundary) + if boundary_id(boundary) == UpperBoundary() + return one(T) + elseif boundary_id(boundary) == LowerBoundary() + return -one(T) + else + throw(ArgumentError("The boundary identifier must be either `LowerBoundary()` or `UpperBoundary()`")) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/multiblockgrids.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,9 @@ +""" + MultiBlockBoundary{N, BID} <: BoundaryIdentifier + +A boundary identifier for a multiblock grids. `N` Specifies which grid and +`BID` which boundary on that grid. +""" +struct MultiBlockBoundary{N, BID} <: BoundaryIdentifier end +grid_id(::MultiBlockBoundary{N, BID}) where {N, BID} = N +boundary_id(::MultiBlockBoundary{N, BID}) where {N, BID} = BID()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/parameter_space.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,216 @@ +""" + ParameterSpace{D} + +A space of parameters of dimension `D`. + +Common parameter spaces are created using functions for unit sized spaces +* [`unitinterval`](@ref) +* [`unitsquare`](@ref) +* [`unitcube`](@ref) +* [`unithyperbox`](@ref) +* [`unittriangle`](@ref) +* [`unittetrahedron`](@ref) +* [`unitsimplex`](@ref) + +See also: [`Interval`](@ref), [`HyperBox`](@ref), +[`Simplex`](@ref). +""" +abstract type ParameterSpace{D} end +Base.ndims(::ParameterSpace{D}) where D = D + +@doc """ + in(x, S::ParameterSpace) + ∈(x, S::ParameterSpace) + +Test if the point `x` is in the parameter space `S`. +""" Base.in(x,::ParameterSpace) + +""" + Interval{T} <: ParameterSpace{1} + +A `ParameterSpace` representing an interval. +""" +struct Interval{T} <: ParameterSpace{1} + a::T + b::T +end + +""" + Interval(a,b) + +An interval with limits `a` and `b`. +""" +function Interval(a,b) + a, b = promote(a, b) + Interval{typeof(a)}(a,b) +end + +""" + limits(i::Interval) + +The limits of the interval. +""" +limits(i::Interval) = (i.a, i.b) + +boundary_identifiers(::Interval) = (LowerBoundary(), UpperBoundary()) + +Base.in(x, i::Interval) = i.a <= x <= i.b + +""" + unitinterval(T=Float64) + +The interval ``(0,1)``. +""" +unitinterval(T=Float64) = Interval(zero(T), one(T)) + + +""" + HyperBox{T,D} <: ParameterSpace{D} + +A `ParameterSpace` representing a hyper box. +""" +struct HyperBox{T,D} <: ParameterSpace{D} + a::SVector{D,T} + b::SVector{D,T} +end + +""" + HyperBox(a,b) + +A `HyperBox` with lower limits `a` and upper limits `b` for each dimension. +""" +function HyperBox(a,b) + ET = promote_type(eltype(a),eltype(b)) + T = SVector{length(a),ET} + HyperBox(convert(T,a), convert(T,b)) +end + +Rectangle{T} = HyperBox{T,2} +Box{T} = HyperBox{T,3} + +""" + limits(box::HyperBox, d) + +Limits of `box` along dimension `d`. +""" +limits(box::HyperBox, d) = (box.a[d], box.b[d]) + +""" + limits(box::HyperBox) + +The lower and upper limits of `box` as tuples. +""" +limits(box::HyperBox) = (box.a, box.b) + +function boundary_identifiers(box::HyperBox) + mapreduce(vcat, 1:ndims(box)) do d + [ + CartesianBoundary{d, LowerBoundary}(), + CartesianBoundary{d, UpperBoundary}(), + ] + end +end + +function Base.in(x, box::HyperBox) + return all(eachindex(x)) do i + box.a[i] <= x[i] <= box.b[i] + end +end + +""" + unitsquare(T=Float64) + +The square limited by 0 and 1 in each dimension. +""" +unitsquare(T=Float64) = unithyperbox(T,2) + +""" + unitcube(T=Float64) + +The cube limited by 0 and 1 in each dimension. +""" +unitcube(T=Float64) = unithyperbox(T,3) + +""" + unithyperbox(T=Float64, D) + +The hypercube limited by 0 and 1 in each dimension. +""" +unithyperbox(T, D) = HyperBox((@SVector zeros(T,D)), (@SVector ones(T,D))) +unithyperbox(D) = unithyperbox(Float64,D) + + +""" + Simplex{T,D,NV} <: ParameterSpace{D} + +A `ParameterSpace` representing a simplex. +""" +struct Simplex{T,D,NV} <: ParameterSpace{D} + verticies::NTuple{NV,SVector{D,T}} + + Simplex(verticies::Tuple{SVector{D,T}, Vararg{SVector{D,T},N}}) where {T,D,N} = new{T,D,N+1}(verticies) + Simplex(::Tuple{}) = throw(ArgumentError("Must provide at least one vertex.")) +end + +""" + Simplex(verticies...) + +A simplex with the given verticies. +""" +function Simplex(verticies::Vararg{AbstractArray}) + ET = mapreduce(eltype,promote_type,verticies) + T = SVector{length(verticies[1]),ET} + + return Simplex(Tuple(convert(T,v) for v ∈ verticies)) +end + +function Base.in(x, s::Simplex) + v₁ = s.verticies[1] + V = map(s.verticies) do v + v - v₁ + end + + A = hcat(V[2:end]...) # Matrix with edge vectors as columns + λ = A \ (x - v₁) + + λ_full = (1 - sum(λ), λ...) # Full barycentric coordinates + + return all(λᵢ -> zero(λᵢ) ≤ λᵢ ≤ one(λᵢ), λ_full) +end + +""" + verticies(s::Simplex) + +Verticies of `s`. +""" +verticies(s::Simplex) = s.verticies + +Triangle{T} = Simplex{T,2} +Tetrahedron{T} = Simplex{T,3} + +""" + unittriangle(T=Float64) + +The simplex with verticies ``(0,0)``, ``(1,0)``, and ``(0,1)``. +""" +unittriangle(T=Float64) = unitsimplex(T,2) + +""" + unittetrahedron(T=Float64) + +The simplex with verticies ``(0,0,0)``, ``(1,0,0)``, ``(0,1,0)``, and ``(0,0,1)``. +""" +unittetrahedron(T=Float64) = unitsimplex(T,3) + +""" + unitsimplex(T=Float64,D) + +The unit simplex in dimension `D` with verticies ``(0,0,0,...)``, ``(1,0,0,...)``, ``(0,1,0,...)``, ``(0,0,1,...)``... +""" +function unitsimplex(T,D) + z = @SVector zeros(T,D) + unitelement = one(eltype(z)) + verticies = ntuple(i->setindex(z, unitelement, i), D) + return Simplex((z,verticies...)) +end +unitsimplex(D) = unitsimplex(Float64, D)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/tensor_grid.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,146 @@ +""" + TensorGrid{T,D} <: Grid{T,D} + +A grid constructed as the tensor product of other grids. + +Currently only supports grids with the `HasShape`-trait. +""" +struct TensorGrid{T,D,GT<:NTuple{N,Grid} where N} <: Grid{T,D} + grids::GT + + function TensorGrid(gs...) + T = mapreduce(eltype, combined_coordinate_vector_type, gs) + D = sum(ndims, gs) + + return new{T,D,typeof(gs)}(gs) + end +end + +# Indexing interface +function Base.getindex(g::TensorGrid, I::Vararg{Int}) + szs = ndims.(g.grids) + + Is = LazyTensors.split_tuple(I, szs) + ps = map((g,I)->SVector(g[I...]), g.grids, Is) + + return vcat(ps...) +end + +function Base.eachindex(g::TensorGrid) + szs = LazyTensors.concatenate_tuples(size.(g.grids)...) + return CartesianIndices(szs) +end + +function Base.axes(g::TensorGrid, d) + i, ld = grid_and_local_dim_index(ndims.(g.grids), d) + return axes(g.grids[i], ld) +end + +# Iteration interface +Base.iterate(g::TensorGrid) = iterate(Iterators.product(g.grids...)) |> _iterate_combine_coords +Base.iterate(g::TensorGrid, state) = iterate(Iterators.product(g.grids...), state) |> _iterate_combine_coords +_iterate_combine_coords(::Nothing) = nothing +_iterate_combine_coords((next,state)) = combine_coordinates(next...), state + +Base.IteratorSize(::Type{<:TensorGrid{<:Any, D}}) where D = Base.HasShape{D}() +Base.length(g::TensorGrid) = prod(length, g.grids) +Base.size(g::TensorGrid) = LazyTensors.concatenate_tuples(size.(g.grids)...) +Base.size(g::TensorGrid, d) = size(g)[d] + +function spacing(g::TensorGrid) + relevant_grids = filter(g->!isa(g,ZeroDimGrid),g.grids) + return spacing.(relevant_grids) +end + +function min_spacing(g::TensorGrid) + relevant_grids = filter(g->!isa(g,ZeroDimGrid),g.grids) + d = min_spacing.(relevant_grids) + return minimum(d) +end + +refine(g::TensorGrid, r::Int) = mapreduce(g->refine(g,r), TensorGrid, g.grids) +coarsen(g::TensorGrid, r::Int) = mapreduce(g->coarsen(g,r), TensorGrid, g.grids) + +""" + TensorGridBoundary{N, BID} <: BoundaryIdentifier + +A boundary identifier for a tensor grid. `N` Specifies which grid in the +tensor product and `BID` which boundary on that grid. +""" +struct TensorGridBoundary{N, BID} <: BoundaryIdentifier end +grid_id(::TensorGridBoundary{N, BID}) where {N, BID} = N +boundary_id(::TensorGridBoundary{N, BID}) where {N, BID} = BID() + +""" + boundary_identifiers(g::TensorGrid) + +Returns a tuple containing the boundary identifiers of `g`. +""" +function boundary_identifiers(g::TensorGrid) + per_grid = map(eachindex(g.grids)) do i + return map(bid -> TensorGridBoundary{i, typeof(bid)}(), boundary_identifiers(g.grids[i])) + end + return LazyTensors.concatenate_tuples(per_grid...) +end + +""" + boundary_grid(g::TensorGrid, id::TensorGridBoundary) + +The grid for the boundary of `g` specified by `id`. +""" +function boundary_grid(g::TensorGrid, id::TensorGridBoundary) + local_boundary_grid = boundary_grid(g.grids[grid_id(id)], boundary_id(id)) + new_grids = Base.setindex(g.grids, local_boundary_grid, grid_id(id)) + return TensorGrid(new_grids...) +end + + +function boundary_indices(g::TensorGrid{<:Any, 1}, id::TensorGridBoundary) + return boundary_indices(g.grids[grid_id(id)], boundary_id(id)) +end +function boundary_indices(g::TensorGrid, id::TensorGridBoundary) + local_b_ind = boundary_indices(g.grids[grid_id(id)], boundary_id(id)) + + b_ind = Base.setindex(map(eachindex, g.grids), local_b_ind, grid_id(id)) + + return view(eachindex(g), b_ind...) +end + +function combined_coordinate_vector_type(coordinate_types...) + combined_coord_length = mapreduce(_ncomponents, +, coordinate_types) + combined_coord_type = mapreduce(eltype, promote_type, coordinate_types) + + if combined_coord_length == 1 + return combined_coord_type + else + return SVector{combined_coord_length, combined_coord_type} + end +end + +function combine_coordinates(coords...) + return mapreduce(SVector, vcat, coords) +end + +""" + grid_and_local_dim_index(nds, d) + +Given a tuple of number of dimensions `nds`, and a global dimension index `d`, +calculate which grid index, and local dimension, `d` corresponds to. + +`nds` would come from broadcasting `ndims` on the grids tuple of a +`TensorGrid`. If you are interested in a dimension `d` of a tensor grid `g` +```julia +gi, ldi = grid_and_local_dim_index(ndims.(g.grids), d) +``` +tells you which grid it belongs to (`gi`) and which index it is at within that +grid (`ldi`). +""" +function grid_and_local_dim_index(nds, d) + I = findfirst(>=(d), cumsum(nds)) + + if I == 1 + return (1, d) + else + return (I, d-cumsum(nds)[I-1]) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/zero_dim_grid.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,28 @@ +""" + ZeroDimGrid{T} <: Grid{T,0} + +A zero dimensional grid consisting of a single point. +""" +struct ZeroDimGrid{T} <: Grid{T,0} + point::T +end + +# Indexing interface +Base.getindex(g::ZeroDimGrid) = g.point +Base.eachindex(g::ZeroDimGrid) = CartesianIndices(()) + +# Iteration interface +Base.iterate(g::ZeroDimGrid) = (g.point, nothing) +Base.iterate(g::ZeroDimGrid, ::Any) = nothing + +Base.IteratorSize(::Type{<:ZeroDimGrid}) = Base.HasShape{0}() +Base.length(g::ZeroDimGrid) = 1 +Base.size(g::ZeroDimGrid) = () + + +refine(g::ZeroDimGrid, ::Int) = g +coarsen(g::ZeroDimGrid, ::Int) = g + +boundary_identifiers(g::ZeroDimGrid) = () +boundary_grid(g::ZeroDimGrid, ::Any) = throw(ArgumentError("ZeroDimGrid has no boundaries")) +boundary_indices(g::ZeroDimGrid, ::Any) = throw(ArgumentError("ZeroDimGrid has no boundaries"))
--- a/src/LazyTensors/LazyTensors.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/LazyTensors.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,8 +1,16 @@ module LazyTensors +export LazyTensor +export apply +export apply_transpose +export range_dim, domain_dim +export range_size, domain_size + export TensorApplication export TensorTranspose export TensorComposition +export TensorNegation +export TensorSum export IdentityTensor export ScalingTensor export DiagonalTensor @@ -13,6 +21,10 @@ export DomainSizeMismatch export RangeSizeMismatch +export LazyArray +export LazyFunctionArray +export +̃, -̃, *̃, /̃ + include("lazy_tensor.jl") include("tensor_types.jl") include("lazy_array.jl") @@ -25,11 +37,17 @@ Base.:*(a::LazyTensor, args::Union{LazyTensor, AbstractArray}...) = foldr(*,(a,args...)) # Addition and subtraction of lazy tensors -Base.:+(s::LazyTensor, t::LazyTensor) = ElementwiseTensorOperation{:+}(s,t) -Base.:-(s::LazyTensor, t::LazyTensor) = ElementwiseTensorOperation{:-}(s,t) +Base.:+(ts::LazyTensor...) = TensorSum(ts...) +Base.:-(t::LazyTensor) = TensorNegation(t) +Base.:-(s::LazyTensor, t::LazyTensor) = s + (-t) +## Specializations to flatten the nesting of tensors. This helps Julia during inference. +Base.:+(t::TensorSum, s::TensorSum) = TensorSum(t.tms..., s.tms...) +Base.:+(t::TensorSum, s::LazyTensor) = TensorSum(t.tms..., s) +Base.:+(t::LazyTensor, s::TensorSum) = TensorSum(t, s.tms...) # Composing lazy tensors Base.:∘(s::LazyTensor, t::LazyTensor) = TensorComposition(s,t) +Base.:∘(s::TensorComposition, t::LazyTensor) = s.t1∘(s.t2∘t) # Outer products of tensors ⊗(a::LazyTensor, b::LazyTensor) = LazyOuterProduct(a,b)
--- a/src/LazyTensors/lazy_array.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/lazy_array.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,12 +1,11 @@ """ LazyArray{T,D} <: AbstractArray{T,D} -Array which is calcualted lazily when indexing. +Array which is calculated lazily when indexing. A subtype of `LazyArray` will use lazy version of `+`, `-`, `*`, `/`. """ abstract type LazyArray{T,D} <: AbstractArray{T,D} end -export LazyArray struct LazyConstantArray{T,D} <: LazyArray{T,D} val::T @@ -25,10 +24,9 @@ f::F size::NTuple{D,Int} end -export LazyFunctionArray function LazyFunctionArray(f::F, size::NTuple{D,Int}) where {F<:Function,D} - T = typeof(f(ones(D)...)) + T = typeof(f(ones(Int, D)...)) return LazyFunctionArray{F,T,D}(f,size) end @@ -36,13 +34,13 @@ function Base.getindex(lfa::LazyFunctionArray{F,T,D}, I::Vararg{Int,D}) where {F,T,D} @boundscheck checkbounds(lfa, I...) - return @inbounds lfa.f(I...) + return @inbounds @inline lfa.f(I...) end """ LazyElementwiseOperation{T,D,Op} <: LazyArray{T,D} -Struct allowing for lazy evaluation of elementwise operations on `AbstractArray`s. +Struct allowing for lazy evaluation of element-wise operations on `AbstractArray`s. A `LazyElementwiseOperation` contains two arrays together with an operation. The operations are carried out when the `LazyElementwiseOperation` is indexed. @@ -93,17 +91,20 @@ -# NOTE: Är det knas att vi har till exempel * istället för .* ?? -# Oklart om det ens går att lösa.. +# Overload +,-,*,/ for LazyArrays +# Element wise operation for `*` and `/` are not overloaded for due to conflicts with the behavior +# of regular `*` and `/` for AbstractArrays. Use tilde versions instead. Base.@propagate_inbounds Base.:+(a::LazyArray{T,D}, b::LazyArray{T,D}) where {T,D} = a +̃ b +Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::LazyArray{T,D}) where {T,D} = a -̃ b + Base.@propagate_inbounds Base.:+(a::LazyArray{T,D}, b::AbstractArray{T,D}) where {T,D} = a +̃ b -Base.@propagate_inbounds Base.:+(a::AbstractArray{T,D}, b::LazyArray{T,D}) where {T,D} = a +̃ b +Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::AbstractArray{T,D}) where {T,D} = a -̃ b -Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::LazyArray{T,D}) where {T,D} = a -̃ b -Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::AbstractArray{T,D}) where {T,D} = a -̃ b +Base.@propagate_inbounds Base.:+(a::AbstractArray{T,D}, b::LazyArray{T,D}) where {T,D} = a +̃ b Base.@propagate_inbounds Base.:-(a::AbstractArray{T,D}, b::LazyArray{T,D}) where {T,D} = a -̃ b -# Element wise operation for `*` and `\` are not overloaded due to conflicts with the behavior -# of regular `*` and `/` for AbstractArrays. Use tilde versions instead. +Base.@propagate_inbounds Base.:+(a::LazyArray{T,D}, b::T) where {T,D} = a +̃ b +Base.@propagate_inbounds Base.:-(a::LazyArray{T,D}, b::T) where {T,D} = a -̃ b -export +̃, -̃, *̃, /̃ +Base.@propagate_inbounds Base.:+(a::T, b::LazyArray{T,D}) where {T,D} = a +̃ b +Base.@propagate_inbounds Base.:-(a::T, b::LazyArray{T,D}) where {T,D} = a -̃ b
--- a/src/LazyTensors/lazy_tensor.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/lazy_tensor.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,9 +1,3 @@ -export LazyTensor -export apply -export apply_transpose -export range_dim, domain_dim -export range_size, domain_size - """ LazyTensor{T,R,D}
--- a/src/LazyTensors/lazy_tensor_operations.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/lazy_tensor_operations.jl Tue Feb 10 22:41:19 2026 +0100 @@ -5,7 +5,7 @@ Allows the result of a `LazyTensor` applied to a vector to be treated as an `AbstractArray`. With a mapping `m` and a vector `v` the TensorApplication object can be created by `m*v`. -The actual result will be calcualted when indexing into `m*v`. +The actual result will be calculated when indexing into `m*v`. """ struct TensorApplication{T,R,D, TM<:LazyTensor{<:Any,R,D}, AA<:AbstractArray{<:Any,D}} <: LazyArray{T,R} t::TM @@ -52,24 +52,66 @@ domain_size(tmt::TensorTranspose) = range_size(tmt.tm) -struct ElementwiseTensorOperation{Op,T,R,D,T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R} - tm1::T1 - tm2::T2 +""" + TensorNegation{T,R,D,...} <: LazyTensor{T,R,D} + +The negation of a LazyTensor. +""" +struct TensorNegation{T,R,D,TM<:LazyTensor{T,R,D}} <: LazyTensor{T,R,D} + tm::TM +end + +apply(tm::TensorNegation, v, I...) = -apply(tm.tm, v, I...) +apply_transpose(tm::TensorNegation, v, I...) = -apply_transpose(tm.tm, v, I...) + +range_size(tm::TensorNegation) = range_size(tm.tm) +domain_size(tm::TensorNegation) = domain_size(tm.tm) + - function ElementwiseTensorOperation{Op,T,R,D}(tm1::T1,tm2::T2) where {Op,T,R,D, T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} - @boundscheck check_domain_size(tm2, domain_size(tm1)) - @boundscheck check_range_size(tm2, range_size(tm1)) - return new{Op,T,R,D,T1,T2}(tm1,tm2) +""" + TensorSum{T,R,D,...} <: LazyTensor{T,R,D} + +The lazy sum of 2 or more lazy tensors. +""" +struct TensorSum{T,R,D,TT<:NTuple{N, LazyTensor{T,R,D}} where N} <: LazyTensor{T,R,D} + tms::TT + + function TensorSum{T,R,D}(tms::TT) where {T,R,D, TT<:NTuple{N, LazyTensor{T,R,D}} where N} + @boundscheck map(tms) do tm + check_domain_size(tm, domain_size(tms[1])) + check_range_size(tm, range_size(tms[1])) + end + + return new{T,R,D,TT}(tms) end end -ElementwiseTensorOperation{Op}(s,t) where Op = ElementwiseTensorOperation{Op,eltype(s), range_dim(s), domain_dim(s)}(s,t) +""" + TensorSum(ts::Vararg{LazyTensor}) + +The lazy sum of the tensors `ts`. +""" +function TensorSum(ts::Vararg{LazyTensor}) + T = eltype(ts[1]) + R = range_dim(ts[1]) + D = domain_dim(ts[1]) + return TensorSum{T,R,D}(ts) +end -apply(tmBinOp::ElementwiseTensorOperation{:+,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) + apply(tmBinOp.tm2, v, I...) -apply(tmBinOp::ElementwiseTensorOperation{:-,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) - apply(tmBinOp.tm2, v, I...) +function apply(tmBinOp::TensorSum{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} + return sum(tmBinOp.tms) do tm + apply(tm,v,I...) + end +end -range_size(tmBinOp::ElementwiseTensorOperation) = range_size(tmBinOp.tm1) -domain_size(tmBinOp::ElementwiseTensorOperation) = domain_size(tmBinOp.tm1) +function apply_transpose(tmBinOp::TensorSum{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} + return sum(tmBinOp.tms) do tm + apply_transpose(tm,v,I...) + end +end + +range_size(tmBinOp::TensorSum) = range_size(tmBinOp.tms[1]) +domain_size(tmBinOp::TensorSum) = domain_size(tmBinOp.tms[1]) """ @@ -103,7 +145,7 @@ TensorComposition(tm, tmi::IdentityTensor) TensorComposition(tmi::IdentityTensor, tm) -Composes a `Tensormapping` `tm` with an `IdentityTensor` `tmi`, by returning `tm` +Composes a `LazyTensor` `tm` with an `IdentityTensor` `tmi`, by returning `tm` """ function TensorComposition(tm::LazyTensor{T,R,D}, tmi::IdentityTensor{T,D}) where {T,R,D} @boundscheck check_domain_size(tm, range_size(tmi)) @@ -126,7 +168,7 @@ """ InflatedTensor{T,R,D} <: LazyTensor{T,R,D} -An inflated `LazyTensor` with dimensions added before and afer its actual dimensions. +An inflated `LazyTensor` with dimensions added before and after its actual dimensions. """ struct InflatedTensor{T,R,D,D_before,R_middle,D_middle,D_after, TM<:LazyTensor{T,R_middle,D_middle}} <: LazyTensor{T,R,D} before::IdentityTensor{T,D_before} @@ -169,15 +211,15 @@ ) end -InflatedTensor(before::IdentityTensor, tm::LazyTensor{T}) where T = InflatedTensor(before,tm,IdentityTensor{T}()) -InflatedTensor(tm::LazyTensor{T}, after::IdentityTensor) where T = InflatedTensor(IdentityTensor{T}(),tm,after) +InflatedTensor(before::IdentityTensor, tm::LazyTensor) = InflatedTensor(before,tm,IdentityTensor{eltype(tm)}()) +InflatedTensor(tm::LazyTensor, after::IdentityTensor) = InflatedTensor(IdentityTensor{eltype(tm)}(),tm,after) # Resolve ambiguity between the two previous methods -InflatedTensor(I1::IdentityTensor{T}, I2::IdentityTensor{T}) where T = InflatedTensor(I1,I2,IdentityTensor{T}()) +InflatedTensor(I1::IdentityTensor, I2::IdentityTensor) = InflatedTensor(I1,I2,IdentityTensor{promote_type(eltype(I1), eltype(I2))}()) # TODO: Implement some pretty printing in terms of ⊗. E.g InflatedTensor(I(3),B,I(2)) -> I(3)⊗B⊗I(2) function range_size(itm::InflatedTensor) - return flatten_tuple( + return concatenate_tuples( range_size(itm.before), range_size(itm.tm), range_size(itm.after), @@ -185,7 +227,7 @@ end function domain_size(itm::InflatedTensor) - return flatten_tuple( + return concatenate_tuples( domain_size(itm.before), domain_size(itm.tm), domain_size(itm.after), @@ -198,7 +240,7 @@ dim_range = range_dim(itm.tm) dim_after = range_dim(itm.after) - view_index, inner_index = split_index(Val(dim_before), Val(dim_domain), Val(dim_range), Val(dim_after), I...) + view_index, inner_index = split_index(dim_before, dim_domain, dim_range, dim_after, I...) v_inner = view(v, view_index...) return apply(itm.tm, v_inner, inner_index...) @@ -210,7 +252,7 @@ dim_range = range_dim(itm.tm) dim_after = range_dim(itm.after) - view_index, inner_index = split_index(Val(dim_before), Val(dim_range), Val(dim_domain), Val(dim_after), I...) + view_index, inner_index = split_index(dim_before, dim_range, dim_domain, dim_after, I...) v_inner = view(v, view_index...) return apply_transpose(itm.tm, v_inner, inner_index...) @@ -229,7 +271,7 @@ @doc raw""" LazyOuterProduct(tms...) -Creates a `TensorComposition` for the outerproduct of `tms...`. +Creates a `TensorComposition` for the outer product 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 @@ -272,13 +314,36 @@ return itm1∘itm2 end -LazyOuterProduct(t1::IdentityTensor{T}, t2::IdentityTensor{T}) where T = IdentityTensor{T}(t1.size...,t2.size...) +LazyOuterProduct(t1::IdentityTensor, t2::IdentityTensor) = IdentityTensor{promote_type(eltype(t1),eltype(t2))}(t1.size...,t2.size...) LazyOuterProduct(t1::LazyTensor, t2::IdentityTensor) = InflatedTensor(t1, t2) LazyOuterProduct(t1::IdentityTensor, t2::LazyTensor) = InflatedTensor(t1, t2) LazyOuterProduct(tms::Vararg{LazyTensor}) = foldl(LazyOuterProduct, tms) + +""" + inflate(tm::LazyTensor, sz, dir) + +Inflate `tm` such that it gets the size `sz` in all directions except `dir`. +Here `sz[dir]` is ignored and replaced with the range and domains size of +`tm`. + +An example of when this operation is useful is when extending a one +dimensional difference operator `D` to a 2D grid of a certain size. In that +case we could have + +```julia +Dx = inflate(D, (10,10), 1) +Dy = inflate(D, (10,10), 2) +``` +""" +function inflate(tm::LazyTensor, sz, dir) + Is = IdentityTensor{eltype(tm)}.(sz) + parts = Base.setindex(Is, tm, dir) + return foldl(⊗, parts) +end + function check_domain_size(tm::LazyTensor, sz) if domain_size(tm) != sz throw(DomainSizeMismatch(tm,sz))
--- a/src/LazyTensors/tensor_types.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/tensor_types.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,7 +1,7 @@ """ IdentityTensor{T,D} <: LazyTensor{T,D,D} -The lazy identity LazyTensor for a given size. Usefull for building up higher dimensional tensor mappings from lower +The lazy identity LazyTensor for a given size. Useful for building up higher dimensional tensor mappings from lower dimensional ones through outer products. Also used in the Implementation for InflatedTensor. """ struct IdentityTensor{T,D} <: LazyTensor{T,D,D} @@ -91,8 +91,8 @@ """ DenseTensor{T,R,D,...}(A, range_indicies, domain_indicies) -LazyTensor defined by the AbstractArray A. `range_indicies` and `domain_indicies` define which indicies of A should -be considerd the range and domain of the LazyTensor. Each set of indices must be ordered in ascending order. +LazyTensor defined by the AbstractArray A. `range_indicies` and `domain_indicies` define which indices of A should +be considered the range and domain of the LazyTensor. Each set of indices must be ordered in ascending order. For instance, if A is a m x n matrix, and range_size = (1,), domain_size = (2,), then the DenseTensor performs the standard matrix-vector product on vectors of size n.
--- a/src/LazyTensors/tuple_manipulation.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/LazyTensors/tuple_manipulation.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,11 +1,12 @@ """ - split_index(::Val{dim_before}, ::Val{dim_view}, ::Val{dim_index}, ::Val{dim_after}, I...) + split_index(dim_before, dim_view, dim_index, dim_after, I...) Splits the multi-index `I` into two parts. One part which is expected to be used as a view, and one which is expected to be used as an index. -Eg. -``` -split_index(Val(1),Val(3),Val(2),Val(1),(1,2,3,4)) -> (1,:,:,:,4), (2,3) +E.g. +```julia-repl +julia> LazyTensors.split_index(1, 3, 2, 1, (1,2,3,4)...) +((1, Colon(), Colon(), Colon(), 4), (2, 3)) ``` `dim_view` controls how many colons are in the view, and `dim_index` controls @@ -18,59 +19,78 @@ * `length(view_index) == dim_before + dim_view + dim_after` * `length(I_middle) == dim_index` """ -function split_index(::Val{dim_before}, ::Val{dim_view}, ::Val{dim_index}, ::Val{dim_after}, I...) where {dim_before,dim_view, dim_index,dim_after} - I_before, I_middle, I_after = split_tuple(I, Val(dim_before), Val(dim_index)) +function split_index(dim_before, dim_view, dim_index, dim_after, I...) + @inline + I_before, I_middle, I_after = split_tuple(I, (dim_before, dim_index, dim_after)) view_index = (I_before..., ntuple((i)->:, dim_view)..., I_after...) return view_index, I_middle end -# TODO: Can this be replaced by something more elegant while still being type stable? 2020-10-21 -# See: -# https://github.com/JuliaLang/julia/issues/34884 -# https://github.com/JuliaLang/julia/issues/30386 -""" - 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. -""" -function slice_tuple(t,::Val{L},::Val{U}) where {L,U} - return ntuple(i->t[i+L-1], U-L+1) -end """ - split_tuple(t::Tuple{...}, ::Val{M}) where {N,M} + split_tuple(t, szs) + +Split the tuple `t` into a set of tuples of the sizes given in `szs`. +`sum(szs)` should equal `length(t)`. -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,) +```julia-repl +julia> LazyTensors.split_tuple((1,2,3,4,5,6), (3,1,2)) +((1, 2, 3), (4,), (5, 6)) ``` """ -function split_tuple(t::NTuple{N,Any},::Val{M}) where {N,M} - return slice_tuple(t,Val(1), Val(M)), slice_tuple(t,Val(M+1), Val(N)) +function split_tuple(t, szs) + @inline + if length(t) != sum(szs; init=0) + throw(ArgumentError("length(t) must equal sum(szs)")) + end + + rs = sizes_to_ranges(szs) + return map(r->t[r], rs) end -""" - split_tuple(t::Tuple{...},::Val{M},::Val{K}) where {N,M,K} - -Same as `split_tuple(t::NTuple{N},::Val{M})` but splits the tuple in three parts. With the first -two parts having lenght `M` and `K`. -""" -function split_tuple(t::NTuple{N,Any},::Val{M},::Val{K}) where {N,M,K} - p1, tail = split_tuple(t, Val(M)) - p2, p3 = split_tuple(tail, Val(K)) - return p1,p2,p3 +function sizes_to_ranges(szs) + cum_szs = cumsum((0, szs...)) + return ntuple(i->cum_szs[i]+1:cum_szs[i+1], length(szs)) end """ - flatten_tuple(t) + concatenate_tuples(t...) -Takes a nested tuple and flattens the whole structure +Concatenate tuples. +""" +concatenate_tuples(t::Tuple,ts::Vararg{Tuple}) = (t..., concatenate_tuples(ts...)...) +concatenate_tuples(t::Tuple) = t + + +""" + left_pad_tuple(t, val, N) + +Left pad the tuple `t` to length `N` using the value `val`. """ -flatten_tuple(t::NTuple{N, Number} where N) = t -flatten_tuple(t::Tuple) = ((flatten_tuple.(t)...)...,) # simplify? -flatten_tuple(ts::Vararg) = flatten_tuple(ts) +function left_pad_tuple(t, val, N) + if N < length(t) + throw(DomainError(N, "Can't pad tuple of length $(length(t)) to $N elements")) + end + + padding = ntuple(i->val, N-length(t)) + return (padding..., t...) +end + +""" + right_pad_tuple(t, val, N) + +Right pad the tuple `t` to length `N` using the value `val`. +""" +function right_pad_tuple(t, val, N) + if N < length(t) + throw(DomainError(N, "Can't pad tuple of length $(length(t)) to $N elements")) + end + + padding = ntuple(i->val, N-length(t)) + return (t..., padding...) +end +
--- a/src/RegionIndices/RegionIndices.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/RegionIndices/RegionIndices.jl Tue Feb 10 22:41:19 2026 +0100 @@ -13,7 +13,7 @@ Index{R,T}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i) Index{R}(i::T) where {R<:Region,T<:Integer} = new{R,T}(i) Index(i::T, ::Type{R}) where {R<:Region,T<:Integer} = Index{R,T}(i) - Index(t::Tuple{T, DataType}) where {R<:Region,T<:Integer} = Index{t[2],T}(t[1]) # TBD: This is not very specific in what types are allowed in t[2]. Can this be fixed? + Index(t::Tuple{T, DataType}) where T<:Integer = Index{t[2],T}(t[1]) # TBD: This is not very specific in what types are allowed in t[2]. Can this be fixed? end export Index @@ -25,18 +25,17 @@ Index(R::Type{<:Region}, T::Type{<:Integer}) = Index{R,T} IndexTupleType(T::Type{<:Integer},R::NTuple{N, DataType} where N) = Tuple{Index.(R, T)...} -Base.convert(::Type{T}, i::Index{R,T} where R) where T = i.i +Base.convert(::Type{T}, i::Index{R,T} where R) where T <: Integer = i.i Base.convert(::Type{CartesianIndex}, I::NTuple{N,Index} where N) = CartesianIndex(convert.(Int, I)) Base.Int(I::Index) = I.i Base.to_index(I::Index) = Int(I) #How to get this to work for all cases?? -Base.getindex(A::AbstractArray{T,N}, I::NTuple{N,Index}) where {T,N} = A[I...] #Is this ok?? function Index(i::Integer, boundary_width::Integer, dim_size::Integer) return Index{getregion(i,boundary_width,dim_size)}(i) end -IndexTuple(t::Vararg{Tuple{T, DataType}}) where T<:Integer = Index.(t) +IndexTuple(t::Vararg{Tuple{Integer, DataType}}) = Index.(t) export IndexTuple # TODO: Use the values of the region structs, e.g. Lower(), for the region parameter instead of the types.
--- a/src/SbpOperators/SbpOperators.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/SbpOperators.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,16 +1,22 @@ module SbpOperators +# Stencils +export Stencil +export CenteredStencil +export NestedStencil +export CenteredNestedStencil + # Stencil set export StencilSet export read_stencil_set export get_stencil_set export parse_stencil +export parse_nested_stencil export parse_scalar export parse_tuple export sbp_operators_path # Operators -export boundary_quadrature export boundary_restriction export inner_product export inverse_inner_product @@ -19,22 +25,43 @@ export normal_derivative export first_derivative export second_derivative - -using Sbplib.RegionIndices -using Sbplib.LazyTensors -using Sbplib.Grids +export second_derivative_variable +export undivided_skewed04 +export closure_size @enum Parity begin odd = -1 even = 1 end + +# Boundary conditions +export BoundaryCondition +export NeumannCondition +export DirichletCondition +export discretize_data +export boundary_data +export boundary +export sat +export sat_tensors + +# Using +using Diffinitive.RegionIndices +using Diffinitive.LazyTensors +using Diffinitive.Grids + +# Includes include("stencil.jl") include("stencil_set.jl") +include("boundary_conditions/boundary_condition.jl") +include("boundary_conditions/sat.jl") include("volumeops/volume_operator.jl") +include("volumeops/stencil_operator_distinct_closures.jl") include("volumeops/constant_interior_scaling_operator.jl") include("volumeops/derivatives/first_derivative.jl") include("volumeops/derivatives/second_derivative.jl") +include("volumeops/derivatives/second_derivative_variable.jl") +include("volumeops/derivatives/dissipation.jl") include("volumeops/laplace/laplace.jl") include("volumeops/inner_products/inner_product.jl") include("volumeops/inner_products/inverse_inner_product.jl")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/boundary_conditions/boundary_condition.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,62 @@ +""" + BoundaryCondition + +Description of a boundary condition. Implementations describe the kind of +boundary condition, what boundary the condition applies to, and any associated +data. Should implement [`boundary`](@ref) and may implement +[`boundary_data`](@ref) if applicable. + +For examples see [`DirichletCondition`](@ref) and [`NeumannCondition`](@ref) +""" +abstract type BoundaryCondition end + +""" + boundary(::BoundaryCondition) + +The boundary identifier of the BoundaryCondition. +""" +function boundary end + +""" + boundary_data(::BoundaryCondition) + +If implemented, the data associated with the BoundaryCondition. +""" +function boundary_data end + +""" + discretize_data(grid, bc::BoundaryCondition) + +The data of `bc` as a lazily evaluated grid function on the boundary grid +specified by `boundary(bc)`. +""" +function discretize_data(grid, bc::BoundaryCondition) + return eval_on(boundary_grid(grid, boundary(bc)), boundary_data(bc)) +end + +""" + DirichletCondition{DT,BID} + +A Dirichlet condition with `data::DT` on the boundary +specified by the boundary identifier `BID`. +""" +struct DirichletCondition{DT,BID} <: BoundaryCondition + data::DT + boundary::BID +end +boundary_data(bc::DirichletCondition) = bc.data +boundary(bc::DirichletCondition) = bc.boundary + +""" + NeumannCondition{DT,BID} + +A Neumann condition with `data::DT` on the boundary +specified by the boundary identifier `BID`. +""" +struct NeumannCondition{DT,BID} <: BoundaryCondition + data::DT + boundary::BID +end +boundary_data(bc::NeumannCondition) = bc.data +boundary(bc::NeumannCondition) = bc.boundary +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/boundary_conditions/sat.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,25 @@ +""" + sat_tensors(op, grid, bc::BoundaryCondition; kwargs...) + +The penalty tensor and boundary operator used to construct a +simultaneous-approximation-term for imposing `bc` related to `op`. + +For `penalty_tensor, L = sat_tensors(...)` then `SAT(u,g) = +penalty_tensor*(L*u - g)` where `g` is the boundary data. +""" +function sat_tensors end + + +""" + sat(op, grid, bc::BoundaryCondition; kwargs...) + +Simultaneous-Approximation-Term for a general `bc` to `op`. Returns a function +`SAT(u,g)` weakly imposing `bc` when added to `op*u`. + +Internally `sat_tensors(op, grid, bc; ...)` is called to construct the +necessary parts for the SAT. +""" +function sat(op, grid, bc::BoundaryCondition; kwargs...) + penalty_tensor, L = sat_tensors(op, grid, bc; kwargs...) + return SAT(u, g) = penalty_tensor*(L*u - g) +end
--- a/src/SbpOperators/boundaryops/boundary_operator.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/boundaryops/boundary_operator.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,80 +1,52 @@ """ - boundary_operator(grid,closure_stencil,boundary) - -Creates a boundary operator on a `Dim`-dimensional grid for the -specified `boundary`. The action of the operator is determined by `closure_stencil`. - -When `Dim=1`, the corresponding `BoundaryOperator` tensor mapping is returned. -When `Dim>1`, the `BoundaryOperator` `op` is inflated by the outer product -of `IdentityTensors` 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, closure_stencil, boundary::CartesianBoundary) - #TODO:Check that dim(boundary) <= Dim? - - # Create 1D boundary operator - r = region(boundary) - d = dim(boundary) - op = BoundaryOperator(restrict(grid, d), closure_stencil, r) - - # Create 1D IdentityTensors for each coordinate direction - one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid))) - Is = IdentityTensor{eltype(grid)}.(size.(one_d_grids)) - - # Formulate the correct outer product sequence of the identity mappings and - # the boundary operator - parts = Base.setindex(Is, op, d) - return foldl(⊗, parts) -end - -""" - BoundaryOperator{T,R,N} <: LazyTensor{T,0,1} + BoundaryOperator{T,B,N} <: LazyTensor{T,0,1} Implements the boundary operator `op` for 1D as a `LazyTensor` -`op` is the restriction of a grid function to the boundary using some closure `Stencil{T,N}`. -The boundary to restrict to is determined by `R`. -`op'` is the prolongation of a zero dimensional array to the whole grid using the same closure stencil. +`op` is the restriction of a grid function to the boundary using some closure +`Stencil{T,N}`. The boundary to restrict to is determined by `B`. `op'` is the +prolongation of a zero dimensional array to the whole grid using the same +closure stencil. """ -struct BoundaryOperator{T,R<:Region,N} <: LazyTensor{T,0,1} +struct BoundaryOperator{T,B<:BoundaryIdentifier,N} <: LazyTensor{T,0,1} stencil::Stencil{T,N} size::Int end -BoundaryOperator{R}(stencil::Stencil{T,N}, size::Int) where {T,R,N} = BoundaryOperator{T,R,N}(stencil, size) - """ - BoundaryOperator(grid::EquidistantGrid{1}, closure_stencil, region) + BoundaryOperator(grid::EquidistantGrid, closure_stencil, boundary) -Constructs the BoundaryOperator with stencil `closure_stencil` for a one-dimensional `grid`, restricting to -to the boundary specified by `region`. +Constructs the BoundaryOperator with stencil `closure_stencil` for a +`EquidistantGrid` `grid`, restricting to to the boundary specified by +`boundary`. """ -function BoundaryOperator(grid::EquidistantGrid{1}, closure_stencil::Stencil{T,N}, region::Region) where {T,N} - return BoundaryOperator{T,typeof(region),N}(closure_stencil,size(grid)[1]) +function BoundaryOperator(grid::EquidistantGrid, closure_stencil::Stencil{T,N}, boundary::BoundaryIdentifier) where {T,N} + return BoundaryOperator{T,typeof(boundary),N}(closure_stencil,size(grid)[1]) end """ closure_size(::BoundaryOperator) + The size of the closure stencil. """ -closure_size(::BoundaryOperator{T,R,N}) where {T,R,N} = N +closure_size(::BoundaryOperator{T,B,N}) where {T,B,N} = N LazyTensors.range_size(op::BoundaryOperator) = () LazyTensors.domain_size(op::BoundaryOperator) = (op.size,) -function LazyTensors.apply(op::BoundaryOperator{<:Any,Lower}, v::AbstractVector) +function LazyTensors.apply(op::BoundaryOperator{<:Any,LowerBoundary}, v::AbstractVector) apply_stencil(op.stencil,v,1) end -function LazyTensors.apply(op::BoundaryOperator{<:Any,Upper}, v::AbstractVector) +function LazyTensors.apply(op::BoundaryOperator{<:Any,UpperBoundary}, v::AbstractVector) apply_stencil_backwards(op.stencil,v,op.size) end -function LazyTensors.apply_transpose(op::BoundaryOperator{<:Any,Lower}, v::AbstractArray{<:Any,0}, i::Index{Lower}) +function LazyTensors.apply_transpose(op::BoundaryOperator{<:Any,LowerBoundary}, v::AbstractArray{<:Any,0}, i::Index{Lower}) return op.stencil[Int(i)-1]*v[] end -function LazyTensors.apply_transpose(op::BoundaryOperator{<:Any,Upper}, v::AbstractArray{<:Any,0}, i::Index{Upper}) +function LazyTensors.apply_transpose(op::BoundaryOperator{<:Any,UpperBoundary}, v::AbstractArray{<:Any,0}, i::Index{Upper}) return op.stencil[op.size[1] - Int(i)]*v[] end
--- a/src/SbpOperators/boundaryops/boundary_restriction.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/boundaryops/boundary_restriction.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,23 +1,27 @@ """ - boundary_restriction(grid, closure_stencil::Stencil, boundary) + boundary_restriction(g, stencil_set::StencilSet, boundary) + boundary_restriction(g::TensorGrid, stencil_set::StencilSet, boundary::TensorGridBoundary) + boundary_restriction(g::EquidistantGrid, stencil_set::StencilSet, boundary) Creates boundary restriction operators `e` as `LazyTensor`s on `boundary` -`e` is the restriction of a grid function to `boundary` using a `Stencil` `closure_stencil`. -`e'` is the prolongation of a grid function on `boundary` to the whole grid using the same `closure_stencil`. -On a one-dimensional `grid`, `e` is a `BoundaryOperator`. On a multi-dimensional `grid`, `e` is the inflation of -a `BoundaryOperator`. +`e` restricts a grid function on `g` to `boundary` using the 'e' stencil +in `stencil_set`. `e'` prolongates a grid function on +`boundary` to the whole grid using the same stencil. On a one-dimensional +grid, `e` is a `BoundaryOperator`. On a multi-dimensional grid, `e` is the +inflation of a `BoundaryOperator`. -See also: [`boundary_operator`](@ref). +See also: [`BoundaryOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -function boundary_restriction(grid, closure_stencil, boundary) - converted_stencil = convert(Stencil{eltype(grid)}, closure_stencil) - return SbpOperators.boundary_operator(grid, converted_stencil, boundary) +function boundary_restriction end + +function boundary_restriction(g::TensorGrid, stencil_set::StencilSet, boundary::TensorGridBoundary) + op = boundary_restriction(g.grids[grid_id(boundary)], stencil_set, boundary_id(boundary)) + return LazyTensors.inflate(op, size(g), grid_id(boundary)) end -""" - boundary_restriction(grid, stencil_set, boundary) - -Creates a `boundary_restriction` operator on `grid` given a `stencil_set`. -""" -boundary_restriction(grid, stencil_set::StencilSet, boundary) = boundary_restriction(grid, parse_stencil(stencil_set["e"]["closure"]), boundary) +function boundary_restriction(g::EquidistantGrid, stencil_set::StencilSet, boundary) + closure_stencil = parse_stencil(stencil_set["e"]["closure"]) + converted_stencil = convert(Stencil{eltype(g)}, closure_stencil) + return BoundaryOperator(g, converted_stencil, boundary) +end
--- a/src/SbpOperators/boundaryops/normal_derivative.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/boundaryops/normal_derivative.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,24 +1,30 @@ """ - normal_derivative(grid, closure_stencil::Stencil, boundary) + normal_derivative(g, stencil_set::StencilSet, boundary) + normal_derivative(g::TensorGrid, stencil_set::StencilSet, boundary::TensorGridBoundary) + normal_derivative(g::EquidistantGrid, stencil_set::StencilSet, boundary) Creates the normal derivative boundary operator `d` as a `LazyTensor` -`d` computes the normal derivative of a grid function on `boundary` a `Stencil` `closure_stencil`. -`d'` is the prolongation of the normal derivative of a grid function to the whole grid using the same `closure_stencil`. -On a one-dimensional `grid`, `d` is a `BoundaryOperator`. On a multi-dimensional `grid`, `d` is the inflation of -a `BoundaryOperator`. +`d` computes the normal derivative at `boundary` of a grid function on `g` using the +'d1' stencil in `stencil_set`. `d'` is the prolongation of the normal +derivative of a grid function to the whole of `g` using the same stencil. On a +one-dimensional grid, `d` is a `BoundaryOperator`. On a multi-dimensional +grid, `d` is the inflation of a `BoundaryOperator`. -See also: [`boundary_operator`](@ref). +See also: [`BoundaryOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -function normal_derivative(grid, closure_stencil, boundary) - direction = dim(boundary) - h_inv = inverse_spacing(grid)[direction] - return SbpOperators.boundary_operator(grid, scale(closure_stencil,h_inv), boundary) +function normal_derivative end + + +function normal_derivative(g::TensorGrid, stencil_set::StencilSet, boundary::TensorGridBoundary) + op = normal_derivative(g.grids[grid_id(boundary)], stencil_set, boundary_id(boundary)) + return LazyTensors.inflate(op, size(g), grid_id(boundary)) end -""" - normal_derivative(grid, stencil_set, boundary) +function normal_derivative(g::EquidistantGrid, stencil_set::StencilSet, boundary) + closure_stencil = parse_stencil(stencil_set["d1"]["closure"]) + h_inv = inverse_spacing(g) -Creates a `normal_derivative` operator on `grid` given a `stencil_set`. -""" -normal_derivative(grid, stencil_set::StencilSet, boundary) = normal_derivative(grid, parse_stencil(stencil_set["d1"]["closure"]), boundary) + scaled_stencil = scale(closure_stencil,h_inv) + return BoundaryOperator(g, scaled_stencil, boundary) +end
--- a/src/SbpOperators/operators/standard_diagonal.toml Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/operators/standard_diagonal.toml Tue Feb 10 22:41:19 2026 +0100 @@ -20,6 +20,10 @@ H.inner = "1" H.closure = ["1/2"] +e.closure = ["1"] +d1.closure = {s = ["3/2", "-2", "1/2"], c = 1} + + D1.inner_stencil = ["-1/2", "0", "1/2"] D1.closure_stencils = [ {s = ["-1", "1"], c = 1}, @@ -30,8 +34,13 @@ {s = ["1", "-2", "1"], c = 1}, ] -e.closure = ["1"] -d1.closure = {s = ["3/2", "-2", "1/2"], c = 1} +D2.positivity = {theta_M = "0.3636363636", theta_R = "1.000000538455350", m_b = "2"} + +D2variable.inner_stencil = [["1/2", "1/2", "0"],[ "-1/2", "-1", "-1/2"],["0", "1/2", "1/2"]] +D2variable.closure_stencils = [ + {s = [["2", "-1", "0"],["-3", "1", "0"],["1","0","0"]], c = 1}, +] + [[stencil_set]] @@ -40,6 +49,9 @@ H.inner = "1" H.closure = ["17/48", "59/48", "43/48", "49/48"] +e.closure = ["1"] +d1.closure = {s = ["11/6", "-3", "3/2", "-1/3"], c = 1} + 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}, @@ -56,5 +68,91 @@ {s = [ "-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"], c = 4}, ] +D2.positivity = {theta_M = "0.2505765857", theta_R = "0.577587500088313", m_b = "4"} + +D2variable.inner_stencil = [ + ["-1/8", "1/6", "-1/8", "0", "0" ], + [ "1/6", "1/2", "1/2", "1/6", "0" ], + ["-1/24", "-5/6", "-3/4", "-5/6", "-1/24"], + [ "0", "1/6", "1/2", "1/2", "1/6" ], + [ "0", "0", "-1/8", "1/6", "-1/8" ], +] +D2variable.closure_stencils = [ + {c = 1, s = [ + [ "920/289", "-59/68", "-81031200387/366633756146", "-69462376031/733267512292", "0", "0", "0", "0" ], + ["-1740/289", "0", "6025413881/7482321554", "1612249989/7482321554", "0", "0", "0", "0" ], + [ "1128/289", "59/68", "-6251815797/8526366422", "-639954015/17052732844", "0", "0", "0", "0" ], + [ "-308/289", "0", "1244724001/7482321554", "-752806667/7482321554", "0", "0", "0", "0" ], + [ "0", "0", "-148737261/10783345769", "148737261/10783345769", "0", "0", "0", "0" ], + [ "0", "0", "-3/833", "3/833", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + ]}, + {c = 2, s = [ + [ "12/17", "0", "102125659/440136562", "27326271/440136562", "0", "0", "0", "0" ], + [ "-59/68", "0", "-156920047993625/159775733917868", "-12001237118451/79887866958934", "0", "0", "0", "0" ], + [ "2/17", "0", "1489556735319/1857857371138", "149729180391/1857857371138", "0", "0", "0", "0" ], + [ "3/68", "0", "-13235456910147/159775733917868", "3093263736297/79887866958934", "0", "0", "0", "0" ], + [ "0", "0", "67535018271/2349643145851", "-67535018271/2349643145851", "0", "0", "0", "0" ], + [ "0", "0", "441/181507", "-441/181507", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + ]}, + {c = 3, s = [ + [ "-96/731", "59/172", "-6251815797/21566691538", "-639954015/43133383076", "0", "0", "0", "0" ], + [ "118/731", "0", "87883847383821/79887866958934", "8834021643069/79887866958934", "0", "0", "0", "0" ], + [ "-16/731", "-59/172", "-1134866646907639536627/727679167377258785038", "-13777050223300597/23487032885926596", "-26254/557679", "0", "0", "0" ], + [ "-6/731", "0", "14509020271326561681/14850595252597118062", "17220493277981/79887866958934", "1500708/7993399", "0", "0", "0" ], + [ "0", "0", "-4841930283098652915/21402328452272317207", "31597236232005/115132514146699", "-26254/185893", "0", "0", "0" ], + [ "0", "0", "-2318724711/1653303156799", "960119/1147305747", "13564/23980197", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + ]}, + {c = 4, s = [ + [ "-36/833", "0", "1244724001/21566691538", "-752806667/21566691538", "0", "0", "0", "0" ], + [ "177/3332", "0", "-780891957698673/7829010961975532", "3724542049827/79887866958934", "0", "0", "0", "0" ], + [ "-6/833", "0", "14509020271326561681/16922771334354855466", "2460070468283/13005001597966", "1500708/9108757", "0", "0", "0" ], + [ "-9/3332", "0", "-217407431400324796377/207908333536359652868", "-1950062198436997/3914505480987766", "-7476412/9108757", "-2/49", "0", "0" ], + [ "0", "0", "4959271814984644613/21402328452272317207", "47996144728947/115132514146699", "4502124/9108757", "8/49", "0", "0" ], + [ "0", "0", "-2258420001/1653303156799", "-1063649/8893843", "1473580/9108757", "-6/49", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + ]}, + {c = 5, s = [ + [ "0", "0", "-49579087/10149031312", "49579087/10149031312", "0", "0", "0", "0" ], + [ "0", "0", "1328188692663/37594290333616", "-1328188692663/37594290333616", "0", "0", "0", "0" ], + [ "0", "0", "-1613976761032884305/7963657098519931984", "10532412077335/42840005263888", "-564461/4461432", "0", "0", "0" ], + [ "0", "0", "4959271814984644613/20965546238960637264", "15998714909649/37594290333616", "375177/743572", "1/6", "0", "0" ], + [ "0", "0", "-8386761355510099813/128413970713633903242", "-2224717261773437/2763180339520776", "-280535/371786", "-5/6", "-1/24", "0" ], + [ "0", "0", "13091810925/13226425254392", "35039615/213452232", "1118749/2230716", "1/2", "1/6", "0" ], + [ "0", "0", "0", "0", "-1/8", "1/6", "-1/8", "0" ], + [ "0", "0", "0", "0", "0", "0", "0", "0" ], + ]}, + {c = 6, s = [ + [ "0", "0", "-1/784", "1/784", "0", "0", "0", "0" ], + [ "0", "0", "8673/2904112", "-8673/2904112", "0", "0", "0", "0" ], + [ "0", "0", "-33235054191/26452850508784", "960119/1280713392", "3391/6692148", "0", "0", "0" ], + [ "0", "0", "-752806667/539854092016", "-1063649/8712336", "368395/2230716", "-1/8", "0", "0" ], + [ "0", "0", "13091810925/13226425254392", "35039615/213452232", "1118749/2230716", "1/2", "1/6", "0" ], + [ "0", "0", "-660204843/13226425254392", "-3290636/80044587", "-5580181/6692148", "-3/4", "-5/6", "-1/24"], + [ "0", "0", "0", "0", "1/6", "1/2", "1/2", "1/6" ], + [ "0", "0", "0", "0", "0", "-1/8", "1/6", "-1/8" ], + ]} +] + + +[[stencil_set]] + +order = 6 + +H.inner = "1" +H.closure = ["13649/43200", "12013/8640", "2711/4320", "5359/4320", "7877/8640", "43801/43200"] + + + + + e.closure = ["1"] -d1.closure = {s = ["11/6", "-3", "3/2", "-1/3"], c = 1} +d1.closure = ["-25/12", "4", "-3", "4/3", "-1/4"] + +D2.positivity = {theta_M = "0.1878687080", theta_R = "0.3697", m_b = "7"}
--- a/src/SbpOperators/stencil.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/stencil.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,18 +1,17 @@ -export CenteredStencil -export CenteredNestedStencil - struct Stencil{T,N} range::UnitRange{Int64} weights::NTuple{N,T} - function Stencil(range::UnitRange,weights::NTuple{N,T}) where {T, N} + function Stencil(range::UnitRange,weights::NTuple{N,Any}) where N + T = eltype(weights) + @assert length(range) == N new{T,N}(range,weights) end end """ - Stencil(weights::NTuple; center::Int) + Stencil(weights...; center::Int) Create a stencil with the given weights with element `center` as the center of the stencil. """ @@ -69,42 +68,67 @@ end Base.@propagate_inbounds @inline function apply_stencil(s::Stencil, v::AbstractVector, i::Int) - w = zero(promote_type(eltype(s),eltype(v))) - @simd for k ∈ 1:length(s) - w += s.weights[k]*v[i + s.range[k]] + return sum(enumerate(s.weights)) do (k,w) #TBD: Which optimizations are needed here? + w*v[i + @inbounds s.range[k]] end - - return w end Base.@propagate_inbounds @inline function apply_stencil_backwards(s::Stencil, v::AbstractVector, i::Int) - w = zero(promote_type(eltype(s),eltype(v))) - @simd for k ∈ length(s):-1:1 - w += s.weights[k]*v[i - s.range[k]] + return sum(enumerate(s.weights)) do (k,w) #TBD: Which optimizations are needed here? + w*v[i - @inbounds s.range[k]] end - return w end +# There are many options for the implementation of `apply_stencil` and +# `apply_stencil_backwards`. Some alternatives were tried on the branch +# bugfix/sbp_operators/stencil_return_type and can be found at the following +# revision: +# +# * 237b980ffb91 (baseline) +# * a72bab15228e (mapreduce) +# * ffd735354d54 (multiplication) +# * b5abd5191f2c (promote_op) +# * 8d56846185fc (return_type) +# + +function left_pad(s::Stencil, N) + weights = LazyTensors.left_pad_tuple(s.weights, zero(eltype(s)), N) + range = (first(s.range) - (N - length(s.weights))):last(s.range) + + return Stencil(range, weights) +end + +function right_pad(s::Stencil, N) + weights = LazyTensors.right_pad_tuple(s.weights, zero(eltype(s)), N) + range = first(s.range):(last(s.range) + (N - length(s.weights))) + + return Stencil(range, weights) +end + + struct NestedStencil{T,N,M} s::Stencil{Stencil{T,N},M} end +NestedStencil(;center) = NestedStencil(Stencil(;center)) +CenteredNestedStencil() = NestedStencil(CenteredStencil()) + # Stencil input NestedStencil(s::Vararg{Stencil}; center) = NestedStencil(Stencil(s... ; center)) CenteredNestedStencil(s::Vararg{Stencil}) = NestedStencil(CenteredStencil(s...)) # Tuple input -function NestedStencil(weights::Vararg{NTuple{N,Any}}; center) where N +function NestedStencil(weights::Vararg{NTuple{N,Any} where N}; center) inner_stencils = map(w -> Stencil(w...; center), weights) return NestedStencil(Stencil(inner_stencils... ; center)) end -function CenteredNestedStencil(weights::Vararg{NTuple{N,Any}}) where N + +function CenteredNestedStencil(weights::Vararg{NTuple{N,Any} where N}) inner_stencils = map(w->CenteredStencil(w...), weights) return CenteredNestedStencil(inner_stencils...) end - # Conversion function NestedStencil{T,N,M}(ns::NestedStencil{S,N,M}) where {T,S,N,M} return NestedStencil(Stencil{Stencil{T}}(ns.s)) @@ -117,7 +141,7 @@ function Base.convert(::Type{NestedStencil{T,N,M}}, s::NestedStencil{S,N,M}) where {T,S,N,M} return NestedStencil{T,N,M}(s) end -Base.convert(::Type{NestedStencil{T}}, stencil) where T = NestedStencil{T}(stencil) +Base.convert(::Type{NestedStencil{T}}, stencil::NestedStencil) where T = NestedStencil{T}(stencil) function Base.promote_rule(::Type{NestedStencil{T,N,M}}, ::Type{NestedStencil{S,N,M}}) where {T,S,N,M} return NestedStencil{promote_type(T,S),N,M}
--- a/src/SbpOperators/stencil_set.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/stencil_set.jl Tue Feb 10 22:41:19 2026 +0100 @@ -5,7 +5,7 @@ StencilSet A `StencilSet` contains a set of associated stencils. The stencils -are are stored in a table, and can be accesed by indexing into the `StencilSet`. +are are stored in a table, and can be accessed by indexing into the `StencilSet`. """ struct StencilSet table @@ -14,14 +14,14 @@ """ -read_stencil_set(filename; filters) + read_stencil_set(filename; filters) Creates a `StencilSet` from a TOML file based on some key-value filters. If more than one set matches the filters an error is raised. The table of the `StencilSet` is a parsed TOML intended for functions like `parse_scalar` and `parse_stencil`. -The `StencilSet` table is not parsed beyond the inital TOML parse. To get usable +The `StencilSet` table is not parsed beyond the initial 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 @@ -110,6 +110,33 @@ end end + +""" + parse_nested_stencil(parsed_toml) + +Accept parsed TOML and read it as a nested tuple. + +See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref). +""" +function parse_nested_stencil(parsed_toml) + if parsed_toml isa Array + weights = parse_stencil.(parsed_toml) + return CenteredNestedStencil(weights...) + end + + center = parsed_toml["c"] + weights = parse_tuple.(parsed_toml["s"]) + return NestedStencil(weights...; center) +end + +""" + parse_nested_stencil(T, parsed_toml) + +Parse the input as a nested stencil with element type `T`. +""" +parse_nested_stencil(T, parsed_toml) = NestedStencil{T}(parse_nested_stencil(parsed_toml)) + + """ parse_scalar(parsed_toml)
--- a/src/SbpOperators/volumeops/constant_interior_scaling_operator.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/constant_interior_scaling_operator.jl Tue Feb 10 22:41:19 2026 +0100 @@ -2,7 +2,8 @@ ConstantInteriorScalingOperator{T,N} <: LazyTensor{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. +scaled with individual weights while all interior points are scaled using the +same factor. """ struct ConstantInteriorScalingOperator{T,N} <: LazyTensor{T,1,1} interior_weight::T @@ -18,7 +19,7 @@ end end -function ConstantInteriorScalingOperator(grid::EquidistantGrid{1}, interior_weight, closure_weights) +function ConstantInteriorScalingOperator(grid::EquidistantGrid, interior_weight::T, closure_weights::NTuple{N,T} where N) where T return ConstantInteriorScalingOperator(interior_weight, Tuple(closure_weights), size(grid)[1]) end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/derivatives/dissipation.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,114 @@ +""" + undivided_skewed04(g::TensorGrid, p, direction) + undivided_skewed04(g::EquidistantGrid, p) + +Undivided difference operators approximating the `p`th derivative. The +operators do not satisfy any SBP property and are meant to be used for +building artificial dissipation terms. + +The operators and how they are used to create accurate artificial dissipation +is described in "K. Mattsson, M. Svärd, and J. Nordström, “Stable and Accurate +Artificial Dissipation,” Journal of Scientific Computing, vol. 21, no. 1, pp. +57–79, Aug. 2004" +""" +function undivided_skewed04 end + +function undivided_skewed04(g::TensorGrid, p, direction) + D,Dᵀ = undivided_skewed04(g.grids[direction], p) + return ( + LazyTensors.inflate(D, size(g), direction), + LazyTensors.inflate(Dᵀ, size(g), direction), + ) +end + +function undivided_skewed04(g::EquidistantGrid, p) + T = eltype(g) + interior_weights = T.(dissipation_interior_weights(p)) + + D = StencilOperatorDistinctClosures( + g, + dissipation_interior_stencil(interior_weights), + dissipation_lower_closure_stencils(interior_weights), + dissipation_upper_closure_stencils(interior_weights), + ) + Dᵀ = StencilOperatorDistinctClosures( + g, + dissipation_transpose_interior_stencil(interior_weights), + dissipation_transpose_lower_closure_stencils(interior_weights), + dissipation_transpose_upper_closure_stencils(interior_weights), + ) + + return D, Dᵀ +end + +function dissipation_interior_weights(p) + if p == 0 + return (1,) + end + + return (0, dissipation_interior_weights(p-1)...) .- (dissipation_interior_weights(p-1)..., 0) +end + +midpoint(weights) = length(weights)÷2 + 1 +midpoint_transpose(weights) = length(weights)+1 - midpoint(weights) + +function dissipation_interior_stencil(weights) + return Stencil(weights..., center=midpoint(weights)) +end +function dissipation_transpose_interior_stencil(weights) + if iseven(length(weights)) + weights = map(-, weights) + end + + return Stencil(weights..., center=midpoint_transpose(weights)) +end + +dissipation_lower_closure_size(weights) = midpoint(weights) - 1 +dissipation_upper_closure_size(weights) = length(weights) - midpoint(weights) + +function dissipation_lower_closure_stencils(interior_weights) + stencil(i) = Stencil(interior_weights..., center=i) + return ntuple(i->stencil(i), dissipation_lower_closure_size(interior_weights)) +end + +function dissipation_upper_closure_stencils(interior_weights) + center(i) = length(interior_weights) - dissipation_upper_closure_size(interior_weights) + i + stencil(i) = Stencil(interior_weights..., center=center(i)) + return ntuple(i->stencil(i), dissipation_upper_closure_size(interior_weights)) +end + +function dissipation_transpose_lower_closure_stencils(interior_weights) + closure = ntuple(i->dissipation_transpose_lower_closure_stencil(interior_weights, i), length(interior_weights)) + + N = maximum(s->length(s.weights), closure) + return right_pad.(closure, N) +end + +function dissipation_transpose_upper_closure_stencils(interior_weights) + closure = reverse(ntuple(i->dissipation_transpose_upper_closure_stencil(interior_weights, i), length(interior_weights))) + + N = maximum(s->length(s.weights), closure) + return left_pad.(closure, N) +end + + +function dissipation_transpose_lower_closure_stencil(interior_weights, i) + w = ntuple(k->interior_weights[i], dissipation_lower_closure_size(interior_weights)) + + for k ∈ i:-1:1 + w = (w..., interior_weights[k]) + end + + return Stencil(w..., center = i) +end + +function dissipation_transpose_upper_closure_stencil(interior_weights, i) + j = length(interior_weights)+1-i + w = ntuple(k->interior_weights[j], dissipation_upper_closure_size(interior_weights)) + + for k ∈ j:1:length(interior_weights) + w = (interior_weights[k], w...) + end + + return Stencil(w..., center = length(interior_weights)-midpoint(interior_weights)+1) +end
--- a/src/SbpOperators/volumeops/derivatives/first_derivative.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/derivatives/first_derivative.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,46 +1,42 @@ """ - first_derivative(grid::EquidistantGrid, inner_stencil, closure_stencils, direction) + first_derivative(g, ..., [direction]) + +The first derivative operator `D1` as a `LazyTensor` on the given grid. -Creates the first-derivative operator `D1` as a `LazyTensor` - -`D1` approximates the first-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. +`D1` approximates the first-derivative d/dξ on `g` along the coordinate +dimension specified by `direction`. +""" +function first_derivative end -On a one-dimensional `grid`, `D1` is a `VolumeOperator`. On a multi-dimensional `grid`, `D1` is the outer product of the -one-dimensional operator with the `IdentityTensor`s in orthogonal coordinate dirrections. +""" + first_derivative(g::TensorGrid, stencil_set, direction) -See also: [`volume_operator`](@ref). +See also: [`VolumeOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -function first_derivative(grid::EquidistantGrid, inner_stencil, closure_stencils, direction) - h_inv = inverse_spacing(grid)[direction] - return SbpOperators.volume_operator(grid, scale(inner_stencil,h_inv), scale.(closure_stencils,h_inv), odd, direction) +function first_derivative(g::TensorGrid, stencil_set, direction) + D₁ = first_derivative(g.grids[direction], stencil_set) + return LazyTensors.inflate(D₁, size(g), direction) end - """ - first_derivative(grid, inner_stencil, closure_stencils) + first_derivative(g::EquidistantGrid, stencil_set::StencilSet) -Creates a `first_derivative` operator on a 1D `grid` given `inner_stencil` and `closure_stencils`. +The first derivative operator on an `EquidistantGrid`. +Uses the `D1` stencil in `stencil_set`. """ -first_derivative(grid::EquidistantGrid{1}, inner_stencil::Stencil, closure_stencils) = first_derivative(grid, inner_stencil, closure_stencils, 1) - +function first_derivative(g::EquidistantGrid, stencil_set::StencilSet) + inner_stencil = parse_stencil(stencil_set["D1"]["inner_stencil"]) + closure_stencils = parse_stencil.(stencil_set["D1"]["closure_stencils"]) + return first_derivative(g, inner_stencil, closure_stencils); +end """ - first_derivative(grid, stencil_set::StencilSet, direction) + first_derivative(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) -Creates a `first_derivative` operator on `grid` along coordinate dimension `direction` given a `stencil_set`. +The first derivative operator on an `EquidistantGrid` given an +`inner_stencil` and `closure_stencils`. """ -function first_derivative(grid::EquidistantGrid, stencil_set::StencilSet, direction) - inner_stencil = parse_stencil(stencil_set["D1"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D1"]["closure_stencils"]) - first_derivative(grid,inner_stencil,closure_stencils,direction); +function first_derivative(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) + h⁻¹ = inverse_spacing(g) + return VolumeOperator(g, scale(inner_stencil,h⁻¹), scale.(closure_stencils,h⁻¹), odd) end - - -""" - first_derivative(grid, stencil_set) - -Creates a `first_derivative` operator on a 1D `grid` given a `stencil_set`. -""" -first_derivative(grid::EquidistantGrid{1}, stencil_set::StencilSet) = first_derivative(grid, stencil_set, 1)
--- a/src/SbpOperators/volumeops/derivatives/second_derivative.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/derivatives/second_derivative.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,46 +1,37 @@ """ - second_derivative(grid::EquidistantGrid, inner_stencil, closure_stencils, direction) + second_derivative(g::EquidistantGrid, stencil_set, direction) -Creates the second-derivative operator `D2` as a `LazyTensor` - -`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. +Creates the second derivative operator `D2` as a `LazyTensor` -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 `IdentityTensor`s in orthogonal coordinate dirrections. +`D2` approximates the second-derivative d²/dξ² on `g` along the coordinate +dimension specified by `direction`. -See also: [`volume_operator`](@ref). +See also: [`VolumeOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -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) +function second_derivative(g::TensorGrid, stencil_set, direction) + D₂ = second_derivative(g.grids[direction], stencil_set) + return LazyTensors.inflate(D₂, size(g), direction) end - """ - second_derivative(grid, inner_stencil, closure_stencils) + second_derivative(g::EquidistantGrid, stencil_set::::StencilSet) -Creates a `second_derivative` operator on a 1D `grid` given `inner_stencil` and `closure_stencils`. +The second derivative operator on an `EquidistantGrid`. +Uses the `D2` stencil in `stencil_set`. """ -second_derivative(grid::EquidistantGrid{1}, inner_stencil::Stencil, closure_stencils) = second_derivative(grid, inner_stencil, closure_stencils,1) - +function second_derivative(g::EquidistantGrid, stencil_set::StencilSet) + inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) + closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) + return second_derivative(g, inner_stencil, closure_stencils) +end """ - second_derivative(grid, stencil_set, direction) + second_derivative(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) -Creates a `second_derivative` operator on `grid` along coordinate dimension `direction` given a `stencil_set`. +The second derivative operator on an `EquidistantGrid`, given `inner_stencil` and +`closure_stencils`. """ -function second_derivative(grid::EquidistantGrid, stencil_set::StencilSet, direction) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - second_derivative(grid,inner_stencil,closure_stencils,direction); +function second_derivative(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) + h⁻¹ = inverse_spacing(g) + return VolumeOperator(g, scale(inner_stencil,h⁻¹^2), scale.(closure_stencils,h⁻¹^2), even) end - - -""" - second_derivative(grid, stencil_set) - -Creates a `second_derivative` operator on a 1D `grid` given a `stencil_set`. -""" -second_derivative(grid::EquidistantGrid{1}, stencil_set::StencilSet) = second_derivative(grid, stencil_set, 1) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/derivatives/second_derivative_variable.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,188 @@ +""" + second_derivative_variable(g, coeff ..., [direction]) + +The variable second derivative operator as a `LazyTensor` on the given grid. +`coeff` is a grid function of the variable coefficient. + +Approximates the d/dξ c d/dξ on `g` along the coordinate dimension specified +by `direction`. +""" +function second_derivative_variable end + +function second_derivative_variable(g::TensorGrid, coeff, stencil_set, dir::Int) + inner_stencil = parse_nested_stencil(eltype(coeff), stencil_set["D2variable"]["inner_stencil"]) + closure_stencils = parse_nested_stencil.(eltype(coeff), stencil_set["D2variable"]["closure_stencils"]) + + return second_derivative_variable(g, coeff, inner_stencil, closure_stencils, dir) +end + +function second_derivative_variable(g::EquidistantGrid, coeff, stencil_set) + return second_derivative_variable(TensorGrid(g), coeff, stencil_set, 1) +end + +function second_derivative_variable(g::TensorGrid, coeff, inner_stencil::NestedStencil, closure_stencils, dir) + check_coefficient(g, coeff) + + Δxᵢ = spacing(g.grids[dir]) + scaled_inner_stencil = scale(inner_stencil, 1/Δxᵢ^2) + scaled_closure_stencils = scale.(Tuple(closure_stencils), 1/Δxᵢ^2) + return SecondDerivativeVariable(coeff, scaled_inner_stencil, scaled_closure_stencils, dir) +end + +function check_coefficient(g, coeff) + if ndims(g) != ndims(coeff) + throw(ArgumentError("The coefficient has dimension $(ndims(coeff)) while the grid is dimension $(ndims(g))")) + end + + if size(g) != size(coeff) + throw(DimensionMismatch("the size $(size(coeff)) of the coefficient does not match the size $(size(g)) of the grid")) + end +end + + +""" + SecondDerivativeVariable{Dir,T,D,...} <: LazyTensor{T,D,D} + +A second derivative operator in direction `Dir` with a variable coefficient. +""" +struct SecondDerivativeVariable{Dir,T,D,M,IStencil<:NestedStencil{T},CStencil<:NestedStencil{T},TArray<:AbstractArray} <: LazyTensor{T,D,D} + inner_stencil::IStencil + closure_stencils::NTuple{M,CStencil} + coefficient::TArray + + function SecondDerivativeVariable(coefficient::AbstractArray, inner_stencil::NestedStencil{T}, closure_stencils::NTuple{M,NestedStencil{T}}, dir) where {T,M} + D = ndims(coefficient) + IStencil = typeof(inner_stencil) + CStencil = eltype(closure_stencils) + TArray = typeof(coefficient) + return new{dir,T,D,M,IStencil,CStencil,TArray}(inner_stencil, closure_stencils, coefficient) + end +end + +derivative_direction(::SecondDerivativeVariable{Dir}) where {Dir} = Dir + +closure_size(op::SecondDerivativeVariable) = length(op.closure_stencils) + +LazyTensors.range_size(op::SecondDerivativeVariable) = size(op.coefficient) +LazyTensors.domain_size(op::SecondDerivativeVariable) = size(op.coefficient) + + +function derivative_view(op, a, I) + d = derivative_direction(op) + + Iview = Base.setindex(I,:,d) + return @view a[Iview...] +end + +function apply_lower(op::SecondDerivativeVariable, v, I...) + ṽ = derivative_view(op, v, I) + c̃ = derivative_view(op, op.coefficient, I) + + i = I[derivative_direction(op)] + return @inbounds apply_stencil(op.closure_stencils[i], c̃, ṽ, i) +end + +function apply_interior(op::SecondDerivativeVariable, v, I...) + ṽ = derivative_view(op, v, I) + c̃ = derivative_view(op, op.coefficient, I) + + i = I[derivative_direction(op)] + return apply_stencil(op.inner_stencil, c̃, ṽ, i) +end + +function apply_upper(op::SecondDerivativeVariable, v, I...) + ṽ = derivative_view(op, v, I) + c̃ = derivative_view(op, op.coefficient, I) + + i = I[derivative_direction(op)] + sz = domain_size(op)[derivative_direction(op)] + stencil = op.closure_stencils[sz-i+1] + return @inbounds apply_stencil_backwards(stencil, c̃, ṽ, i) +end + +function LazyTensors.apply(op::SecondDerivativeVariable, v::AbstractArray, I::Vararg{Index}) + if I[derivative_direction(op)] isa Index{Lower} + return apply_lower(op, v, Int.(I)...) + elseif I[derivative_direction(op)] isa Index{Upper} + return apply_upper(op, v, Int.(I)...) + elseif I[derivative_direction(op)] isa Index{Interior} + return apply_interior(op, v, Int.(I)...) + else + error("Invalid region") + end +end + +function LazyTensors.apply(op::SecondDerivativeVariable, v::AbstractArray, I...) + dir = derivative_direction(op) + sz = domain_size(op)[dir] + + i = I[dir] + + I = map(i->Index(i, Interior), I) + if 0 < i <= closure_size(op) + I = Base.setindex(I, Index(i, Lower), dir) + return LazyTensors.apply(op, v, I...) + elseif closure_size(op) < i <= sz-closure_size(op) + I = Base.setindex(I, Index(i, Interior), dir) + return LazyTensors.apply(op, v, I...) + elseif sz-closure_size(op) < i <= sz + I = Base.setindex(I, Index(i, Upper), dir) + return LazyTensors.apply(op, v, I...) + else + error("Bounds error") # This should be `throw(BoundsError())` but the type inference is so fragile that it doesn't work. Needs investigation. / Jonatan 2023-06-08 + end +end + + +# 2D Specific implementations to avoid type instability +# TBD: Can this be solved by fixing the general methods instead? + + +## x-direction +function apply_lower(op::SecondDerivativeVariable{1}, v, i, j) + ṽ = @view v[:,j] + c̃ = @view op.coefficient[:,j] + + return @inbounds apply_stencil(op.closure_stencils[i], c̃, ṽ, i) +end + +function apply_interior(op::SecondDerivativeVariable{1}, v, i, j) + ṽ = @view v[:,j] + c̃ = @view op.coefficient[:,j] + + return @inbounds apply_stencil(op.inner_stencil, c̃, ṽ, i) +end + +function apply_upper(op::SecondDerivativeVariable{1}, v, i, j) + ṽ = @view v[:,j] + c̃ = @view op.coefficient[:,j] + + sz = domain_size(op)[derivative_direction(op)] + stencil = op.closure_stencils[sz-i+1] + return @inbounds apply_stencil_backwards(stencil, c̃, ṽ, i) +end + + +## y-direction +function apply_lower(op::SecondDerivativeVariable{2}, v, i, j) + ṽ = @view v[i,:] + c̃ = @view op.coefficient[i,:] + + return @inbounds apply_stencil(op.closure_stencils[j], c̃, ṽ, j) +end + +function apply_interior(op::SecondDerivativeVariable{2}, v, i, j) + ṽ = @view v[i,:] + c̃ = @view op.coefficient[i,:] + + return @inbounds apply_stencil(op.inner_stencil, c̃, ṽ, j) +end + +function apply_upper(op::SecondDerivativeVariable{2}, v, i, j) + ṽ = @view v[i,:] + c̃ = @view op.coefficient[i,:] + + sz = domain_size(op)[derivative_direction(op)] + stencil = op.closure_stencils[sz-j+1] + return @inbounds apply_stencil_backwards(stencil, c̃, ṽ, j) +end
--- a/src/SbpOperators/volumeops/inner_products/inner_product.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/inner_products/inner_product.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,46 +1,52 @@ """ - inner_product(grid::EquidistantGrid, interior_weight, closure_weights) + inner_product(grid, ...) -Creates the discrete inner product operator `H` as a `LazyTensor` on an -equidistant grid, defined as `(u,v) = u'Hv` for grid functions `u,v`. +The inner product on a given grid with weights from a stencils set or given +explicitly. +""" +function inner_product end + +""" + inner_product(tg::TensorGrid, stencil_set::StencilSet) -`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. +The inner product on `tg`, i.e., the tensor product of the +individual grids' inner products, using weights `H` from `stencil_set`. +""" +function inner_product(tg::TensorGrid, stencil_set::StencilSet) + return mapreduce(g->inner_product(g,stencil_set), ⊗, tg.grids) +end -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 `IdentityTensor`. +""" + inner_product(g::EquidistantGrid, stencil_set::StencilSet) + +The inner product on `g` using weights `H` from `stencil_set`. See also: [`ConstantInteriorScalingOperator`](@ref). """ -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 foldl(⊗, Hs) +function inner_product(g::EquidistantGrid, stencil_set::StencilSet) + interior_weight = parse_scalar(stencil_set["H"]["inner"]) + closure_weights = parse_tuple(stencil_set["H"]["closure"]) + return inner_product(g, interior_weight, closure_weights) end -function inner_product(grid::EquidistantGrid{1}, interior_weight, closure_weights) - h = spacing(grid)[1] +""" + inner_product(g::EquidistantGrid, interior_weight, closure_weights) + +The inner product on `g` with explicit weights. - H = SbpOperators.ConstantInteriorScalingOperator(grid, h*interior_weight, h.*closure_weights) - return H +See also: [`ConstantInteriorScalingOperator`](@ref). +""" +function inner_product(g::EquidistantGrid, interior_weight, closure_weights) + h = spacing(g) + return SbpOperators.ConstantInteriorScalingOperator(g, h*interior_weight, h.*closure_weights) end -inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityTensor{eltype(grid)}() - """ - inner_product(grid, stencil_set) + inner_product(g::ZeroDimGrid, stencil_set::StencilSet) -Creates a `inner_product` operator on `grid` given a `stencil_set`. +The identity tensor with the correct type parameters. + +Implemented to simplify 1D code for SBP operators. """ -function inner_product(grid, stencil_set::StencilSet) - inner_stencil = parse_scalar(stencil_set["H"]["inner"]) - closure_stencils = parse_tuple(stencil_set["H"]["closure"]) - return inner_product(grid, inner_stencil, closure_stencils) -end +inner_product(g::ZeroDimGrid, stencil_set::StencilSet) = IdentityTensor{component_type(g)}() +
--- a/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,42 +1,51 @@ """ - inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights) + inverse_inner_product(grid, ...) + +The inverse inner product on a given grid with weights from a stencils set or given +explicitly. +""" +function inverse_inner_product end + +""" + inverse_inner_product(tg::TensorGrid, stencil_set::StencilSet) -Constructs the inverse inner product operator `H⁻¹` as a `LazyTensor` using -the weights of `H`, `interior_weight`, `closure_weights`. `H⁻¹` is inverse of -the inner product operator `H`. +The inverse of inner product on `tg`, i.e., the tensor product of the +individual grids' inverse inner products, using weights `H` from `stencil_set`. +""" +function inverse_inner_product(tg::TensorGrid, stencil_set::StencilSet) + return mapreduce(g->inverse_inner_product(g,stencil_set), ⊗, tg.grids) +end -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 `IdentityTensor`. +""" + inverse_inner_product(g::EquidistantGrid, stencil_set::StencilSet) + +The inverse of the inner product on `g` using weights `H` from `stencil_set`. See also: [`ConstantInteriorScalingOperator`](@ref). """ -function inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights) - H⁻¹s = () - - for i ∈ 1:dimension(grid) - H⁻¹s = (H⁻¹s..., inverse_inner_product(restrict(grid, i), interior_weight, closure_weights)) - end - - return foldl(⊗, H⁻¹s) +function inverse_inner_product(g::EquidistantGrid, stencil_set::StencilSet) + interior_weight = parse_scalar(stencil_set["H"]["inner"]) + closure_weights = parse_tuple(stencil_set["H"]["closure"]) + return inverse_inner_product(g, interior_weight, closure_weights) 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⁻¹ +""" + inverse_inner_product(g::EquidistantGrid, interior_weight, closure_weights) + +The inverse inner product on `g` with explicit weights. + +See also: [`ConstantInteriorScalingOperator`](@ref). +""" +function inverse_inner_product(g::EquidistantGrid, interior_weight, closure_weights) + h⁻¹ = inverse_spacing(g) + return SbpOperators.ConstantInteriorScalingOperator(g, h⁻¹*1/interior_weight, h⁻¹./closure_weights) end -inverse_inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityTensor{eltype(grid)}() - """ - inverse_inner_product(grid, stencil_set) + inverse_inner_product(g::ZeroDimGrid, stencil_set::StencilSet) -Creates a `inverse_inner_product` operator on `grid` given a `stencil_set`. +The identity tensor with the correct type parameters. + +Implemented to simplify 1D code for SBP operators. """ -function inverse_inner_product(grid, stencil_set::StencilSet) - inner_stencil = parse_scalar(stencil_set["H"]["inner"]) - closure_stencils = parse_tuple(stencil_set["H"]["closure"]) - return inverse_inner_product(grid, inner_stencil, closure_stencils) -end +inverse_inner_product(g::ZeroDimGrid, stencil_set::StencilSet) = IdentityTensor{component_type(g)}()
--- a/src/SbpOperators/volumeops/laplace/laplace.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/laplace/laplace.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,9 +1,8 @@ """ Laplace{T, Dim, TM} <: LazyTensor{T, Dim, Dim} -Implements the Laplace operator, approximating ∑d²/xᵢ² , i = 1,...,`Dim` as a -`LazyTensor`. Additionally `Laplace` stores the `StencilSet` -used to construct the `LazyTensor `. +The Laplace operator, approximating ∑d²/xᵢ² , i = 1,...,`Dim` as a +`LazyTensor`. """ struct Laplace{T, Dim, TM<:LazyTensor{T, Dim, Dim}} <: LazyTensor{T, Dim, Dim} D::TM # Difference operator @@ -11,17 +10,15 @@ end """ - Laplace(grid::Equidistant, stencil_set) + Laplace(g::Grid, stencil_set::StencilSet) -Creates the `Laplace` operator `Δ` on `grid` given a `stencil_set`. +Creates the `Laplace` operator `Δ` on `g` given `stencil_set`. See also [`laplace`](@ref). """ -function Laplace(grid::EquidistantGrid, stencil_set::StencilSet) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - Δ = laplace(grid, inner_stencil,closure_stencils) - return Laplace(Δ,stencil_set) +function Laplace(g::Grid, stencil_set::StencilSet) + Δ = laplace(g, stencil_set) + return Laplace(Δ, stencil_set) end LazyTensors.range_size(L::Laplace) = LazyTensors.range_size(L.D) @@ -32,24 +29,99 @@ # Base.show(io::IO, L::Laplace) = ... """ - laplace(grid::EquidistantGrid, inner_stencil, closure_stencils) - -Creates the Laplace operator operator `Δ` as a `LazyTensor` + laplace(g::Grid, stencil_set) -`Δ` 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. +Creates the Laplace operator operator `Δ` as a `LazyTensor` on `g`. -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. +`Δ` approximates the Laplace operator ∑d²/xᵢ² , i = 1,...,`Dim` on `g`. The +approximation depends on the type of grid and the stencil set. See also: [`second_derivative`](@ref). """ -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) +function laplace end +function laplace(g::TensorGrid, stencil_set) + # return mapreduce(+, enumerate(g.grids)) do (i, gᵢ) + # Δᵢ = laplace(gᵢ, stencil_set) + # LazyTensors.inflate(Δᵢ, size(g), i) + # end + + Δ = LazyTensors.inflate(laplace(g.grids[1], stencil_set), size(g), 1) + for d = 2:ndims(g) + Δ += LazyTensors.inflate(laplace(g.grids[d], stencil_set), size(g), d) end return Δ end +laplace(g::EquidistantGrid, stencil_set) = second_derivative(g, stencil_set) + +""" + sat_tensors(Δ::Laplace, g::Grid, bc::DirichletCondition; H_tuning, R_tuning) + +The operators required to construct the SAT for imposing a Dirichlet +condition. `H_tuning` and `R_tuning` are used to specify the strength of the +penalty. + +See also: [`sat`](@ref), [`DirichletCondition`](@ref), [`positivity_decomposition`](@ref). +""" +function sat_tensors(Δ::Laplace, g::Grid, bc::DirichletCondition; H_tuning = 1., R_tuning = 1.) + id = boundary(bc) + set = Δ.stencil_set + H⁻¹ = inverse_inner_product(g,set) + Hᵧ = inner_product(boundary_grid(g, id), set) + e = boundary_restriction(g, set, id) + d = normal_derivative(g, set, id) + B = positivity_decomposition(Δ, g, boundary(bc); H_tuning, R_tuning) + penalty_tensor = H⁻¹∘(d' - B*e')∘Hᵧ + return penalty_tensor, e +end + +""" + sat_tensors(Δ::Laplace, g::Grid, bc::NeumannCondition) + +The operators required to construct the SAT for imposing a Neumann condition. + +See also: [`sat`](@ref), [`NeumannCondition`](@ref). +""" +function sat_tensors(Δ::Laplace, g::Grid, bc::NeumannCondition) + id = boundary(bc) + set = Δ.stencil_set + H⁻¹ = inverse_inner_product(g,set) + Hᵧ = inner_product(boundary_grid(g, id), set) + e = boundary_restriction(g, set, id) + d = normal_derivative(g, set, id) + + penalty_tensor = -H⁻¹∘e'∘Hᵧ + return penalty_tensor, d +end + +""" + positivity_decomposition(Δ::Laplace, g::Grid, b::BoundaryIdentifier; H_tuning, R_tuning) + +Constructs the scalar `B` such that `d' - 1/2*B*e'` is symmetric positive +definite with respect to the boundary quadrature. Here `d` is the normal +derivative and `e` is the boundary restriction operator. `B` can then be used +to form a symmetric and energy stable penalty for a Dirichlet condition. The +parameters `H_tuning` and `R_tuning` are used to specify the strength of the +penalty and must be greater than 1. For details we refer to +<https://doi.org/10.1016/j.jcp.2020.109294> +""" +function positivity_decomposition(Δ::Laplace, g::Grid, b::BoundaryIdentifier; H_tuning, R_tuning) + @assert(H_tuning ≥ 1.) + @assert(R_tuning ≥ 1.) + Nτ_H, τ_R = positivity_limits(Δ,g,b) + return H_tuning*Nτ_H + R_tuning*τ_R +end + +function positivity_limits(Δ::Laplace, g::EquidistantGrid, b::BoundaryIdentifier) + h = spacing(g) + θ_H = parse_scalar(Δ.stencil_set["H"]["closure"][1]) + θ_R = parse_scalar(Δ.stencil_set["D2"]["positivity"]["theta_R"]) + + τ_H = one(eltype(Δ))/(h*θ_H) + τ_R = one(eltype(Δ))/(h*θ_R) + return τ_H, τ_R +end + +function positivity_limits(Δ::Laplace, g::TensorGrid, b::BoundaryIdentifier) + τ_H, τ_R = positivity_limits(Δ, g.grids[grid_id(b)], b) + return τ_H*ndims(g), τ_R +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/stencil_operator_distinct_closures.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,54 @@ +""" + StencilOperatorDistinctClosures{T,K,N,M,L} <: LazyTensor{T,1} + +A one dimensional stencil operator with separate closures for the two +boundaries. + +`StencilOperatorDistinctClosures` can be contrasted to `VolumeOperator` in +that it has different closure stencils for the upper and lower boundary. +`VolumeOperator` uses the same closure for both boundaries. Having distinct +closures is useful for representing operators with skewed stencils like upwind +operators. + +See also: [`VolumeOperator`](@ref) +""" +struct StencilOperatorDistinctClosures{T,K,N,M,LC<:NTuple{N,Stencil{T,L}} where L, UC<:NTuple{M,Stencil{T,L}} where L} <: LazyTensor{T,1,1} + inner_stencil::Stencil{T,K} + lower_closure::LC + upper_closure::UC + size::Tuple{Int} +end + +function StencilOperatorDistinctClosures(grid::EquidistantGrid, inner_stencil, lower_closure, upper_closure) + return StencilOperatorDistinctClosures(inner_stencil, Tuple(lower_closure), Tuple(upper_closure), size(grid)) +end + +lower_closure_size(::StencilOperatorDistinctClosures{T,K,N,M}) where {T,K,N,M} = N +upper_closure_size(::StencilOperatorDistinctClosures{T,K,N,M}) where {T,K,N,M} = M + +LazyTensors.range_size(op::StencilOperatorDistinctClosures) = op.size +LazyTensors.domain_size(op::StencilOperatorDistinctClosures) = op.size + +function LazyTensors.apply(op::StencilOperatorDistinctClosures, v::AbstractVector, i::Index{Lower}) + return @inbounds apply_stencil(op.lower_closure[Int(i)], v, Int(i)) +end + +function LazyTensors.apply(op::StencilOperatorDistinctClosures, v::AbstractVector, i::Index{Interior}) + return apply_stencil(op.inner_stencil, v, Int(i)) +end + +function LazyTensors.apply(op::StencilOperatorDistinctClosures, v::AbstractVector, i::Index{Upper}) + stencil_index = Int(i) - (op.size[1]-upper_closure_size(op)) + return @inbounds apply_stencil(op.upper_closure[stencil_index], v, Int(i)) +end + +function LazyTensors.apply(op::StencilOperatorDistinctClosures, v::AbstractVector, i) + if i <= lower_closure_size(op) + LazyTensors.apply(op, v, Index(i, Lower)) + elseif i > op.size[1]-upper_closure_size(op) + LazyTensors.apply(op, v, Index(i, Upper)) + else + LazyTensors.apply(op, v, Index(i, Interior)) + end +end +# TODO: Move this to LazyTensors when we have the region communication down.
--- a/src/SbpOperators/volumeops/volume_operator.jl Mon May 23 07:20:27 2022 +0200 +++ b/src/SbpOperators/volumeops/volume_operator.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,47 +1,28 @@ -""" - 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 -returned operator is the appropriate outer product of a one-dimensional -operators and `IdentityTensor`s, e.g for `Dim=3` the volume operator in the -y-direction is `I⊗op⊗I`. -""" -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 IdentityTensors for each coordinate direction - one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid))) - Is = IdentityTensor{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) - return foldl(⊗, parts) -end - """ VolumeOperator{T,N,M,K} <: LazyTensor{T,1,1} -Implements a one-dimensional constant coefficients volume operator + +A one-dimensional constant coefficients stencil operator. """ struct VolumeOperator{T,N,M,K} <: LazyTensor{T,1,1} inner_stencil::Stencil{T,N} closure_stencils::NTuple{M,Stencil{T,K}} - size::NTuple{1,Int} + size::Int parity::Parity + + function VolumeOperator(inner_stencil::Stencil{T,N}, closure_stencils::Tuple{Stencil{T,K}, Vararg{Stencil{T,K}}}, size::Int, parity::Parity) where {T,N,K} + M = length(closure_stencils) + return new{T,N,M,K}(inner_stencil, closure_stencils, size, parity) + end end -function VolumeOperator(grid::EquidistantGrid{1}, inner_stencil, closure_stencils, parity) - return VolumeOperator(inner_stencil, Tuple(closure_stencils), size(grid), parity) -end +function VolumeOperator(grid::EquidistantGrid, inner_stencil, closure_stencils, parity) + return VolumeOperator(inner_stencil, Tuple(closure_stencils), size(grid,1), parity) +end # TBD: Remove this function? closure_size(::VolumeOperator{T,N,M}) where {T,N,M} = M -LazyTensors.range_size(op::VolumeOperator) = op.size -LazyTensors.domain_size(op::VolumeOperator) = op.size +LazyTensors.range_size(op::VolumeOperator) = (op.size,) +LazyTensors.domain_size(op::VolumeOperator) = (op.size,) function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i::Index{Lower}) return @inbounds apply_stencil(op.closure_stencils[Int(i)], v, Int(i)) @@ -52,10 +33,11 @@ end function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i::Index{Upper}) - return @inbounds Int(op.parity)*apply_stencil_backwards(op.closure_stencils[op.size[1]-Int(i)+1], v, Int(i)) + return @inbounds Int(op.parity)*apply_stencil_backwards(op.closure_stencils[op.size-Int(i)+1], v, Int(i)) end function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i) - r = getregion(i, closure_size(op), op.size[1]) + r = getregion(i, closure_size(op), op.size) return LazyTensors.apply(op, v, Index(i, r)) end +# TODO: Move this to LazyTensors when we have the region communication down.
--- a/src/Sbplib.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -module Sbplib - -include("StaticDicts/StaticDicts.jl") -include("RegionIndices/RegionIndices.jl") -include("LazyTensors/LazyTensors.jl") -include("Grids/Grids.jl") -include("SbpOperators/SbpOperators.jl") -include("DiffOps/DiffOps.jl") - -export RegionIndices -export LazyTensors -export Grids -export SbpOperators -export DiffOps - -end
--- a/src/StaticDicts/StaticDicts.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -module StaticDicts - -export StaticDict - -""" - StaticDict{K,V,N} <: AbstractDict{K,V} - -A static dictionary implementing the interface for an `AbstractDict`. A -`StaticDict` is fully immutable and after creation no changes can be made. - -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 -aspect of this is that `StaticDict` can be used in a struct while still -allowing the struct to be compared using the default implementation of `==` for -structs. - -Lookups are done by linear search. - -Duplicate keys are not allowed and an error will be thrown if they are passed -to the constructor. -""" -struct StaticDict{K,V,N} <: AbstractDict{K,V} - pairs::NTuple{N,Pair{K,V}} - - function StaticDict{K,V}(pairs::Vararg{Pair,N}) where {K,V,N} - if !allunique(first.(pairs)) - throw(DomainError(pairs, "keys must be unique")) - end - return new{K,V,N}(pairs) - end -end - -function StaticDict(pairs::Vararg{Pair}) - K = typejoin(firsttype.(pairs)...) - V = typejoin(secondtype.(pairs)...) - return StaticDict{K,V}(pairs...) -end - -StaticDict(pairs::NTuple{N,Pair} where N) = StaticDict(pairs...) - -function Base.get(d::StaticDict, key, default) - for p ∈ d.pairs - if key == p.first - return p.second - end - end - - return default -end - -Base.iterate(d::StaticDict) = iterate(d.pairs) -Base.iterate(d::StaticDict, state) = iterate(d.pairs,state) -Base.length(d::StaticDict) = length(d.pairs) - - -""" - merge(d1::StaticDict, d2::StaticDict) - -Merge two `StaticDict`. Repeating keys is considered and error. This may -change in a future version. -""" -function Base.merge(d1::StaticDict, d2::StaticDict) - return StaticDict(d1.pairs..., d2.pairs...) -end - - -""" - firsttype(::Pair{T1,T2}) - -The type of the first element in the pair. -""" -firsttype(::Pair{T1,T2}) where {T1,T2} = T1 - -""" - secondtype(::Pair{T1,T2}) - -The type of the secondtype element in the pair. -""" -secondtype(::Pair{T1,T2}) where {T1,T2} = T2 - -end # module
--- a/test/DiffOps/DiffOps_test.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -using Test -using Sbplib.DiffOps -using Sbplib.Grids -using Sbplib.SbpOperators -using Sbplib.RegionIndices -using Sbplib.LazyTensors - -# -# @testset "BoundaryValue" begin -# op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4) -# g = EquidistantGrid((4,5), (0.0, 0.0), (1.0,1.0)) -# -# e_w = BoundaryValue(op, g, CartesianBoundary{1,Lower}()) -# e_e = BoundaryValue(op, g, CartesianBoundary{1,Upper}()) -# e_s = BoundaryValue(op, g, CartesianBoundary{2,Lower}()) -# e_n = BoundaryValue(op, g, CartesianBoundary{2,Upper}()) -# -# v = zeros(Float64, 4, 5) -# v[:,5] = [1, 2, 3,4] -# v[:,4] = [1, 2, 3,4] -# v[:,3] = [4, 5, 6, 7] -# v[:,2] = [7, 8, 9, 10] -# v[:,1] = [10, 11, 12, 13] -# -# @test e_w isa LazyTensor{T,2,1} where T -# @test e_w' isa LazyTensor{T,1,2} where T -# -# @test domain_size(e_w, (3,2)) == (2,) -# @test domain_size(e_e, (3,2)) == (2,) -# @test domain_size(e_s, (3,2)) == (3,) -# @test domain_size(e_n, (3,2)) == (3,) -# -# @test size(e_w'*v) == (5,) -# @test size(e_e'*v) == (5,) -# @test size(e_s'*v) == (4,) -# @test size(e_n'*v) == (4,) -# -# @test collect(e_w'*v) == [10,7,4,1.0,1] -# @test collect(e_e'*v) == [13,10,7,4,4.0] -# @test collect(e_s'*v) == [10,11,12,13.0] -# @test collect(e_n'*v) == [1,2,3,4.0] -# -# g_x = [1,2,3,4.0] -# g_y = [5,4,3,2,1.0] -# -# G_w = zeros(Float64, (4,5)) -# G_w[1,:] = g_y -# -# G_e = zeros(Float64, (4,5)) -# G_e[4,:] = g_y -# -# G_s = zeros(Float64, (4,5)) -# G_s[:,1] = g_x -# -# G_n = zeros(Float64, (4,5)) -# G_n[:,5] = g_x -# -# @test size(e_w*g_y) == (UnknownDim,5) -# @test size(e_e*g_y) == (UnknownDim,5) -# @test size(e_s*g_x) == (4,UnknownDim) -# @test size(e_n*g_x) == (4,UnknownDim) -# -# # These tests should be moved to where they are possible (i.e we know what the grid should be) -# @test_broken collect(e_w*g_y) == G_w -# @test_broken collect(e_e*g_y) == G_e -# @test_broken collect(e_s*g_x) == G_s -# @test_broken collect(e_n*g_x) == G_n -# end -# -# @testset "NormalDerivative" begin -# op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4) -# g = EquidistantGrid((5,6), (0.0, 0.0), (4.0,5.0)) -# -# d_w = NormalDerivative(op, g, CartesianBoundary{1,Lower}()) -# d_e = NormalDerivative(op, g, CartesianBoundary{1,Upper}()) -# d_s = NormalDerivative(op, g, CartesianBoundary{2,Lower}()) -# d_n = NormalDerivative(op, g, CartesianBoundary{2,Upper}()) -# -# -# v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y) -# v∂x = evalOn(g, (x,y)-> 2*x + y) -# v∂y = evalOn(g, (x,y)-> 2*(y-1) + x) -# -# @test d_w isa LazyTensor{T,2,1} where T -# @test d_w' isa LazyTensor{T,1,2} where T -# -# @test domain_size(d_w, (3,2)) == (2,) -# @test domain_size(d_e, (3,2)) == (2,) -# @test domain_size(d_s, (3,2)) == (3,) -# @test domain_size(d_n, (3,2)) == (3,) -# -# @test size(d_w'*v) == (6,) -# @test size(d_e'*v) == (6,) -# @test size(d_s'*v) == (5,) -# @test size(d_n'*v) == (5,) -# -# @test collect(d_w'*v) ≈ v∂x[1,:] -# @test collect(d_e'*v) ≈ v∂x[5,:] -# @test collect(d_s'*v) ≈ v∂y[:,1] -# @test collect(d_n'*v) ≈ v∂y[:,6] -# -# -# d_x_l = zeros(Float64, 5) -# d_x_u = zeros(Float64, 5) -# for i ∈ eachindex(d_x_l) -# d_x_l[i] = op.dClosure[i-1] -# d_x_u[i] = -op.dClosure[length(d_x_u)-i] -# end -# -# d_y_l = zeros(Float64, 6) -# d_y_u = zeros(Float64, 6) -# for i ∈ eachindex(d_y_l) -# d_y_l[i] = op.dClosure[i-1] -# d_y_u[i] = -op.dClosure[length(d_y_u)-i] -# end -# -# function prod_matrix(x,y) -# G = zeros(Float64, length(x), length(y)) -# for I ∈ CartesianIndices(G) -# G[I] = x[I[1]]*y[I[2]] -# end -# -# return G -# end -# -# g_x = [1,2,3,4.0,5] -# g_y = [5,4,3,2,1.0,11] -# -# G_w = prod_matrix(d_x_l, g_y) -# G_e = prod_matrix(d_x_u, g_y) -# G_s = prod_matrix(g_x, d_y_l) -# G_n = prod_matrix(g_x, d_y_u) -# -# -# @test size(d_w*g_y) == (UnknownDim,6) -# @test size(d_e*g_y) == (UnknownDim,6) -# @test size(d_s*g_x) == (5,UnknownDim) -# @test size(d_n*g_x) == (5,UnknownDim) -# -# # These tests should be moved to where they are possible (i.e we know what the grid should be) -# @test_broken collect(d_w*g_y) ≈ G_w -# @test_broken collect(d_e*g_y) ≈ G_e -# @test_broken collect(d_s*g_x) ≈ G_s -# @test_broken collect(d_n*g_x) ≈ G_n -# end -# -# @testset "BoundaryQuadrature" begin -# op = read_D2_operator(sbp_operators_path()*"standard_diagonal.toml"; order=4) -# g = EquidistantGrid((10,11), (0.0, 0.0), (1.0,1.0)) -# -# H_w = BoundaryQuadrature(op, g, CartesianBoundary{1,Lower}()) -# H_e = BoundaryQuadrature(op, g, CartesianBoundary{1,Upper}()) -# H_s = BoundaryQuadrature(op, g, CartesianBoundary{2,Lower}()) -# H_n = BoundaryQuadrature(op, g, CartesianBoundary{2,Upper}()) -# -# v = evalOn(g, (x,y)-> x^2 + (y-1)^2 + x*y) -# -# function get_quadrature(N) -# qc = op.quadratureClosure -# q = (qc..., ones(N-2*closuresize(op))..., reverse(qc)...) -# @assert length(q) == N -# return q -# end -# -# v_w = v[1,:] -# v_e = v[10,:] -# v_s = v[:,1] -# v_n = v[:,11] -# -# q_x = spacing(g)[1].*get_quadrature(10) -# q_y = spacing(g)[2].*get_quadrature(11) -# -# @test H_w isa TensorOperator{T,1} where T -# -# @test domain_size(H_w, (3,)) == (3,) -# @test domain_size(H_n, (3,)) == (3,) -# -# @test range_size(H_w, (3,)) == (3,) -# @test range_size(H_n, (3,)) == (3,) -# -# @test size(H_w*v_w) == (11,) -# @test size(H_e*v_e) == (11,) -# @test size(H_s*v_s) == (10,) -# @test size(H_n*v_n) == (10,) -# -# @test collect(H_w*v_w) ≈ q_y.*v_w -# @test collect(H_e*v_e) ≈ q_y.*v_e -# @test collect(H_s*v_s) ≈ q_x.*v_s -# @test collect(H_n*v_n) ≈ q_x.*v_n -# -# @test collect(H_w'*v_w) == collect(H_w'*v_w) -# @test collect(H_e'*v_e) == collect(H_e'*v_e) -# @test collect(H_s'*v_s) == collect(H_s'*v_s) -# @test collect(H_n'*v_n) == collect(H_n'*v_n) -# end
--- a/test/Grids/EquidistantGrid_test.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,123 +0,0 @@ -using Sbplib.Grids -using Test -using Sbplib.RegionIndices - - -@testset "EquidistantGrid" begin - @test EquidistantGrid(4,0.0,1.0) isa EquidistantGrid - @test EquidistantGrid(4,0.0,8.0) isa EquidistantGrid - # constuctor - @test_throws DomainError EquidistantGrid(0,0.0,1.0) - @test_throws DomainError EquidistantGrid(1,1.0,1.0) - @test_throws DomainError EquidistantGrid(1,1.0,-1.0) - @test EquidistantGrid(4,0.0,1.0) == EquidistantGrid((4,),(0.0,),(1.0,)) - - @testset "Base" begin - @test eltype(EquidistantGrid(4,0.0,1.0)) == Float64 - @test eltype(EquidistantGrid((4,3),(0,0),(1,3))) == Int - @test size(EquidistantGrid(4,0.0,1.0)) == (4,) - @test size(EquidistantGrid((5,3), (0.0,0.0), (2.0,1.0))) == (5,3) - end - - # dimension - @test dimension(EquidistantGrid(4,0.0,1.0)) == 1 - @test dimension(EquidistantGrid((5,3), (0.0,0.0), (2.0,1.0))) == 2 - - # spacing - @test [spacing(EquidistantGrid(4,0.0,1.0))...] ≈ [(1. /3,)...] atol=5e-13 - @test [spacing(EquidistantGrid((5,3), (0.0,-1.0), (2.0,1.0)))...] ≈ [(0.5, 1.)...] atol=5e-13 - - # inverse_spacing - @test [inverse_spacing(EquidistantGrid(4,0.0,1.0))...] ≈ [(3.,)...] atol=5e-13 - @test [inverse_spacing(EquidistantGrid((5,3), (0.0,-1.0), (2.0,1.0)))...] ≈ [(2, 1.)...] atol=5e-13 - - # points - g = EquidistantGrid((5,3), (-1.0,0.0), (0.0,7.11)) - gp = points(g); - p = [(-1.,0.) (-1.,7.11/2) (-1.,7.11); - (-0.75,0.) (-0.75,7.11/2) (-0.75,7.11); - (-0.5,0.) (-0.5,7.11/2) (-0.5,7.11); - (-0.25,0.) (-0.25,7.11/2) (-0.25,7.11); - (0.,0.) (0.,7.11/2) (0.,7.11)] - for i ∈ eachindex(gp) - @test [gp[i]...] ≈ [p[i]...] atol=5e-13 - end - - # restrict - g = EquidistantGrid((5,3), (0.0,0.0), (2.0,1.0)) - @test restrict(g, 1) == EquidistantGrid(5,0.0,2.0) - @test restrict(g, 2) == EquidistantGrid(3,0.0,1.0) - - g = EquidistantGrid((2,5,3), (0.0,0.0,0.0), (2.0,1.0,3.0)) - @test restrict(g, 1) == EquidistantGrid(2,0.0,2.0) - @test restrict(g, 2) == EquidistantGrid(5,0.0,1.0) - @test restrict(g, 3) == EquidistantGrid(3,0.0,3.0) - @test restrict(g, 1:2) == EquidistantGrid((2,5),(0.0,0.0),(2.0,1.0)) - @test restrict(g, 2:3) == EquidistantGrid((5,3),(0.0,0.0),(1.0,3.0)) - @test restrict(g, [1,3]) == EquidistantGrid((2,3),(0.0,0.0),(2.0,3.0)) - @test restrict(g, [2,1]) == EquidistantGrid((5,2),(0.0,0.0),(1.0,2.0)) - - @testset "boundary_identifiers" begin - g = EquidistantGrid((2,5,3), (0.0,0.0,0.0), (2.0,1.0,3.0)) - bids = (CartesianBoundary{1,Lower}(),CartesianBoundary{1,Upper}(), - CartesianBoundary{2,Lower}(),CartesianBoundary{2,Upper}(), - CartesianBoundary{3,Lower}(),CartesianBoundary{3,Upper}()) - @test boundary_identifiers(g) == bids - @inferred boundary_identifiers(g) - end - - @testset "boundary_grid" begin - @testset "1D" begin - g = EquidistantGrid(5,0.0,2.0) - (id_l, id_r) = boundary_identifiers(g) - @test boundary_grid(g,id_l) == EquidistantGrid{Float64}() - @test boundary_grid(g,id_r) == EquidistantGrid{Float64}() - @test_throws DomainError boundary_grid(g,CartesianBoundary{2,Lower}()) - @test_throws DomainError boundary_grid(g,CartesianBoundary{0,Lower}()) - end - @testset "2D" begin - g = EquidistantGrid((5,3),(0.0,0.0),(1.0,3.0)) - (id_w, id_e, id_s, id_n) = boundary_identifiers(g) - @test boundary_grid(g,id_w) == restrict(g,2) - @test boundary_grid(g,id_e) == restrict(g,2) - @test boundary_grid(g,id_s) == restrict(g,1) - @test boundary_grid(g,id_n) == restrict(g,1) - @test_throws DomainError boundary_grid(g,CartesianBoundary{4,Lower}()) - end - @testset "3D" begin - g = EquidistantGrid((2,5,3), (0.0,0.0,0.0), (2.0,1.0,3.0)) - (id_w, id_e, - id_s, id_n, - id_t, id_b) = boundary_identifiers(g) - @test boundary_grid(g,id_w) == restrict(g,[2,3]) - @test boundary_grid(g,id_e) == restrict(g,[2,3]) - @test boundary_grid(g,id_s) == restrict(g,[1,3]) - @test boundary_grid(g,id_n) == restrict(g,[1,3]) - @test boundary_grid(g,id_t) == restrict(g,[1,2]) - @test boundary_grid(g,id_b) == restrict(g,[1,2]) - @test_throws DomainError boundary_grid(g,CartesianBoundary{4,Lower}()) - end - end - - @testset "refine" begin - @test refine(EquidistantGrid{Float64}(), 1) == EquidistantGrid{Float64}() - @test refine(EquidistantGrid{Float64}(), 2) == EquidistantGrid{Float64}() - - g = EquidistantGrid((10,5),(0.,1.),(2.,3.)) - @test refine(g, 1) == g - @test refine(g, 2) == EquidistantGrid((19,9),(0.,1.),(2.,3.)) - @test refine(g, 3) == EquidistantGrid((28,13),(0.,1.),(2.,3.)) - end - - @testset "coarsen" begin - @test coarsen(EquidistantGrid{Float64}(), 1) == EquidistantGrid{Float64}() - @test coarsen(EquidistantGrid{Float64}(), 2) == EquidistantGrid{Float64}() - - g = EquidistantGrid((7,13),(0.,1.),(2.,3.)) - @test coarsen(g, 1) == g - @test coarsen(g, 2) == EquidistantGrid((4,7),(0.,1.),(2.,3.)) - @test coarsen(g, 3) == EquidistantGrid((3,5),(0.,1.),(2.,3.)) - - @test_throws DomainError(4, "Size minus 1 must be divisible by the ratio.") coarsen(g, 4) == EquidistantGrid((3,5),(0.,1.),(2.,3.)) - end -end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/equidistant_grid_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,189 @@ +using Diffinitive.Grids +using Test +using Diffinitive.LazyTensors +using StaticArrays + + +@testset "EquidistantGrid" begin + @test EquidistantGrid(0:0.1:10) isa EquidistantGrid + @test EquidistantGrid(range(0,1,length=10)) isa EquidistantGrid + @test EquidistantGrid(LinRange(0,1,11)) isa EquidistantGrid + + @testset "Indexing Interface" begin + g = EquidistantGrid(0:0.1:10) + @test g[1] == 0.0 + @test g[5] == 0.4 + @test g[101] == 10.0 + + @test g[begin] == 0.0 + @test g[end] == 10.0 + + @test all(eachindex(g) .== 1:101) + + @test firstindex(g) == 1 + @test lastindex(g) == 101 + end + + @testset "Iterator interface" begin + @test eltype(EquidistantGrid(0:10)) == Int + @test eltype(EquidistantGrid(0:2:10)) == Int + @test eltype(EquidistantGrid(0:0.1:10)) == Float64 + @test size(EquidistantGrid(0:10)) == (11,) + @test size(EquidistantGrid(0:0.1:10)) == (101,) + + @test size(EquidistantGrid(0:0.1:10),1) == 101 + + @test collect(EquidistantGrid(0:0.1:0.5)) == [0.0, 0.1, 0.2, 0.3, 0.4, 0.5] + + @test Base.IteratorSize(EquidistantGrid{Float64, StepRange{Float64}}) == Base.HasShape{1}() + end + + @testset "Base" begin + @test ndims(EquidistantGrid(0:10)) == 1 + + g = EquidistantGrid(0:0.1:10) + @test axes(g,1) == 1:101 + @test axes(g) == (1:101,) + end + + @testset "spacing" begin + @test spacing(EquidistantGrid(0:10)) == 1 + @test spacing(EquidistantGrid(0:0.1:10)) == 0.1 + end + + @testset "inverse_spacing" begin + @test inverse_spacing(EquidistantGrid(0:10)) == 1 + @test inverse_spacing(EquidistantGrid(0:0.1:10)) == 10 + end + + @testset "min_spacing" begin + @test min_spacing(EquidistantGrid(0:10)) == 1 + @test min_spacing(EquidistantGrid(0:0.1:10)) == 0.1 + end + + @testset "boundary_identifiers" begin + g = EquidistantGrid(0:0.1:10) + @test boundary_identifiers(g) == (LowerBoundary(), UpperBoundary()) + @inferred boundary_identifiers(g) + end + + @testset "boundary_grid" begin + g = EquidistantGrid(0:0.1:1) + @test boundary_grid(g, LowerBoundary()) == ZeroDimGrid(0.0) + @test boundary_grid(g, UpperBoundary()) == ZeroDimGrid(1.0) + end + + @testset "boundary_indices" begin + g = EquidistantGrid(0:0.1:1) + @test boundary_indices(g, LowerBoundary()) == 1 + @test boundary_indices(g, UpperBoundary()) == 11 + + gf = collect(g) + @test gf[boundary_indices(g, LowerBoundary())] == gf[1] + @test gf[boundary_indices(g, UpperBoundary())] == gf[11] + + g = EquidistantGrid(2:0.1:10) + @test boundary_indices(g, LowerBoundary()) == 1 + @test boundary_indices(g, UpperBoundary()) == 81 + end + + @testset "refine" begin + g = EquidistantGrid(0:0.1:1) + @test refine(g, 1) == g + @test refine(g, 2) == EquidistantGrid(0:0.05:1) + @test refine(g, 3) == EquidistantGrid(0:(0.1/3):1) + end + + @testset "coarsen" begin + g = EquidistantGrid(0:1:10) + @test coarsen(g, 1) == g + @test coarsen(g, 2) == EquidistantGrid(0:2:10) + + g = EquidistantGrid(0:0.1:1) + @test coarsen(g, 1) == g + @test coarsen(g, 2) == EquidistantGrid(0:0.2:1) + + g = EquidistantGrid(0:10) + @test coarsen(g, 1) == EquidistantGrid(0:1:10) + @test coarsen(g, 2) == EquidistantGrid(0:2:10) + + @test_throws DomainError(3, "Size minus 1 must be divisible by the ratio.") coarsen(g, 3) + end +end + + +@testset "equidistant_grid" begin + @test equidistant_grid(0.0,1.0, 4) isa EquidistantGrid + @test equidistant_grid((0.0,0.0),(8.0,5.0), 4, 3) isa TensorGrid + @test equidistant_grid((0.0,),(8.0,), 4) isa TensorGrid + + # constuctor + @test_throws DomainError equidistant_grid(0.0, 1.0, 0) + @test_throws DomainError equidistant_grid(1.0, 1.0, 1) + @test_throws DomainError equidistant_grid(1.0, -1.0, 1) + + @test_throws DomainError equidistant_grid((0.0,0.0),(1.0,1.0), 0, 0) + @test_throws DomainError equidistant_grid((1.0,1.0),(1.0,1.0), 1, 1) + @test_throws DomainError equidistant_grid((1.0,1.0),(-1.0,-1.0), 1, 1) + + @test_throws ArgumentError equidistant_grid((0.0,),(8.0,5.0), 4, 3, 4) + + @testset "Base" begin + @test eltype(equidistant_grid(0.0, 1.0, 4)) == Float64 + @test eltype(equidistant_grid((0,0),(1,3), 4, 3)) <: AbstractVector{Float64} + + @test size(equidistant_grid(0.0, 1.0, 4)) == (4,) + @test size(equidistant_grid((0.0,0.0), (2.0,1.0), 5, 3)) == (5,3) + + @test size(equidistant_grid((0.0,0.0), (2.0,1.0), 5, 3), 1) == 5 + @test size(equidistant_grid((0.0,0.0), (2.0,1.0), 5, 3), 2) == 3 + + @test ndims(equidistant_grid(0.0, 1.0, 4)) == 1 + @test ndims(equidistant_grid((0.0,0.0), (2.0,1.0), 5, 3)) == 2 + end + + @testset "getindex" begin + g = equidistant_grid((-1.0,0.0), (0.0,7.11), 5, 3) + gp = collect(g); + p = [(-1.,0.) (-1.,7.11/2) (-1.,7.11); + (-0.75,0.) (-0.75,7.11/2) (-0.75,7.11); + (-0.5,0.) (-0.5,7.11/2) (-0.5,7.11); + (-0.25,0.) (-0.25,7.11/2) (-0.25,7.11); + (0.,0.) (0.,7.11/2) (0.,7.11)] + for i ∈ eachindex(gp) + @test [gp[i]...] ≈ [p[i]...] atol=5e-13 + end + end + + @testset "equidistant_grid(::ParameterSpace)" begin + ps = HyperBox((0,0),(2,1)) + + @test equidistant_grid(ps, 3,4) == equidistant_grid((0,0), (2,1), 3,4) + + @test equidistant_grid(unitinterval(),3) == equidistant_grid(0,1,3) + @test equidistant_grid(HyperBox((0,),(2,)),4) == equidistant_grid(@SVector[0], @SVector[2], 4) + end + + @testset "equidistant_grid(::Chart)" begin + c = Chart(unitsquare()) do (ξ,η) + @SVector[2ξ, 3η] + end + Grids.jacobian(c::typeof(c), ξ̄) = @SMatrix[2 0; 0 3] + + @test equidistant_grid(c, 5, 4) isa Grid + end +end + + +@testset "change_length" begin + @test Grids.change_length(0:20, 21) == 0:20 + @test Grids.change_length(0:20, 11) == 0:2:20 + @test Grids.change_length(0:2:20, 21) == 0:20 + + @test Grids.change_length(range(0,1,length=10), 10) == range(0,1,length=10) + @test Grids.change_length(range(0,1,length=10), 5) == range(0,1,length=5) + @test Grids.change_length(range(0,1,length=10), 20) == range(0,1,length=20) + + @test Grids.change_length(LinRange(1,2,10),10) == LinRange(1,2,10) + @test Grids.change_length(LinRange(1,2,10),15) == LinRange(1,2,15) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/grid_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,148 @@ +using Test +using Diffinitive.Grids +using Diffinitive.LazyTensors +using StaticArrays + +@testset "Grid" begin + struct DummyGrid{T,D} <: Grid{T,D} end + + @test eltype(DummyGrid{Int, 2}) == Int + @test eltype(DummyGrid{Int, 2}()) == Int + + @test ndims(DummyGrid{Int, 2}()) == 2 + + @test coordinate_size(DummyGrid{Int, 1}()) == 1 + @test coordinate_size(DummyGrid{SVector{3,Float64}, 2}()) == 3 + + @test coordinate_size(DummyGrid{SVector{3,Float64}, 2}) == 3 +end + +@testset "component_type" begin + @test component_type(DummyGrid{Int,1}()) == Int + @test component_type(DummyGrid{Float64,1}()) == Float64 + @test component_type(DummyGrid{Rational,1}()) == Rational + + @test component_type(DummyGrid{SVector{3,Int},2}()) == Int + @test component_type(DummyGrid{SVector{2,Float64},3}()) == Float64 + @test component_type(DummyGrid{SVector{4,Rational},4}()) == Rational + + @test component_type(DummyGrid{Float64,1}) == Float64 + @test component_type(DummyGrid{SVector{2,Float64},3}) == Float64 + + @test component_type(fill(@SVector[1,2], 4,2)) == Int +end + +@testset "eval_on" begin + @test eval_on(ZeroDimGrid(@SVector[1.,2.]), x̄->x̄[1]+x̄[2]) isa LazyArray + @test eval_on(ZeroDimGrid(@SVector[1.,2.]), x̄->x̄[1]+x̄[2]) == fill(3.) + @test eval_on(ZeroDimGrid(@SVector[3.,2.]), x̄->x̄[1]+x̄[2]) == fill(5.) + + @test eval_on(ZeroDimGrid(1.), x̄->2x̄) isa LazyArray + @test eval_on(ZeroDimGrid(1.), x̄->2x̄) == fill(2.) + + @test eval_on(ZeroDimGrid(@SVector[1.,2.]), π) isa LazyArray + @test eval_on(ZeroDimGrid(@SVector[1.,2.]), π) == fill(π) + + @test eval_on(EquidistantGrid(range(0,1,length=4)), x->2x) isa LazyArray + @test eval_on(EquidistantGrid(range(0,1,length=4)), x->2x) == 2 .* range(0,1,length=4) + + + g = equidistant_grid((0.0,0.0), (2.0,1.0), 5, 3) + + @test eval_on(g, x̄ -> 0.) isa LazyArray + @test eval_on(g, x̄ -> 0.) == fill(0., (5,3)) + + @test eval_on(g, x̄ -> sin(x̄[1])*cos(x̄[2])) == map(x̄->sin(x̄[1])*cos(x̄[2]), g) + + @test eval_on(g, π) == fill(π, (5,3)) + + # Vector valued function + @test eval_on(g, x̄ -> @SVector[x̄[2], x̄[1]]) isa LazyArray{SVector{2,Float64}} + @test eval_on(g, x̄ -> @SVector[x̄[2], x̄[1]]) == map(x̄ -> @SVector[x̄[2], x̄[1]], g) + + # Multi-argument functions + f(x,y) = sin(x)*cos(y) + @test eval_on(g, f) == map(x̄->f(x̄...), g) +end + +@testset "componentview" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + + @test componentview(v, 1, 1) isa AbstractArray + @test componentview(v, 1, :) isa AbstractArray + + A = @SMatrix[ + 1 4 7; + 2 5 8; + 3 6 9; + ] + v = [A .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test componentview(v, 2:3, 1:2) isa AbstractArray + + # Correctness of the result is tested in ArrayComponentView +end + +@testset "ArrayComponentView" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + + @testset "==" begin + @test ArrayComponentView(v, (1,1)) == ArrayComponentView(v, (1,1)) + @test ArrayComponentView(v, (1,1)) == ArrayComponentView(copy(v), (1,1)) + @test ArrayComponentView(v, (1,1)) == [1 .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test [1 .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] == ArrayComponentView(v, (1,1)) + end + + @testset "components" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + + @test ArrayComponentView(v, (1, 1)) == [1 .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (1, 2)) == [3 .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (2, 1)) == [2 .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + + @test ArrayComponentView(v, (1, :)) == [@SVector[1,3] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (2, :)) == [@SVector[2,4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (:, 1)) == [@SVector[1,2] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (:, 2)) == [@SVector[3,4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + + + A = @SMatrix[ + 1 4 7; + 2 5 8; + 3 6 9; + ] + v = [A .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (1:2, 1:2)) == [@SMatrix[1 4;2 5] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + @test ArrayComponentView(v, (2:3, 1:2)) == [@SMatrix[2 5;3 6] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + end + + @testset "size" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + cv = ArrayComponentView(v, (1, 1)) + + @test size(cv) == (3,4) + end + + @testset "IndexStyle" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + cv = ArrayComponentView(v, (1, 1)) + + @test IndexStyle(cv) == IndexStyle(v) + end + + @testset "_array_type" begin + v = [@SMatrix[1 3; 2 4] .+ 100*i .+ 10*j for i ∈ 1:3, j∈ 1:4] + cv = ArrayComponentView(v, (1, 1)) + + @test Grids._array_type(cv) == typeof(v) + end +end + +@testset "_ncomponents" begin + @test Grids._ncomponents(Int) == 1 + @test Grids._ncomponents(Float64) == 1 + @test Grids._ncomponents(Rational) == 1 + + @test Grids._ncomponents(SVector{3,Int}) == 3 + @test Grids._ncomponents(SVector{2,Float64}) == 2 + @test Grids._ncomponents(SVector{4,Rational}) == 4 +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/manifolds_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,217 @@ +using Test + +using Diffinitive.Grids +using Diffinitive.RegionIndices +using Diffinitive.LazyTensors + +using StaticArrays + +west = CartesianBoundary{1,LowerBoundary} +east = CartesianBoundary{1,UpperBoundary} +south = CartesianBoundary{2,LowerBoundary} +north = CartesianBoundary{2,UpperBoundary} +bottom = CartesianBoundary{3, LowerBoundary} +top = CartesianBoundary{3, UpperBoundary} + +@testset "Chart" begin + X(ξ) = 2ξ + Grids.jacobian(::typeof(X), ξ) = @SMatrix[2 0; 0 2] + c = Chart(X, unitsquare()) + @test c isa Chart{2} + @test parameterspace(c) == unitsquare() + @test ndims(c) == 2 + + @testset "Calling" begin + c = Chart(X, unitsquare()) + @test c([.3,.2]) == [.6,.4] + @test_throws DomainError c([3,2]) + end + + @testset "jacobian(::Chart, ⋅)" begin + c = Chart(X, unitsquare()) + @test_throws DomainError jacobian(c, [3,2]) + @test jacobian(c, [.3,.2]) == [2 0; 0 2] + end + + @test Set(boundary_identifiers(Chart(X,unitsquare()))) == Set([east(),west(),south(),north()]) +end + +@testset "CartesianAtlas" begin + @testset "Constructors" begin + c = Chart(identity, unitsquare()) + @test CartesianAtlas([c c; c c]) isa Atlas + + c2 = Chart(x->2x, unitsquare()) + @test CartesianAtlas([c c2; c2 c]) isa CartesianAtlas + @test CartesianAtlas(@SMatrix[c c; c c]) isa CartesianAtlas + @test CartesianAtlas(@SMatrix[c c2; c2 c]) isa CartesianAtlas + end + + @testset "Getters" begin + c = Chart(identity, unitsquare()) + a = CartesianAtlas([c c; c c]) + @test charts(a) == [c c; c c] + end + + @testset "connections" begin + # 2D + a = CartesianAtlas(fill(Chart(identity, unitsquare()), 2,3)) + + @test Set(connections(a)) == Set([ + (MultiBlockBoundary{(1,1), east}(), MultiBlockBoundary{(2,1), west}()), + (MultiBlockBoundary{(1,1), north}(), MultiBlockBoundary{(1,2), south}()), + (MultiBlockBoundary{(2,1), north}(), MultiBlockBoundary{(2,2), south}()), + (MultiBlockBoundary{(1,2), east}(), MultiBlockBoundary{(2,2), west}()), + (MultiBlockBoundary{(1,2), north}(), MultiBlockBoundary{(1,3), south}()), + (MultiBlockBoundary{(2,2), north}(), MultiBlockBoundary{(2,3), south}()), + (MultiBlockBoundary{(1,3), east}(), MultiBlockBoundary{(2,3), west}()), + ]) + + # 3D + a = CartesianAtlas(fill(Chart(identity, unitcube()), 2,2,3)) + @test Set(connections(a)) == Set([ + (MultiBlockBoundary{(1,1,1), east}(), MultiBlockBoundary{(2,1,1), west}()), + (MultiBlockBoundary{(1,1,1), north}(), MultiBlockBoundary{(1,2,1), south}()), + (MultiBlockBoundary{(2,1,1), north}(), MultiBlockBoundary{(2,2,1), south}()), + (MultiBlockBoundary{(1,2,1), east}(), MultiBlockBoundary{(2,2,1), west}()), + + (MultiBlockBoundary{(1,1,2), east}(), MultiBlockBoundary{(2,1,2), west}()), + (MultiBlockBoundary{(1,1,2), north}(), MultiBlockBoundary{(1,2,2), south}()), + (MultiBlockBoundary{(2,1,2), north}(), MultiBlockBoundary{(2,2,2), south}()), + (MultiBlockBoundary{(1,2,2), east}(), MultiBlockBoundary{(2,2,2), west}()), + + (MultiBlockBoundary{(1,1,3), east}(), MultiBlockBoundary{(2,1,3), west}()), + (MultiBlockBoundary{(1,1,3), north}(), MultiBlockBoundary{(1,2,3), south}()), + (MultiBlockBoundary{(2,1,3), north}(), MultiBlockBoundary{(2,2,3), south}()), + (MultiBlockBoundary{(1,2,3), east}(), MultiBlockBoundary{(2,2,3), west}()), + + (MultiBlockBoundary{(1,1,1), top}(), MultiBlockBoundary{(1,1,2), bottom}()), + (MultiBlockBoundary{(2,1,1), top}(), MultiBlockBoundary{(2,1,2), bottom}()), + (MultiBlockBoundary{(1,2,1), top}(), MultiBlockBoundary{(1,2,2), bottom}()), + (MultiBlockBoundary{(2,2,1), top}(), MultiBlockBoundary{(2,2,2), bottom}()), + + (MultiBlockBoundary{(1,1,2), top}(), MultiBlockBoundary{(1,1,3), bottom}()), + (MultiBlockBoundary{(2,1,2), top}(), MultiBlockBoundary{(2,1,3), bottom}()), + (MultiBlockBoundary{(1,2,2), top}(), MultiBlockBoundary{(1,2,3), bottom}()), + (MultiBlockBoundary{(2,2,2), top}(), MultiBlockBoundary{(2,2,3), bottom}()), + ]) + end + + @testset "boundary_identifiers" begin + # 2D + a = CartesianAtlas(fill(Chart(identity, unitcube()), 2,3)) + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{(1,1), south}(), + MultiBlockBoundary{(2,1), south}(), + MultiBlockBoundary{(2,1), east}(), + MultiBlockBoundary{(2,2), east}(), + MultiBlockBoundary{(2,3), east}(), + MultiBlockBoundary{(1,3), north}(), + MultiBlockBoundary{(2,3), north}(), + MultiBlockBoundary{(1,1), west}(), + MultiBlockBoundary{(1,2), west}(), + MultiBlockBoundary{(1,3), west}(), + ]) + + # 3D + a = CartesianAtlas(fill(Chart(identity, unitsquare()), 2,2,3)) + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{(1,1,1), bottom}(), + MultiBlockBoundary{(2,1,1), bottom}(), + MultiBlockBoundary{(1,2,1), bottom}(), + MultiBlockBoundary{(2,2,1), bottom}(), + + MultiBlockBoundary{(1,1,3), top}(), + MultiBlockBoundary{(2,1,3), top}(), + MultiBlockBoundary{(1,2,3), top}(), + MultiBlockBoundary{(2,2,3), top}(), + + MultiBlockBoundary{(1,1,1), west}(), + MultiBlockBoundary{(1,2,1), west}(), + MultiBlockBoundary{(1,1,2), west}(), + MultiBlockBoundary{(1,2,2), west}(), + MultiBlockBoundary{(1,1,3), west}(), + MultiBlockBoundary{(1,2,3), west}(), + + MultiBlockBoundary{(2,1,1), east}(), + MultiBlockBoundary{(2,2,1), east}(), + MultiBlockBoundary{(2,1,2), east}(), + MultiBlockBoundary{(2,2,2), east}(), + MultiBlockBoundary{(2,1,3), east}(), + MultiBlockBoundary{(2,2,3), east}(), + + MultiBlockBoundary{(1,1,1), south}(), + MultiBlockBoundary{(2,1,1), south}(), + MultiBlockBoundary{(1,1,2), south}(), + MultiBlockBoundary{(2,1,2), south}(), + MultiBlockBoundary{(1,1,3), south}(), + MultiBlockBoundary{(2,1,3), south}(), + + MultiBlockBoundary{(1,2,1), north}(), + MultiBlockBoundary{(2,2,1), north}(), + MultiBlockBoundary{(1,2,2), north}(), + MultiBlockBoundary{(2,2,2), north}(), + MultiBlockBoundary{(1,2,3), north}(), + MultiBlockBoundary{(2,2,3), north}(), + ]) + end +end + +@testset "UnstructuredAtlas" begin + @testset "Constructors" begin + c1 = Chart(identity, unitsquare()) + c2 = Chart(x->2x, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + @test UnstructuredAtlas([c1, c1, c1], cn) isa UnstructuredAtlas + @test UnstructuredAtlas([c1, c2, c1, c2], cn) isa UnstructuredAtlas + + + cn = @SVector[ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + @test UnstructuredAtlas(@SVector[c1, c1, c1], cn) isa UnstructuredAtlas + @test UnstructuredAtlas(@SVector[c1, c2, c1, c2], cn) isa UnstructuredAtlas + end + + @testset "Getters" begin + c = Chart(identity, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + a = UnstructuredAtlas([c, c, c], cn) + + @test charts(a) == [c,c,c] + @test connections(a) == cn + end + + @testset "boundary_identifiers" begin + c = Chart(identity, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + a = UnstructuredAtlas([c, c, c], cn) + + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{1, west}(), + MultiBlockBoundary{1, south}(), + MultiBlockBoundary{2, south}(), + MultiBlockBoundary{2, east}(), + MultiBlockBoundary{3, north}(), + MultiBlockBoundary{3, east}(), + ]) + + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/mapped_grid_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,383 @@ +using Diffinitive.Grids +using Diffinitive.RegionIndices +using Test +using StaticArrays +using LinearAlgebra + + +_skew_mapping(a,b) = (ξ̄->ξ̄[1]*a + ξ̄[2]*b, ξ̄->[a b]) + +function _partially_curved_mapping() + x̄((ξ, η)) = @SVector[ξ, η*(1+ξ*(ξ-1))] + J((ξ, η)) = @SMatrix[ + 1 0; + η*(2ξ-1) 1+ξ*(ξ-1); + ] + + return x̄, J +end + +function _fully_curved_mapping() + x̄((ξ, η)) = @SVector[2ξ + η*(1-η), 3η+(1+η/2)*ξ^2] + J((ξ, η)) = @SMatrix[ + 2 1-2η; + (2+η)*ξ 3+1/2*ξ^2; + ] + + return x̄, J +end + +@testset "MappedGrid" begin + @testset "Constructor" begin + lg = equidistant_grid((0,0), (1,1), 11, 21) + + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + mg = MappedGrid(lg, x̄, J) + + @test mg isa Grid{SVector{2, Float64},2} + @test jacobian(mg) isa Array{<:AbstractMatrix} + @test logical_grid(mg) isa Grid + + @test collect(mg) == x̄ + @test jacobian(mg) == J + @test logical_grid(mg) == lg + + + x̄ = map(ξ̄ -> @SVector[ξ̄[1],ξ̄[2], ξ̄[1] + ξ̄[2]], lg) + J = map(ξ̄ -> @SMatrix[1 0; 0 1; 1 1], lg) + mg = MappedGrid(lg, x̄, J) + + @test mg isa Grid{SVector{3, Float64},2} + @test jacobian(mg) isa Array{<:AbstractMatrix} + @test logical_grid(mg) isa Grid + + @test collect(mg) == x̄ + @test jacobian(mg) == J + @test logical_grid(mg) == lg + + sz1 = (10,11) + sz2 = (10,12) + @test_throws ArgumentError("Sizes must match") MappedGrid( + equidistant_grid((0,0), (1,1), sz2...), + rand(SVector{2},sz1...), + rand(SMatrix{2,2},sz1...), + ) + + @test_throws ArgumentError("Sizes must match") MappedGrid( + equidistant_grid((0,0), (1,1), sz1...), + rand(SVector{2},sz2...), + rand(SMatrix{2,2},sz1...), + ) + + @test_throws ArgumentError("Sizes must match") MappedGrid( + equidistant_grid((0,0), (1,1), sz1...), + rand(SVector{2},sz1...), + rand(SMatrix{2,2},sz2...), + ) + + err_str = "The size of the jacobian must match the dimensions of the grid and coordinates" + @test_throws ArgumentError(err_str) MappedGrid( + equidistant_grid((0,0), (1,1), 10, 11), + rand(SVector{3}, 10, 11), + rand(SMatrix{3,4}, 10, 11), + ) + + @test_throws ArgumentError(err_str) MappedGrid( + equidistant_grid((0,0), (1,1), 10, 11), + rand(SVector{3}, 10, 11), + rand(SMatrix{4,2}, 10, 11), + ) + end + + @testset "Indexing Interface" begin + lg = equidistant_grid((0,0), (1,1), 11, 21) + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + mg = MappedGrid(lg, x̄, J) + @test mg[1,1] == [0.0, 0.0] + @test mg[4,2] == [0.6, 0.1] + @test mg[6,10] == [1., 0.9] + + @test mg[begin, begin] == [0.0, 0.0] + @test mg[end,end] == [2.0, 2.0] + @test mg[begin,end] == [0., 2.] + + @test axes(mg) == (1:11, 1:21) + + @testset "cartesian indexing" begin + cases = [ + (1,1) , + (3,5) , + (10,6), + (1,1) , + (3,2) , + ] + + @testset "i = $is" for (lg, is) ∈ cases + @test mg[CartesianIndex(is...)] == mg[is...] + end + end + + @testset "eachindex" begin + @test eachindex(mg) == CartesianIndices((11,21)) + end + + @testset "firstindex" begin + @test firstindex(mg, 1) == 1 + @test firstindex(mg, 2) == 1 + end + + @testset "lastindex" begin + @test lastindex(mg, 1) == 11 + @test lastindex(mg, 2) == 21 + end + end + + @testset "Iterator interface" begin + lg = equidistant_grid((0,0), (1,1), 11, 21) + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + + mg = MappedGrid(lg, x̄, J) + + lg2 = equidistant_grid((0,0), (1,1), 15, 11) + sg = MappedGrid( + equidistant_grid((0,0), (1,1), 15, 11), + map(ξ̄ -> @SArray[ξ̄[1], ξ̄[2], -ξ̄[1]], lg2), rand(SMatrix{3,2,Float64},15,11) + ) + + @test eltype(mg) == SVector{2,Float64} + @test eltype(sg) == SVector{3,Float64} + + @test eltype(typeof(mg)) == SVector{2,Float64} + @test eltype(typeof(sg)) == SVector{3,Float64} + + @test size(mg) == (11,21) + @test size(sg) == (15,11) + + @test size(mg,2) == 21 + @test size(sg,2) == 11 + + @test length(mg) == 231 + @test length(sg) == 165 + + @test Base.IteratorSize(mg) == Base.HasShape{2}() + @test Base.IteratorSize(typeof(mg)) == Base.HasShape{2}() + + @test Base.IteratorSize(sg) == Base.HasShape{2}() + @test Base.IteratorSize(typeof(sg)) == Base.HasShape{2}() + + element, state = iterate(mg) + @test element == lg[1,1].*2 + element, _ = iterate(mg, state) + @test element == lg[2,1].*2 + + element, state = iterate(sg) + @test element == sg.physicalcoordinates[1,1] + element, _ = iterate(sg, state) + @test element == sg.physicalcoordinates[2,1] + + @test collect(mg) == 2 .* lg + end + + @testset "Base" begin + lg = equidistant_grid((0,0), (1,1), 11, 21) + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + mg = MappedGrid(lg, x̄, J) + + @test ndims(mg) == 2 + end + + @testset "==" begin + sz = (15,11) + lg = equidistant_grid((0,0), (1,1), sz...) + x = rand(SVector{3,Float64}, sz...) + J = rand(SMatrix{3,2,Float64}, sz...) + + sg = MappedGrid(lg, x, J) + + sg1 = MappedGrid(equidistant_grid((0,0), (1,1), sz...), copy(x), copy(J)) + + sz2 = (15,12) + lg2 = equidistant_grid((0,0), (1,1), sz2...) + x2 = rand(SVector{3,Float64}, sz2...) + J2 = rand(SMatrix{3,2,Float64}, sz2...) + sg2 = MappedGrid(lg2, x2, J2) + + sg3 = MappedGrid(lg, rand(SVector{3,Float64}, sz...), J) + sg4 = MappedGrid(lg, x, rand(SMatrix{3,2,Float64}, sz...)) + + @test sg == sg1 + @test sg != sg2 # Different size + @test sg != sg3 # Different coordinates + @test sg != sg4 # Different jacobian + end + + @testset "boundary_identifiers" begin + lg = equidistant_grid((0,0), (1,1), 11, 15) + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + mg = MappedGrid(lg, x̄, J) + @test boundary_identifiers(mg) == boundary_identifiers(lg) + end + + @testset "boundary_indices" begin + lg = equidistant_grid((0,0), (1,1), 11, 15) + x̄ = map(ξ̄ -> 2ξ̄, lg) + J = map(ξ̄ -> @SArray(fill(2., 2, 2)), lg) + mg = MappedGrid(lg, x̄, J) + + @test boundary_indices(mg, CartesianBoundary{1,LowerBoundary}()) == boundary_indices(lg,CartesianBoundary{1,LowerBoundary}()) + @test boundary_indices(mg, CartesianBoundary{2,LowerBoundary}()) == boundary_indices(lg,CartesianBoundary{2,LowerBoundary}()) + @test boundary_indices(mg, CartesianBoundary{1,UpperBoundary}()) == boundary_indices(lg,CartesianBoundary{1,UpperBoundary}()) + end + + @testset "boundary_grid" begin + x̄, J = _partially_curved_mapping() + mg = mapped_grid(x̄, J, 10, 11) + J1((ξ, η)) = @SMatrix[ + 1 ; + η*(2ξ-1); + ] + J2((ξ, η)) = @SMatrix[ + 0; + 1+ξ*(ξ-1); + ] + + function expected_bg(mg, bId, Jb) + lg = logical_grid(mg) + return MappedGrid( + boundary_grid(lg, bId), + map(x̄, boundary_grid(lg, bId)), + map(Jb, boundary_grid(lg, bId)), + ) + end + + let bid = TensorGridBoundary{1, LowerBoundary}() + @test boundary_grid(mg, bid) == expected_bg(mg, bid, J2) + end + + let bid = TensorGridBoundary{1, UpperBoundary}() + @test boundary_grid(mg, bid) == expected_bg(mg, bid, J2) + end + + let bid = TensorGridBoundary{2, LowerBoundary}() + @test boundary_grid(mg, bid) == expected_bg(mg, bid, J1) + end + + let bid = TensorGridBoundary{2, UpperBoundary}() + @test boundary_grid(mg, bid) == expected_bg(mg, bid, J1) + end + end +end + +@testset "mapped_grid" begin + x̄, J = _partially_curved_mapping() + mg = mapped_grid(x̄, J, 10, 11) + @test mg isa MappedGrid{SVector{2,Float64}, 2} + + lg = equidistant_grid(unitsquare(), 10, 11) + @test logical_grid(mg) == lg + @test collect(mg) == map(x̄, lg) + + @test mapped_grid(x̄, J, lg) == mg + + @test mapped_grid(x̄, J, unitsquare(), 10, 11) == mg +end + +@testset "metric_tensor" begin + x̄((ξ, η)) = @SVector[ξ*η, ξ + η^2] + J((ξ, η)) = @SMatrix[ + η ξ; + 1 2η; + ] + + g = mapped_grid(x̄, J, 10, 11) + G = map(logical_grid(g)) do (ξ,η) + @SMatrix[ + 1+η^2 ξ*η+2η; + ξ*η+2η ξ^2 + 4η^2; + ] + end + @test metric_tensor(g) ≈ G +end + +@testset "min_spacing" begin + let g = mapped_grid(identity, x->@SMatrix[1], 11) + @test min_spacing(g) ≈ 0.1 + end + + let g = mapped_grid(x->x+x.^2/2, x->@SMatrix[1 .+ x], 11) + @test min_spacing(g) ≈ 0.105 + end + + let g = mapped_grid(x->x + x.*(1 .- x)/2, x->@SMatrix[1.5 .- x], 11) + @test min_spacing(g) ≈ 0.055 + end + + let g = mapped_grid(identity, x->@SMatrix[1 0; 0 1], 11,11) + @test min_spacing(g) ≈ 0.1 + end + + let g = mapped_grid(identity, x->@SMatrix[1 0; 0 1], 11,21) + @test min_spacing(g) ≈ 0.05 + end + + + @testset let a = @SVector[1,0], b = @SVector[1,1]/√2 + g = mapped_grid(_skew_mapping(a,b)...,11,11) + + @test min_spacing(g) ≈ 0.1*norm(b-a) + end + + @testset let a = @SVector[1,0], b = @SVector[-1,1]/√2 + g = mapped_grid(_skew_mapping(a,b)...,11,11) + + @test min_spacing(g) ≈ 0.1*norm(a+b) + end +end + +@testset "normal" begin + g = mapped_grid(_partially_curved_mapping()...,10, 11) + + @test normal(g, CartesianBoundary{1,LowerBoundary}()) == fill(@SVector[-1,0], 11) + @test normal(g, CartesianBoundary{1,UpperBoundary}()) == fill(@SVector[1,0], 11) + @test normal(g, CartesianBoundary{2,LowerBoundary}()) == fill(@SVector[0,-1], 10) + @test normal(g, CartesianBoundary{2,UpperBoundary}()) ≈ map(boundary_grid(g,CartesianBoundary{2,UpperBoundary}())|>logical_grid) do ξ̄ + α = 1-2ξ̄[1] + @SVector[α,1]/√(α^2 + 1) + end + + g = mapped_grid(_fully_curved_mapping()...,5,4) + + unit(v) = v/norm(v) + @testset let bId = CartesianBoundary{1,LowerBoundary}() + lbg = boundary_grid(logical_grid(g), bId) + @test normal(g, bId) ≈ map(lbg) do (ξ, η) + -unit(@SVector[1/2, η/3-1/6]) + end + end + + @testset let bId = CartesianBoundary{1,UpperBoundary}() + lbg = boundary_grid(logical_grid(g), bId) + @test normal(g, bId) ≈ map(lbg) do (ξ, η) + unit(@SVector[7/2, 2η-1]/(5 + 3η + 2η^2)) + end + end + + @testset let bId = CartesianBoundary{2,LowerBoundary}() + lbg = boundary_grid(logical_grid(g), bId) + @test normal(g, bId) ≈ map(lbg) do (ξ, η) + -unit(@SVector[-2ξ, 2]/(6 + ξ^2 - 2ξ)) + end + end + + @testset let bId = CartesianBoundary{2,UpperBoundary}() + lbg = boundary_grid(logical_grid(g), bId) + @test normal(g, bId) ≈ map(lbg) do (ξ, η) + unit(@SVector[-3ξ, 2]/(6 + ξ^2 + 3ξ)) + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/multiblockgrids_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,9 @@ +using Diffinitive.Grids + +@testset "MultiBlockBoundary" begin + @test MultiBlockBoundary{1,UpperBoundary}() isa BoundaryIdentifier + + @test grid_id(MultiBlockBoundary{1,UpperBoundary}()) == 1 + + @test boundary_id(MultiBlockBoundary{1,UpperBoundary}()) == UpperBoundary() +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/parameter_space_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,123 @@ +using Test + +using Diffinitive.Grids +using StaticArrays + +@testset "ParameterSpace" begin + @test ndims(HyperBox([1,1], [2,2])) == 2 + @test ndims(unittetrahedron()) == 3 +end + +@testset "Interval" begin + @test Interval <: ParameterSpace{1} + + @test Interval(0,1) isa Interval{Int} + @test Interval(0,1.) isa Interval{Float64} + + @test unitinterval() isa Interval{Float64} + @test unitinterval() == Interval(0.,1.) + @test limits(unitinterval()) == (0.,1.) + + @test unitinterval(Int) isa Interval{Int} + @test unitinterval(Int) == Interval(0,1) + @test limits(unitinterval(Int)) == (0,1) + + @test boundary_identifiers(unitinterval()) == (LowerBoundary(), UpperBoundary()) + + @test 0 ∈ Interval(0,1) + @test 0. ∈ Interval(0,1) + @test 1. ∈ Interval(0,1) + @test 2 ∉ Interval(0,1) + @test -1 ∉ Interval(0,1) + @test -1. ∉ Interval(0,1) +end + +@testset "HyperBox" begin + @test HyperBox{<:Any, 2} <: ParameterSpace{2} + @test HyperBox([1,1], [2,2]) isa HyperBox{Int, 2} + + @test HyperBox([1,2], [1.,2.]) isa HyperBox{Float64,2} + + @test limits(HyperBox([1,2], [3,4])) == ([1,2], [3,4]) + @test limits(HyperBox([1,2], [3,4]), 1) == (1,3) + @test limits(HyperBox([1,2], [3,4]), 2) == (2,4) + + @test unitsquare() isa HyperBox{Float64,2} + @test limits(unitsquare()) == ([0,0],[1,1]) + + @test unitcube() isa HyperBox{Float64,3} + @test limits(unitcube()) == ([0,0,0],[1,1,1]) + + @test unithyperbox(4) isa HyperBox{Float64,4} + @test limits(unithyperbox(4)) == ([0,0,0,0],[1,1,1,1]) + + + @test boundary_identifiers(unitsquare()) == [ + CartesianBoundary{1,LowerBoundary}(), + CartesianBoundary{1,UpperBoundary}(), + CartesianBoundary{2,LowerBoundary}(), + CartesianBoundary{2,UpperBoundary}(), + ] + + @test boundary_identifiers(unitcube()) == [ + CartesianBoundary{1,LowerBoundary}(), + CartesianBoundary{1,UpperBoundary}(), + CartesianBoundary{2,LowerBoundary}(), + CartesianBoundary{2,UpperBoundary}(), + CartesianBoundary{3,LowerBoundary}(), + CartesianBoundary{3,UpperBoundary}(), + ] + + @test @SVector[1.5, 3.5] ∈ HyperBox([1,2], [3,4]) + @test @SVector[1, 2] ∈ HyperBox([1,2], [3,4]) + @test @SVector[3, 4] ∈ HyperBox([1,2], [3,4]) + + @test @SVector[0.5, 3.5] ∉ HyperBox([1,2], [3,4]) + @test @SVector[1.5, 4.5] ∉ HyperBox([1,2], [3,4]) +end + +@testset "Simplex" begin + @test Simplex{<:Any, 3} <: ParameterSpace{3} + @test Simplex([1,2], [3,4]) isa Simplex{Int, 2} + @test Simplex([1,2,3], [4,5,6],[1,1,1]) isa Simplex{Int, 3} + + @test Simplex([1,2], [3.,4.]) isa Simplex{Float64, 2} + + @test verticies(Simplex([1,2], [3,4])) == ([1,2], [3,4]) + + @test unittriangle() isa Simplex{Float64,2} + @test verticies(unittriangle()) == ([0,0], [1,0], [0,1]) + + @test unittetrahedron() isa Simplex{Float64,3} + @test verticies(unittetrahedron()) == ([0,0,0], [1,0,0], [0,1,0],[0,0,1]) + + @test unitsimplex(4) isa Simplex{Float64,4} + + @testset "Base.in" begin + @testset "2D" begin + T₂ = Simplex([0.0, 0.0], [1.0, 0.0], [0.0, 1.0]) + @test [0.1, 0.1] ∈ T₂ + @test [0.3, 0.3] ∈ T₂ + @test [1.0, 0.0] ∈ T₂ + @test [0.0, 0.0] ∈ T₂ + @test [0.0, 1.0] ∈ T₂ + @test [0.5, 0.5] ∈ T₂ + + @test [0.6, 0.6] ∉ T₂ + @test [-0.1, 0.1] ∉ T₂ + end + + @testset "3D" begin + T₃ = Simplex([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]) + @test [0.1, 0.1, 0.1] ∈ T₃ + @test [0.0, 0.0, 0.0] ∈ T₃ + @test [1.0, 0.0, 0.0] ∈ T₃ + @test [0.25, 0.25, 0.25] ∈ T₃ + @test [0.5, 0.5, 0.0] ∈ T₃ + @test [0.3, 0.3, 0.3] ∈ T₃ + + @test [0.5, 0.5, 1.0] ∉ T₃ + @test [0.3, 0.3, 0.5] ∉ T₃ + end + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/tensor_grid_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,275 @@ +using Test +using Diffinitive.Grids +using StaticArrays + +@testset "TensorGrid" begin + g₁ = EquidistantGrid(range(0,1,length=11)) + g₂ = EquidistantGrid(range(2,3,length=6)) + g₃ = EquidistantGrid(1:10) + g₄ = ZeroDimGrid(@SVector[1,2]) + + @test TensorGrid(g₁, g₂) isa TensorGrid + @test TensorGrid(g₁, g₂) isa Grid{SVector{2,Float64}, 2} + @test TensorGrid(g₃, g₃) isa Grid{SVector{2,Int}, 2} + @test TensorGrid(g₁, g₂, g₃) isa Grid{SVector{3,Float64}, 3} + @test TensorGrid(g₁, g₄) isa Grid{SVector{3,Float64}, 1} + @test TensorGrid(g₁, g₄, g₂) isa Grid{SVector{4,Float64}, 2} + + @testset "Indexing Interface" begin + @testset "regular indexing" begin + @test TensorGrid(g₁, g₂)[1,1] isa SVector{2,Float64} + @test TensorGrid(g₁, g₂)[1,1] == [0.0,2.0] + @test TensorGrid(g₁, g₂)[3,5] == [0.2,2.8] + @test TensorGrid(g₁, g₂)[10,6] == [0.9,3.0] + + @test TensorGrid(g₁, g₃)[1,1] isa SVector{2,Float64} + @test TensorGrid(g₁, g₃)[1,1] == [0.0,1.0] + + @test TensorGrid(g₁, g₂, g₃)[3,4,5] isa SVector{3,Float64} + @test TensorGrid(g₁, g₂, g₃)[3,4,5] == [0.2, 2.6, 5.0] + + @test TensorGrid(g₁, g₄)[3] isa SVector{3,Float64} + @test TensorGrid(g₁, g₄)[3] == [0.2, 1., 2.] + + @test TensorGrid(g₁, g₄, g₂)[3,2] isa SVector{4,Float64} + @test TensorGrid(g₁, g₄, g₂)[3,2] == [0.2, 1., 2., 2.2] + + g = TensorGrid(g₁, g₂) + @test g[begin, begin] == g[1,1] + @test g[begin, end] == g[1,6] + @test g[end, end] == g[11,6] + end + + @testset "cartesian indexing" begin + cases = [ + (TensorGrid(g₁, g₂), (1,1) ), + (TensorGrid(g₁, g₂), (3,5) ), + (TensorGrid(g₁, g₂), (10,6) ), + (TensorGrid(g₁, g₃), (1,1) ), + (TensorGrid(g₁, g₂, g₃), (3,4,5)), + (TensorGrid(g₁, g₄), (3) ), + (TensorGrid(g₁, g₄, g₂), (3,2) ), + ] + + @testset "i = $is" for (g, is) ∈ cases + @test g[CartesianIndex(is...)] == g[is...] + end + end + + @testset "eachindex" begin + @test eachindex(TensorGrid(g₁, g₂)) == CartesianIndices((11,6)) + @test eachindex(TensorGrid(g₁, g₃)) == CartesianIndices((11,10)) + @test eachindex(TensorGrid(g₁, g₂, g₃)) == CartesianIndices((11,6,10)) + @test eachindex(TensorGrid(g₁, g₄)) == CartesianIndices((11,)) + @test eachindex(TensorGrid(g₁, g₄, g₂)) == CartesianIndices((11,6)) + end + + @testset "firstindex" begin + @test firstindex(TensorGrid(g₁, g₂, g₃), 1) == 1 + @test firstindex(TensorGrid(g₁, g₂, g₃), 2) == 1 + @test firstindex(TensorGrid(g₁, g₂, g₃), 3) == 1 + end + + @testset "lastindex" begin + @test lastindex(TensorGrid(g₁, g₂, g₃), 1) == 11 + @test lastindex(TensorGrid(g₁, g₂, g₃), 2) == 6 + @test lastindex(TensorGrid(g₁, g₂, g₃), 3) == 10 + end + end + + @testset "Iterator interface" begin + @test eltype(TensorGrid(g₁, g₂)) == SVector{2,Float64} + @test eltype(TensorGrid(g₁, g₃)) == SVector{2,Float64} + @test eltype(TensorGrid(g₁, g₂, g₃)) == SVector{3,Float64} + @test eltype(TensorGrid(g₁, g₄)) == SVector{3,Float64} + @test eltype(TensorGrid(g₁, g₄, g₂)) == SVector{4,Float64} + + @test eltype(typeof(TensorGrid(g₁, g₂))) == SVector{2,Float64} + @test eltype(typeof(TensorGrid(g₁, g₃))) == SVector{2,Float64} + @test eltype(typeof(TensorGrid(g₁, g₂, g₃))) == SVector{3,Float64} + @test eltype(typeof(TensorGrid(g₁, g₄))) == SVector{3,Float64} + @test eltype(typeof(TensorGrid(g₁, g₄, g₂))) == SVector{4,Float64} + + @test size(TensorGrid(g₁, g₂)) == (11,6) + @test size(TensorGrid(g₁, g₃)) == (11,10) + @test size(TensorGrid(g₁, g₂, g₃)) == (11,6,10) + @test size(TensorGrid(g₁, g₄)) == (11,) + @test size(TensorGrid(g₁, g₄, g₂)) == (11,6) + + @test size(TensorGrid(g₁, g₂, g₃),1) == 11 + @test size(TensorGrid(g₁, g₂, g₃),2) == 6 + @test size(TensorGrid(g₁, g₂, g₃),3) == 10 + @test size(TensorGrid(g₁, g₄, g₂),1) == 11 + @test size(TensorGrid(g₁, g₄, g₂),2) == 6 + + @test length(TensorGrid(g₁, g₂)) == 66 + @test length(TensorGrid(g₁, g₃)) == 110 + @test length(TensorGrid(g₁, g₂, g₃)) == 660 + @test length(TensorGrid(g₁, g₄)) == 11 + @test length(TensorGrid(g₁, g₄, g₂)) == 66 + + @test Base.IteratorSize(TensorGrid(g₁, g₂)) == Base.HasShape{2}() + @test Base.IteratorSize(TensorGrid(g₁, g₃)) == Base.HasShape{2}() + @test Base.IteratorSize(TensorGrid(g₁, g₂, g₃)) == Base.HasShape{3}() + @test Base.IteratorSize(TensorGrid(g₁, g₄)) == Base.HasShape{1}() + @test Base.IteratorSize(TensorGrid(g₁, g₄, g₂)) == Base.HasShape{2}() + + @test iterate(TensorGrid(g₁, g₂))[1] isa SVector{2,Float64} + @test iterate(TensorGrid(g₁, g₃))[1] isa SVector{2,Float64} + @test iterate(TensorGrid(g₁, g₂, g₃))[1] isa SVector{3,Float64} + @test iterate(TensorGrid(g₁, g₄))[1] isa SVector{3,Float64} + @test iterate(TensorGrid(g₁, g₄, g₂))[1] isa SVector{4,Float64} + + @test collect(TensorGrid(g₁, g₂)) == [@SVector[x,y] for x ∈ range(0,1,length=11), y ∈ range(2,3,length=6)] + @test collect(TensorGrid(g₁, g₃)) == [@SVector[x,y] for x ∈ range(0,1,length=11), y ∈ 1:10] + @test collect(TensorGrid(g₁, g₂, g₃)) == [@SVector[x,y,z] for x ∈ range(0,1,length=11), y ∈ range(2,3,length=6), z ∈ 1:10] + @test collect(TensorGrid(g₁, g₄)) == [@SVector[x,1,2] for x ∈ range(0,1,length=11)] + @test collect(TensorGrid(g₁, g₄, g₂)) == [@SVector[x,1,2,y] for x ∈ range(0,1,length=11), y ∈ range(2,3,length=6)] + end + + @testset "Base" begin + g₁ = EquidistantGrid(range(0,1,length=11)) + g₂ = EquidistantGrid(range(2,3,length=6)) + g = TensorGrid(g₁, g₂) + + @test axes(g, 1) == 1:11 + @test axes(g, 2) == 1:6 + @test axes(g) == (1:11,1:6) + end + + @testset "spacing" begin + g₁ = EquidistantGrid(range(0,1,length=11)) + g₂ = EquidistantGrid(range(2,3,length=6)) + g₃ = ZeroDimGrid(@SVector[1,2]) + + @test spacing(TensorGrid(g₁)) == (1/10,) + @test spacing(TensorGrid(g₂)) == (1/5,) + + @test spacing(TensorGrid(g₁, g₂)) == (1/10, 1/5) + + @test spacing(TensorGrid(g₁, g₃)) == (1/10,) + @test spacing(TensorGrid(g₃, g₂)) == (1/5,) + + + @test spacing(TensorGrid(g₁, g₂, g₁)) == (1/10, 1/5, 1/10) + + @test spacing(TensorGrid(g₃, g₂, g₁)) == (1/5, 1/10) + @test spacing(TensorGrid(g₁, g₃, g₁)) == (1/10, 1/10) + @test spacing(TensorGrid(g₁, g₂, g₃)) == (1/10, 1/5) + end + + @testset "min_spacing" begin + g₁ = EquidistantGrid(range(0,1,length=11)) + g₂ = EquidistantGrid(range(2,3,length=6)) + g₃ = ZeroDimGrid(@SVector[1,2]) + + @test min_spacing(TensorGrid(g₁, g₂)) == 1/10 + @test min_spacing(TensorGrid(g₂, g₃)) == 1/5 + end + + @testset "refine" begin + g1(n) = EquidistantGrid(range(0,1,length=n)) + g2(n) = EquidistantGrid(range(2,3,length=n)) + + @test refine(TensorGrid(g1(11), g2(6)),1) == TensorGrid(g1(11), g2(6)) + @test refine(TensorGrid(g1(11), g2(6)),2) == TensorGrid(g1(21), g2(11)) + @test refine(TensorGrid(g1(11), g2(6)),3) == TensorGrid(g1(31), g2(16)) + @test refine(TensorGrid(g1(11), g₄), 1) == TensorGrid(g1(11), g₄) + @test refine(TensorGrid(g1(11), g₄), 2) == TensorGrid(g1(21), g₄) + end + + @testset "coarsen" begin + g1(n) = EquidistantGrid(range(0,1,length=n)) + g2(n) = EquidistantGrid(range(2,3,length=n)) + + @test coarsen(TensorGrid(g1(11), g2(6)),1) == TensorGrid(g1(11), g2(6)) + @test coarsen(TensorGrid(g1(21), g2(11)),2) == TensorGrid(g1(11), g2(6)) + @test coarsen(TensorGrid(g1(31), g2(16)),3) == TensorGrid(g1(11), g2(6)) + @test coarsen(TensorGrid(g1(11), g₄), 1) == TensorGrid(g1(11), g₄) + @test coarsen(TensorGrid(g1(21), g₄), 2) == TensorGrid(g1(11), g₄) + end + + @testset "boundary_identifiers" begin + @test boundary_identifiers(TensorGrid(g₁, g₂)) == map((n,id)->TensorGridBoundary{n,id}(), (1,1,2,2), (LowerBoundary,UpperBoundary,LowerBoundary,UpperBoundary)) + @test boundary_identifiers(TensorGrid(g₁, g₄)) == (TensorGridBoundary{1,LowerBoundary}(),TensorGridBoundary{1,UpperBoundary}()) + end + + @testset "boundary_grid" begin + @test boundary_grid(TensorGrid(g₁, g₂), TensorGridBoundary{1, UpperBoundary}()) == TensorGrid(ZeroDimGrid(g₁[end]), g₂) + @test boundary_grid(TensorGrid(g₁, g₄), TensorGridBoundary{1, UpperBoundary}()) == TensorGrid(ZeroDimGrid(g₁[end]), g₄) + end + + @testset "boundary_indices" begin + g₁ = EquidistantGrid(range(0,1,length=11)) + g₂ = EquidistantGrid(range(2,3,length=6)) + g₄ = ZeroDimGrid(@SVector[1,2]) + + gf = reshape(1:(11*6),11,6) + @test gf[boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, LowerBoundary}())] == gf[1,:] + @test gf[boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, UpperBoundary}())] == gf[11,:] + @test gf[boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, LowerBoundary}())] == gf[:,1] + @test gf[boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, UpperBoundary}())] == gf[:,6] + + gf = rand(11) + @test gf[boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, LowerBoundary}())] == gf[1] + @test gf[boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, UpperBoundary}())] == gf[11] + @test gf[boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, LowerBoundary}())] == gf[1] + @test gf[boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, UpperBoundary}())] == gf[11] + + @test collect(boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, LowerBoundary}())) == [CartesianIndex(1,i) for i ∈ 1:6] + @test collect(boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, UpperBoundary}())) == [CartesianIndex(11,i) for i ∈ 1:6] + @test collect(boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, LowerBoundary}())) == [CartesianIndex(i,1) for i ∈ 1:11] + @test collect(boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, UpperBoundary}())) == [CartesianIndex(i,6) for i ∈ 1:11] + @test collect(boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, LowerBoundary}())) == fill(1) + @test collect(boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, UpperBoundary}())) == fill(11) + @test collect(boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, LowerBoundary}())) == fill(1) + @test collect(boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, UpperBoundary}())) == fill(11) + end +end + +@testset "combined_coordinate_vector_type" begin + @test Grids.combined_coordinate_vector_type(Float64) == Float64 + @test Grids.combined_coordinate_vector_type(Float64, Int) == SVector{2,Float64} + @test Grids.combined_coordinate_vector_type(Float32, Int16, Int32) == SVector{3,Float32} + + @test Grids.combined_coordinate_vector_type(SVector{2,Float64}) == SVector{2,Float64} + @test Grids.combined_coordinate_vector_type(SVector{2,Float64}, SVector{1,Float64}) == SVector{3,Float64} + @test Grids.combined_coordinate_vector_type(SVector{2,Float64}, SVector{1,Int}, SVector{3, Float32}) == SVector{6,Float64} +end + +@testset "combine_coordinates" begin + @test Grids.combine_coordinates(1,2,3) isa SVector{3, Int} + @test Grids.combine_coordinates(1,2,3) == [1,2,3] + @test Grids.combine_coordinates(1,2.,3) isa SVector{3, Float64} + @test Grids.combine_coordinates(1,2.,3) == [1,2,3] + @test Grids.combine_coordinates(1,@SVector[2.,3]) isa SVector{3, Float64} + @test Grids.combine_coordinates(1,@SVector[2.,3]) == [1,2,3] +end + +@testset "grid_and_local_dim_index" begin + cases = [ + ((1,), 1) => (1,1), + + ((1,1), 1) => (1,1), + ((1,1), 2) => (2,1), + + ((1,2), 1) => (1,1), + ((1,2), 2) => (2,1), + ((1,2), 3) => (2,2), + + ((2,1), 1) => (1,1), + ((2,1), 2) => (1,2), + ((2,1), 3) => (2,1), + + ((2,1,3), 1) => (1,1), + ((2,1,3), 2) => (1,2), + ((2,1,3), 3) => (2,1), + ((2,1,3), 4) => (3,1), + ((2,1,3), 5) => (3,2), + ((2,1,3), 6) => (3,3), + ] + + @testset "grid_and_local_dim_index$args" for (args, expected) ∈ cases + @test Grids.grid_and_local_dim_index(args...) == expected + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/zero_dim_grid_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,48 @@ +using Test +using Diffinitive.Grids +using StaticArrays + +@testset "ZeroDimGrid" begin + @test ZeroDimGrid(1) isa ZeroDimGrid{Int} + @test ZeroDimGrid([1,2,3]) isa ZeroDimGrid{Vector{Int}} + @test ZeroDimGrid(@SVector[1.0,2.0]) isa ZeroDimGrid{SVector{2,Float64}} + + @testset "Indexing Interface" begin + g = ZeroDimGrid(@SVector[1,2]) + + @test g[] == [1,2] + @test eachindex(g) == CartesianIndices(()) + end + + @testset "Iterator interface" begin + g = ZeroDimGrid(@SVector[1,2]) + + @test Base.IteratorSize(g) == Base.HasShape{0}() + @test eltype(g) == SVector{2,Int} + @test length(g) == 1 + @test size(g) == () + @test collect(g) == fill(@SVector[1,2]) + end + + @testset "refine" begin + @test refine(ZeroDimGrid(@SVector[1.0,2.0]),1) == ZeroDimGrid(@SVector[1.0,2.0]) + @test refine(ZeroDimGrid(@SVector[1.0,2.0]),2) == ZeroDimGrid(@SVector[1.0,2.0]) + end + + @testset "coarsen" begin + @test coarsen(ZeroDimGrid(@SVector[1.0,2.0]),1) == ZeroDimGrid(@SVector[1.0,2.0]) + @test coarsen(ZeroDimGrid(@SVector[1.0,2.0]),2) == ZeroDimGrid(@SVector[1.0,2.0]) + end + + @testset "boundary_identifiers" begin + @test boundary_identifiers(ZeroDimGrid(@SVector[1.0,2.0])) == () + end + + @testset "boundary_grid" begin + @test_throws ArgumentError("ZeroDimGrid has no boundaries") boundary_grid(ZeroDimGrid(@SVector[1.0,2.0]), :bid) + end + + @testset "boundary_indices" begin + @test_throws ArgumentError("ZeroDimGrid has no boundaries") boundary_indices(ZeroDimGrid(@SVector[1.0,2.0]), :bid) + end +end
--- a/test/LazyTensors/lazy_array_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/LazyTensors/lazy_array_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,6 +1,6 @@ using Test -using Sbplib.LazyTensors -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices @testset "LazyArray" begin @@ -67,11 +67,18 @@ @test isa(v1 + v2, LazyArray) @test isa(v2 + v1, LazyArray) @test isa(v1 - v2, LazyArray) - @test isa(v2 - v1, LazyArray) + @test isa(v2 - v1, LazyArray) + @test isa(v1 + s, LazyArray) + @test isa(s + v1, LazyArray) + @test isa(v1 - s, LazyArray) + @test isa(s - v1, LazyArray) for i ∈ eachindex(v2) @test (v1 + v2)[i] == (v2 + v1)[i] == r_add_v[i] @test (v1 - v2)[i] == -(v2 - v1)[i] == r_sub_v[i] + @test (v1 + s)[i] == (s + v1)[i] == r_add_s[i] + @test (v1 - s)[i] == -(s - v1)[i] == r_sub_s[i] end + @test_throws BoundsError (v1 + v2)[4] v2 = [1., 2, 3, 4] @test_throws DimensionMismatch v1 + v2 @@ -95,4 +102,7 @@ @test_throws BoundsError LazyFunctionArray((i,j)->i*j, (3,2))[4,2] @test_throws BoundsError LazyFunctionArray((i,j)->i*j, (3,2))[2,3] + # Test that the constructor works with a restrictive function + f(x::Vararg{Int}) = sum(x) + @test LazyFunctionArray(f,(3,4)) isa LazyFunctionArray end
--- a/test/LazyTensors/lazy_tensor_operations_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/LazyTensors/lazy_tensor_operations_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,16 +1,16 @@ using Test -using Sbplib.LazyTensors -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices using Tullio -struct DummyMapping{T,R,D} <: LazyTensor{T,R,D} end +struct TransposableDummyMapping{T,R,D} <: LazyTensor{T,R,D} end -LazyTensors.apply(m::DummyMapping{T,R}, v, I::Vararg{Any,R}) where {T,R} = :apply -LazyTensors.apply_transpose(m::DummyMapping{T,R,D}, v, I::Vararg{Any,D}) where {T,R,D} = :apply_transpose +LazyTensors.apply(m::TransposableDummyMapping{T,R}, v, I::Vararg{Any,R}) where {T,R} = :apply +LazyTensors.apply_transpose(m::TransposableDummyMapping{T,R,D}, v, I::Vararg{Any,D}) where {T,R,D} = :apply_transpose -LazyTensors.range_size(m::DummyMapping) = :range_size -LazyTensors.domain_size(m::DummyMapping) = :domain_size +LazyTensors.range_size(m::TransposableDummyMapping) = :range_size +LazyTensors.domain_size(m::TransposableDummyMapping) = :domain_size struct SizeDoublingMapping{T,R,D} <: LazyTensor{T,R,D} @@ -22,9 +22,8 @@ LazyTensors.domain_size(m::SizeDoublingMapping) = m.domain_size - @testset "Mapping transpose" begin - m = DummyMapping{Float64,2,3}() + m = TransposableDummyMapping{Float64,2,3}() @test m' isa LazyTensor{Float64, 3,2} @test m'' == m @test apply(m',zeros(Float64,(0,0)), 0, 0, 0) == :apply_transpose @@ -128,8 +127,35 @@ end end +@testset "TensorNegation" begin + A = rand(2,3) + B = rand(3,4) -@testset "LazyTensor binary operations" begin + Ã = DenseTensor(A, (1,), (2,)) + B̃ = DenseTensor(B, (1,), (2,)) + + @test -Ã isa TensorNegation + + v = rand(3) + @test (-Ã)*v == -(Ã*v) + + v = rand(4) + @test (-B̃)*v == -(B̃*v) + + v = rand(2) + @test (-Ã)'*v == -(Ã'*v) + + v = rand(3) + @test (-B̃)'*v == -(B̃'*v) + + @test domain_size(-Ã) == (3,) + @test domain_size(-B̃) == (4,) + + @test range_size(-Ã) == (2,) + @test range_size(-B̃) == (3,) +end + +@testset "TensorSum" begin A = ScalingTensor(2.0, (3,)) B = ScalingTensor(3.0, (3,)) @@ -142,6 +168,10 @@ @test ((A-B)*v)[i] == 2*v[i] - 3*v[i] end + for i ∈ eachindex(v) + @test ((A+B)'*v)[i] == 2*v[i] + 3*v[i] + end + @test range_size(A+B) == range_size(A) == range_size(B) @test domain_size(A+B) == domain_size(A) == domain_size(B) @@ -156,6 +186,30 @@ @test_throws RangeSizeMismatch ScalingTensor(2.0, (2,)) + SizeDoublingMapping{Float64,1,1}((2,)) @test_throws RangeSizeMismatch SizeDoublingMapping{Float64,1,1}((2,)) + ScalingTensor(2.0, (2,)) end + + @testset "Chained operators" begin + A = ScalingTensor(1.0, (3,)) + B = ScalingTensor(2.0, (3,)) + C = ScalingTensor(3.0, (3,)) + D = ScalingTensor(4.0, (3,)) + + @test A+B+C+D isa TensorSum + @test length((A+B+C+D).tms) == 4 + + + @test A+B-C+D isa TensorSum + @test length((A+B-C+D).tms) == 4 + + v = rand(3) + @test (A+B-C+D)*v == 1v + 2v - 3v + 4v + + + @test -A-B-C-D isa TensorSum + @test length((-A-B-C-D).tms) == 4 + + v = rand(3) + @test (-A-B-C-D)*v == -1v - 2v - 3v - 4v + end end @@ -188,7 +242,7 @@ @test a*Ã == Ã*a @test range_size(a*Ã) == range_size(Ã) @test domain_size(a*Ã) == domain_size(Ã) - @test a*Ã*v == a.*A*v + @test a*Ã*v ≈ a.*A*v rtol=1e-14 end @@ -380,3 +434,17 @@ @test I1⊗Ã⊗I2 == InflatedTensor(I1, Ã, I2) end end + +@testset "inflate" begin + I = LazyTensors.inflate(IdentityTensor(),(3,4,5,6), 2) + @test I isa LazyTensor{Float64, 3,3} + @test range_size(I) == (3,5,6) + @test domain_size(I) == (3,5,6) + + @test LazyTensors.inflate(ScalingTensor(1., (4,)),(3,4,5,6), 1) == InflatedTensor(IdentityTensor{Float64}(),ScalingTensor(1., (4,)),IdentityTensor(4,5,6)) + @test LazyTensors.inflate(ScalingTensor(2., (1,)),(3,4,5,6), 2) == InflatedTensor(IdentityTensor(3),ScalingTensor(2., (1,)),IdentityTensor(5,6)) + @test LazyTensors.inflate(ScalingTensor(3., (6,)),(3,4,5,6), 4) == InflatedTensor(IdentityTensor(3,4,5),ScalingTensor(3., (6,)),IdentityTensor{Float64}()) + + @test_throws BoundsError LazyTensors.inflate(ScalingTensor(1., (4,)),(3,4,5,6), 0) + @test_throws BoundsError LazyTensors.inflate(ScalingTensor(1., (4,)),(3,4,5,6), 5) +end
--- a/test/LazyTensors/lazy_tensor_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/LazyTensors/lazy_tensor_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,5 +1,5 @@ using Test -using Sbplib.LazyTensors +using Diffinitive.LazyTensors @testset "Generic Mapping methods" begin struct DummyMapping{T,R,D} <: LazyTensor{T,R,D} end
--- a/test/LazyTensors/tensor_types_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/LazyTensors/tensor_types_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,5 +1,5 @@ using Test -using Sbplib.LazyTensors +using Diffinitive.LazyTensors using BenchmarkTools @testset "IdentityTensor" begin
--- a/test/LazyTensors/tuple_manipulation_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/LazyTensors/tuple_manipulation_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,62 +1,83 @@ using Test -using Sbplib.LazyTensors +using Diffinitive.LazyTensors @testset "split_index" begin - @test LazyTensors.split_index(Val(2),Val(1),Val(2),Val(2),1,2,3,4,5,6) == ((1,2,:,5,6),(3,4)) - @test LazyTensors.split_index(Val(2),Val(3),Val(2),Val(2),1,2,3,4,5,6) == ((1,2,:,:,:,5,6),(3,4)) - @test LazyTensors.split_index(Val(3),Val(1),Val(1),Val(2),1,2,3,4,5,6) == ((1,2,3,:,5,6),(4,)) - @test LazyTensors.split_index(Val(3),Val(2),Val(1),Val(2),1,2,3,4,5,6) == ((1,2,3,:,:,5,6),(4,)) - @test LazyTensors.split_index(Val(1),Val(1),Val(2),Val(3),1,2,3,4,5,6) == ((1,:,4,5,6),(2,3)) - @test LazyTensors.split_index(Val(1),Val(2),Val(2),Val(3),1,2,3,4,5,6) == ((1,:,:,4,5,6),(2,3)) + @test LazyTensors.split_index(2,1,2,2, 1,2,3,4,5,6) == ((1,2,:,5,6),(3,4)) + @test LazyTensors.split_index(2,3,2,2, 1,2,3,4,5,6) == ((1,2,:,:,:,5,6),(3,4)) + @test LazyTensors.split_index(3,1,1,2, 1,2,3,4,5,6) == ((1,2,3,:,5,6),(4,)) + @test LazyTensors.split_index(3,2,1,2, 1,2,3,4,5,6) == ((1,2,3,:,:,5,6),(4,)) + @test LazyTensors.split_index(1,1,2,3, 1,2,3,4,5,6) == ((1,:,4,5,6),(2,3)) + @test LazyTensors.split_index(1,2,2,3, 1,2,3,4,5,6) == ((1,:,:,4,5,6),(2,3)) - @test LazyTensors.split_index(Val(0),Val(1),Val(3),Val(3),1,2,3,4,5,6) == ((:,4,5,6),(1,2,3)) - @test LazyTensors.split_index(Val(3),Val(1),Val(3),Val(0),1,2,3,4,5,6) == ((1,2,3,:),(4,5,6)) - - @inferred LazyTensors.split_index(Val(2),Val(3),Val(2),Val(2),1,2,3,2,2,4) -end + @test LazyTensors.split_index(0,1,3,3, 1,2,3,4,5,6) == ((:,4,5,6),(1,2,3)) + @test LazyTensors.split_index(3,1,3,0, 1,2,3,4,5,6) == ((1,2,3,:),(4,5,6)) -@testset "slice_tuple" begin - @test LazyTensors.slice_tuple((1,2,3),Val(1), Val(3)) == (1,2,3) - @test LazyTensors.slice_tuple((1,2,3,4,5,6),Val(2), Val(5)) == (2,3,4,5) - @test LazyTensors.slice_tuple((1,2,3,4,5,6),Val(1), Val(3)) == (1,2,3) - @test LazyTensors.slice_tuple((1,2,3,4,5,6),Val(4), Val(6)) == (4,5,6) + split_index_static(::Val{dim_before}, + ::Val{dim_view}, + ::Val{dim_index}, + ::Val{dim_after}, I...) where {dim_before,dim_view,dim_index,dim_after} = + LazyTensors.split_index(dim_before, dim_view, dim_index, dim_after, I...) + @inferred split_index_static(Val(2),Val(3),Val(2),Val(2),1,2,3,2,2,4) end @testset "split_tuple" begin - @testset "2 parts" begin - @test LazyTensors.split_tuple((),Val(0)) == ((),()) - @test LazyTensors.split_tuple((1,),Val(0)) == ((),(1,)) - @test LazyTensors.split_tuple((1,),Val(1)) == ((1,),()) - - @test LazyTensors.split_tuple((1,2,3,4),Val(0)) == ((),(1,2,3,4)) - @test LazyTensors.split_tuple((1,2,3,4),Val(1)) == ((1,),(2,3,4)) - @test LazyTensors.split_tuple((1,2,3,4),Val(2)) == ((1,2),(3,4)) - @test LazyTensors.split_tuple((1,2,3,4),Val(3)) == ((1,2,3),(4,)) - @test LazyTensors.split_tuple((1,2,3,4),Val(4)) == ((1,2,3,4),()) - - @test LazyTensors.split_tuple((1,2,true,4),Val(3)) == ((1,2,true),(4,)) + @testset "general" begin + @test LazyTensors.split_tuple((),()) == () + @test LazyTensors.split_tuple((),(0,)) == ((),) + @test LazyTensors.split_tuple((1,), (1,)) == tuple((1,)) + @test LazyTensors.split_tuple((1,2), (1,1)) == tuple((1,),(2,)) + @test LazyTensors.split_tuple((1,2), (0,1,1)) == tuple((),(1,),(2,)) + @test LazyTensors.split_tuple((1,2), (1,0,1)) == tuple((1,),(),(2,)) + @test LazyTensors.split_tuple((1,2), (1,1,0)) == tuple((1,),(2,),()) + @test LazyTensors.split_tuple((1,2,3,4), (2,0,1,1)) == tuple((1,2),(),(3,),(4,)) - @inferred LazyTensors.split_tuple((1,2,3,4),Val(3)) - @inferred LazyTensors.split_tuple((1,2,true,4),Val(3)) - end + err_msg = "length(t) must equal sum(szs)" + @test_throws ArgumentError(err_msg) LazyTensors.split_tuple((), (2,)) + @test_throws ArgumentError(err_msg) LazyTensors.split_tuple((2,), ()) + @test_throws ArgumentError(err_msg) LazyTensors.split_tuple((1,), (2,)) + @test_throws ArgumentError(err_msg) LazyTensors.split_tuple((1,2), (1,2)) + @test_throws ArgumentError(err_msg) LazyTensors.split_tuple((1,2), (1)) - @testset "3 parts" begin - @test LazyTensors.split_tuple((),Val(0),Val(0)) == ((),(),()) - @test LazyTensors.split_tuple((1,2,3),Val(1), Val(1)) == ((1,),(2,),(3,)) - @test LazyTensors.split_tuple((1,true,3),Val(1), Val(1)) == ((1,),(true,),(3,)) - - @test LazyTensors.split_tuple((1,2,3,4,5,6),Val(1),Val(2)) == ((1,),(2,3),(4,5,6)) - @test LazyTensors.split_tuple((1,2,3,4,5,6),Val(3),Val(2)) == ((1,2,3),(4,5),(6,)) - - @inferred LazyTensors.split_tuple((1,2,3,4,5,6),Val(3),Val(2)) - @inferred LazyTensors.split_tuple((1,true,3),Val(1), Val(1)) + split_tuple_static(t, ::Val{SZS}) where {SZS} = LazyTensors.split_tuple(t,SZS) + @inferred split_tuple_static((1,2,3,4,5,6), Val((3,1,2))) + @inferred split_tuple_static((1,2,3,4),Val((3,1))) + @inferred split_tuple_static((1,2,true,4),Val((3,1))) + @inferred split_tuple_static((1,2,3,4,5,6),Val((3,2,1))) + @inferred split_tuple_static((1,true,3),Val((1,1,1))) end end -@testset "flatten_tuple" begin - @test LazyTensors.flatten_tuple((1,)) == (1,) - @test LazyTensors.flatten_tuple((1,2,3,4,5,6)) == (1,2,3,4,5,6) - @test LazyTensors.flatten_tuple((1,2,(3,4),5,6)) == (1,2,3,4,5,6) - @test LazyTensors.flatten_tuple((1,2,(3,(4,5)),6)) == (1,2,3,4,5,6) - @test LazyTensors.flatten_tuple(((1,2),(3,4),(5,),6)) == (1,2,3,4,5,6) +@testset "sizes_to_ranges" begin + @test LazyTensors.sizes_to_ranges((1,)) == (1:1,) + @test LazyTensors.sizes_to_ranges((2,)) == (1:2,) + @test LazyTensors.sizes_to_ranges((2,3)) == (1:2,3:5) + @test LazyTensors.sizes_to_ranges((3,2,4)) == (1:3,4:5,6:9) + @test LazyTensors.sizes_to_ranges((0,2)) == (1:0,1:2) + @test LazyTensors.sizes_to_ranges((2,0)) == (1:2,2:1) + @test LazyTensors.sizes_to_ranges((2,0,3)) == (1:2,2:1,3:5) end + +@testset "concatenate_tuples" begin + @test LazyTensors.concatenate_tuples(()) == () + @test LazyTensors.concatenate_tuples((1,)) == (1,) + @test LazyTensors.concatenate_tuples((1,), ()) == (1,) + @test LazyTensors.concatenate_tuples((),(1,)) == (1,) + @test LazyTensors.concatenate_tuples((1,2,3),(4,5)) == (1,2,3,4,5) + @test LazyTensors.concatenate_tuples((1,2,3),(4,5),(6,7)) == (1,2,3,4,5,6,7) +end + +@testset "left_pad_tuple" begin + @test LazyTensors.left_pad_tuple((1,2), 0, 2) == (1,2) + @test LazyTensors.left_pad_tuple((1,2), 0, 3) == (0,1,2) + @test LazyTensors.left_pad_tuple((3,2), 1, 6) == (1,1,1,1,3,2) + err_msg = "Can't pad tuple of length 2 to 0 elements" + @test_throws DomainError(0, err_msg) LazyTensors.left_pad_tuple((1,2), 0, 0) == (1,2) +end + +@testset "right_pad_tuple" begin + @test LazyTensors.right_pad_tuple((1,2), 0, 2) == (1,2) + @test LazyTensors.right_pad_tuple((1,2), 0, 3) == (1,2,0) + @test LazyTensors.right_pad_tuple((3,2), 1, 6) == (3,2,1,1,1,1) + err_msg = "Can't pad tuple of length 2 to 0 elements" + @test_throws DomainError(0,err_msg) LazyTensors.right_pad_tuple((1,2), 0, 0) == (1,2) +end
--- a/test/Manifest.toml Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,286 +0,0 @@ -# This file is machine-generated - editing it directly is not advised - -julia_version = "1.7.1" -manifest_format = "2.0" - -[[deps.ArgTools]] -uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" - -[[deps.Artifacts]] -uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" - -[[deps.Base64]] -uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" - -[[deps.BenchmarkTools]] -deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] -git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93" -uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "1.3.1" - -[[deps.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "9950387274246d08af38f6eef8cb5480862a435f" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.14.0" - -[[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 = "96b0bc6c52df76506efc8a441c6cf1adcb1babc4" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.42.0" - -[[deps.CompilerSupportLibraries_jll]] -deps = ["Artifacts", "Libdl"] -uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" - -[[deps.Dates]] -deps = ["Printf"] -uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" - -[[deps.DeepDiffs]] -git-tree-sha1 = "9824894295b62a6a4ab6adf1c7bf337b3a9ca34c" -uuid = "ab62b9b5-e342-54a8-a765-a90f495de1a6" -version = "1.2.0" - -[[deps.DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[[deps.DiffRules]] -deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "dd933c4ef7b4c270aacd4eb88fa64c147492acf0" -uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.10.0" - -[[deps.Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - -[[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" - -[[deps.InteractiveUtils]] -deps = ["Markdown"] -uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.InverseFunctions]] -deps = ["Test"] -git-tree-sha1 = "91b5dcf362c5add98049e6c29ee756910b03051d" -uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.3" - -[[deps.IrrationalConstants]] -git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151" -uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.1.1" - -[[deps.JLLWrappers]] -deps = ["Preferences"] -git-tree-sha1 = "abc9885a7ca2052a736a600f7fa66209f96506e1" -uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.4.1" - -[[deps.JSON]] -deps = ["Dates", "Mmap", "Parsers", "Unicode"] -git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" -uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.3" - -[[deps.LibCURL]] -deps = ["LibCURL_jll", "MozillaCACerts_jll"] -uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" - -[[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" - -[[deps.LibSSH2_jll]] -deps = ["Artifacts", "Libdl", "MbedTLS_jll"] -uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" - -[[deps.Libdl]] -uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" - -[[deps.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[deps.LogExpFunctions]] -deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "58f25e56b706f95125dcb796f39e1fb01d913a71" -uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.10" - -[[deps.Logging]] -uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" - -[[deps.Markdown]] -deps = ["Base64"] -uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" - -[[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 = "737a5957f387b17e74d4ad2f440eb330b39a62c5" -uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "1.0.0" - -[[deps.NetworkOptions]] -uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" - -[[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 = "13652491f6856acfd2db29360e1bbcd4565d04f1" -uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.5+0" - -[[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "85b5da0fa43588c75bb1ff986493443f821c70b7" -uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.2.3" - -[[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" - -[[deps.Preferences]] -deps = ["TOML"] -git-tree-sha1 = "d3538e7f8a790dc8903519090857ef8e1283eecd" -uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.2.5" - -[[deps.Printf]] -deps = ["Unicode"] -uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" - -[[deps.Profile]] -deps = ["Printf"] -uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" - -[[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.Requires]] -deps = ["UUIDs"] -git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" -uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.3.0" - -[[deps.SHA]] -uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[deps.Serialization]] -uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - -[[deps.Sockets]] -uuid = "6462fe0b-24de-5631-8697-dd941f90decc" - -[[deps.SparseArrays]] -deps = ["LinearAlgebra", "Random"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[deps.SpecialFunctions]] -deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "5ba658aeecaaf96923dce0da9e703bd1fe7666f9" -uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.1.4" - -[[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" - -[[deps.TOML]] -deps = ["Dates"] -uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" - -[[deps.Tar]] -deps = ["ArgTools", "SHA"] -uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" - -[[deps.Test]] -deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] -uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" - -[[deps.TestSetExtensions]] -deps = ["DeepDiffs", "Distributed", "Test"] -git-tree-sha1 = "3a2919a78b04c29a1a57b05e1618e473162b15d0" -uuid = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" -version = "2.0.0" - -[[deps.Tullio]] -deps = ["ChainRulesCore", "DiffRules", "LinearAlgebra", "Requires"] -git-tree-sha1 = "7830c974acc69437a3fee35dd7b510a74cbc862d" -uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" -version = "0.3.3" - -[[deps.UUIDs]] -deps = ["Random", "SHA"] -uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" - -[[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/Project.toml Mon May 23 07:20:27 2022 +0200 +++ b/test/Project.toml Tue Feb 10 22:41:19 2026 +0100 @@ -1,8 +1,15 @@ [deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Diffinitive = "5a373a26-915f-4769-bcab-bf03835de17b" Glob = "c27321d9-0574-5035-807b-f59d2c89b15c" +JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" +Tokens = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" Tullio = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc"
--- a/test/RegionIndices/RegionIndices_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/RegionIndices/RegionIndices_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,3 +1,3 @@ -using Sbplib.RegionIndices +using Diffinitive.RegionIndices using Test
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/boundary_conditions/boundary_condition_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,39 @@ +using Test + +using Diffinitive.Grids +using Diffinitive.RegionIndices +using Diffinitive.SbpOperators + +@testset "BoundaryCondition" begin + grid_1d = equidistant_grid(0.0, 1.0, 11) + grid_2d = equidistant_grid((0.0, 0.0), (1.0,1.0), 11, 15) + grid_3d = equidistant_grid((0.0, 0.0, 0.0), (1.0,1.0, 1.0), 11, 15, 13) + (id_l,_) = boundary_identifiers(grid_1d) + (_,_,_,id_n) = boundary_identifiers(grid_2d) + (_,_,_,_,id_b,_) = boundary_identifiers(grid_3d) + + g = 3.14 + f(x,y,z) = x^2+y^2+z^2 + @testset "Constructors" begin + @test DirichletCondition(g,id_l) isa DirichletCondition{Float64,LowerBoundary} + @test NeumannCondition(f,id_b) isa NeumannCondition{<:Function,CartesianBoundary{3,LowerBoundary}} + end + + @testset "boundary" begin + @test boundary(DirichletCondition(g,id_l)) == id_l + @test boundary(NeumannCondition(f,id_b)) == id_b + end + + @testset "boundary_data" begin + @test boundary_data(DirichletCondition(g,id_l)) == g + @test boundary_data(NeumannCondition(f,id_b)) == f + end + + @testset "discretize_data" begin + @test fill(g) ≈ discretize_data(grid_1d,DirichletCondition(g,id_l)) + @test g*ones(11,1) ≈ discretize_data(grid_2d,DirichletCondition(g,id_n)) + X = repeat(0:1/10:1, inner = (1,15)) + Y = repeat(0:1/14:1, outer = (1,11)) + @test map((x,y)->f(x,y,0), X,Y') ≈ discretize_data(grid_3d,NeumannCondition(f,id_b)) + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/boundary_conditions/sat_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,71 @@ +using Test + +using Diffinitive.Grids +using Diffinitive.LazyTensors +using Diffinitive.SbpOperators + +stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order = 4) + +struct MockOp end + +function SbpOperators.sat_tensors(op::MockOp, g::Grid, bc::DirichletCondition; a = 1.) + e = boundary_restriction(g, stencil_set, boundary(bc)) + L = a*e + sat_op = e' + return sat_op, L +end + +function SbpOperators.sat_tensors(op::MockOp, g::Grid, bc::NeumannCondition) + e = boundary_restriction(g, stencil_set, boundary(bc)) + d = normal_derivative(g, stencil_set, boundary(bc)) + L = d + sat_op = e' + return sat_op, L +end + +@testset "sat" begin + op = MockOp() + @testset "1D" begin + grid = equidistant_grid(0., 1., 11) + l, r = boundary_identifiers(grid) + u = eval_on(grid, x-> 1. + 2x^2) + dc = DirichletCondition(1.0, l) + g_l = discretize_data(grid, dc) + SAT_l = sat(op, grid, dc) + @test SAT_l(u, g_l) ≈ zeros((size(grid))) atol = 1e-13 + + nc = NeumannCondition(4.0, r) + g_r = discretize_data(grid, nc) + SAT_r = sat(op, grid, nc) + @test SAT_r(u, g_r) ≈ zeros((size(grid))) atol = 1e-13 + end + @testset "2D" begin + grid = equidistant_grid((0.,0.), (1.,1.), 11, 13) + W, E, S, N = boundary_identifiers(grid) + u = eval_on(grid, (x,y) -> x+y^2) + + dc_W = DirichletCondition(1.0, W) + SAT_W = sat(op, grid, dc_W) + g_W = discretize_data(grid, dc_W) + r_W = zeros(size(grid)) + r_W[1,:] .= map(y -> (y^2-1.), range(0., 1., length=13)) + @test SAT_W(u, g_W) ≈ r_W atol = 1e-13 + + dc_E = DirichletCondition(2, E) + SAT_E = sat(op, grid, dc_E; a = 2.) + g_E = discretize_data(grid, dc_E) + r_E = zeros(size(grid)) + r_E[end,:] .= map(y -> (2*(1. + y^2)-2.), range(0., 1., length=13)) + @test SAT_E(u, g_E) ≈ r_E atol = 1e-13 + + nc_S = NeumannCondition(.0, S) + SAT_S = sat(op, grid, nc_S) + g_S = discretize_data(grid, nc_S) + @test SAT_S(u, g_S) ≈ zeros(size(grid)) atol = 1e-13 + + nc_N = NeumannCondition(2.0, N) + SAT_N = sat(op, grid, nc_N) + g_N = discretize_data(grid, nc_N) + @test SAT_N(u, g_N) ≈ zeros(size(grid)) atol = 1e-13 + end +end
--- a/test/SbpOperators/boundaryops/boundary_operator_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/boundaryops/boundary_operator_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,133 +1,62 @@ using Test -using Sbplib.LazyTensors -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.RegionIndices -import Sbplib.SbpOperators.Stencil -import Sbplib.SbpOperators.BoundaryOperator -import Sbplib.SbpOperators.boundary_operator +using Diffinitive.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.RegionIndices +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.BoundaryOperator + @testset "BoundaryOperator" begin closure_stencil = Stencil(2.,1.,3.; center = 1) - g_1D = EquidistantGrid(11, 0.0, 1.0) - g_2D = EquidistantGrid((11,15), (0.0, 0.0), (1.0,1.0)) + g_1D = EquidistantGrid(range(0,1,length=11)) @testset "Constructors" begin - @testset "1D" begin - op_l = BoundaryOperator{Lower}(closure_stencil,size(g_1D)[1]) - @test op_l == BoundaryOperator(g_1D,closure_stencil,Lower()) - @test op_l == boundary_operator(g_1D,closure_stencil,CartesianBoundary{1,Lower}()) - @test op_l isa LazyTensor{T,0,1} where T + @test BoundaryOperator(g_1D, closure_stencil, LowerBoundary()) isa LazyTensor{T,0,1} where T + @test BoundaryOperator(g_1D, closure_stencil, UpperBoundary()) isa LazyTensor{T,0,1} where T + end - op_r = BoundaryOperator{Upper}(closure_stencil,size(g_1D)[1]) - @test op_r == BoundaryOperator(g_1D,closure_stencil,Upper()) - @test op_r == boundary_operator(g_1D,closure_stencil,CartesianBoundary{1,Upper}()) - @test op_r isa LazyTensor{T,0,1} where T - end - - @testset "2D" begin - e_w = boundary_operator(g_2D,closure_stencil,CartesianBoundary{1,Upper}()) - @test e_w isa InflatedTensor - @test e_w isa LazyTensor{T,1,2} where T - end - end - op_l, op_r = boundary_operator.(Ref(g_1D), Ref(closure_stencil), boundary_identifiers(g_1D)) - op_w, op_e, op_s, op_n = boundary_operator.(Ref(g_2D), Ref(closure_stencil), boundary_identifiers(g_2D)) + op_l = BoundaryOperator(g_1D, closure_stencil, LowerBoundary()) + op_r = BoundaryOperator(g_1D, closure_stencil, UpperBoundary()) @testset "Sizes" begin - @testset "1D" begin - @test domain_size(op_l) == (11,) - @test domain_size(op_r) == (11,) - - @test range_size(op_l) == () - @test range_size(op_r) == () - end + @test domain_size(op_l) == (11,) + @test domain_size(op_r) == (11,) - @testset "2D" begin - @test domain_size(op_w) == (11,15) - @test domain_size(op_e) == (11,15) - @test domain_size(op_s) == (11,15) - @test domain_size(op_n) == (11,15) - - @test range_size(op_w) == (15,) - @test range_size(op_e) == (15,) - @test range_size(op_s) == (11,) - @test range_size(op_n) == (11,) - end + @test range_size(op_l) == () + @test range_size(op_r) == () end @testset "Application" begin - @testset "1D" begin - v = evalOn(g_1D,x->1+x^2) - u = fill(3.124) - @test (op_l*v)[] == 2*v[1] + v[2] + 3*v[3] - @test (op_r*v)[] == 2*v[end] + v[end-1] + 3*v[end-2] - @test (op_r*v)[1] == 2*v[end] + v[end-1] + 3*v[end-2] - @test op_l'*u == [2*u[]; u[]; 3*u[]; zeros(8)] - @test op_r'*u == [zeros(8); 3*u[]; u[]; 2*u[]] - - v = evalOn(g_1D, x->1. +x*im) - @test (op_l*v)[] isa ComplexF64 + v = eval_on(g_1D,x->1+x^2) + u = fill(3.124) + @test (op_l*v)[] == 2*v[1] + v[2] + 3*v[3] + @test (op_r*v)[] == 2*v[end] + v[end-1] + 3*v[end-2] + @test (op_r*v)[1] == 2*v[end] + v[end-1] + 3*v[end-2] + @test op_l'*u == [2*u[]; u[]; 3*u[]; zeros(8)] + @test op_r'*u == [zeros(8); 3*u[]; u[]; 2*u[]] - u = fill(1. +im) - @test (op_l'*u)[1] isa ComplexF64 - @test (op_l'*u)[5] isa ComplexF64 - @test (op_l'*u)[11] isa ComplexF64 - end - - @testset "2D" begin - v = rand(size(g_2D)...) - u = fill(3.124) - @test op_w*v ≈ 2*v[1,:] + v[2,:] + 3*v[3,:] rtol = 1e-14 - @test op_e*v ≈ 2*v[end,:] + v[end-1,:] + 3*v[end-2,:] rtol = 1e-14 - @test op_s*v ≈ 2*v[:,1] + v[:,2] + 3*v[:,3] rtol = 1e-14 - @test op_n*v ≈ 2*v[:,end] + v[:,end-1] + 3*v[:,end-2] rtol = 1e-14 - - - g_x = rand(size(g_2D)[1]) - g_y = rand(size(g_2D)[2]) - - G_w = zeros(Float64, size(g_2D)...) - G_w[1,:] = 2*g_y - G_w[2,:] = g_y - G_w[3,:] = 3*g_y + v = eval_on(g_1D, x->1. +x*im) + @test (op_l*v)[] isa ComplexF64 - G_e = zeros(Float64, size(g_2D)...) - G_e[end,:] = 2*g_y - G_e[end-1,:] = g_y - G_e[end-2,:] = 3*g_y - - G_s = zeros(Float64, size(g_2D)...) - G_s[:,1] = 2*g_x - G_s[:,2] = g_x - G_s[:,3] = 3*g_x - - G_n = zeros(Float64, size(g_2D)...) - G_n[:,end] = 2*g_x - G_n[:,end-1] = g_x - G_n[:,end-2] = 3*g_x + u = fill(1. +im) + @test (op_l'*u)[1] isa ComplexF64 + @test (op_l'*u)[5] isa ComplexF64 + @test (op_l'*u)[11] isa ComplexF64 - @test op_w'*g_y == G_w - @test op_e'*g_y == G_e - @test op_s'*g_x == G_s - @test op_n'*g_x == G_n - end + u = fill(3.124) + @test (op_l'*u)[Index(1,Lower)] == 2*u[] + @test (op_l'*u)[Index(2,Lower)] == u[] + @test (op_l'*u)[Index(6,Interior)] == 0 + @test (op_l'*u)[Index(10,Upper)] == 0 + @test (op_l'*u)[Index(11,Upper)] == 0 - @testset "Regions" begin - u = fill(3.124) - @test (op_l'*u)[Index(1,Lower)] == 2*u[] - @test (op_l'*u)[Index(2,Lower)] == u[] - @test (op_l'*u)[Index(6,Interior)] == 0 - @test (op_l'*u)[Index(10,Upper)] == 0 - @test (op_l'*u)[Index(11,Upper)] == 0 - - @test (op_r'*u)[Index(1,Lower)] == 0 - @test (op_r'*u)[Index(2,Lower)] == 0 - @test (op_r'*u)[Index(6,Interior)] == 0 - @test (op_r'*u)[Index(10,Upper)] == u[] - @test (op_r'*u)[Index(11,Upper)] == 2*u[] - end + @test (op_r'*u)[Index(1,Lower)] == 0 + @test (op_r'*u)[Index(2,Lower)] == 0 + @test (op_r'*u)[Index(6,Interior)] == 0 + @test (op_r'*u)[Index(10,Upper)] == u[] + @test (op_r'*u)[Index(11,Upper)] == 2*u[] end @testset "Inferred" begin
--- a/test/SbpOperators/boundaryops/boundary_restriction_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/boundaryops/boundary_restriction_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,35 +1,32 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors -using Sbplib.RegionIndices -using Sbplib.SbpOperators: BoundaryOperator, Stencil +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices +using Diffinitive.SbpOperators: BoundaryOperator, Stencil @testset "boundary_restriction" begin 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)) + g_1D = equidistant_grid(0.0, 1.0, 11) + g_2D = equidistant_grid((0.0, 0.0), (1.0,1.0), 11, 15) @testset "boundary_restriction" begin @testset "1D" begin - e_l = boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Lower}()) - @test e_l == boundary_restriction(g_1D,stencil_set,CartesianBoundary{1,Lower}()) - @test e_l == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Lower()) - @test e_l isa BoundaryOperator{T,Lower} where T + e_l = boundary_restriction(g_1D,stencil_set,LowerBoundary()) + @test e_l == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),LowerBoundary()) + @test e_l isa BoundaryOperator{T,LowerBoundary} where T @test e_l isa LazyTensor{T,0,1} where T - e_r = boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Upper}()) - @test e_r == boundary_restriction(g_1D,stencil_set,CartesianBoundary{1,Upper}()) - @test e_r == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Upper()) - @test e_r isa BoundaryOperator{T,Upper} where T + e_r = boundary_restriction(g_1D,stencil_set,UpperBoundary()) + @test e_r == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),UpperBoundary()) + @test e_r isa BoundaryOperator{T,UpperBoundary} where T @test e_r isa LazyTensor{T,0,1} where T end @testset "2D" begin - e_w = boundary_restriction(g_2D,e_closure,CartesianBoundary{1,Upper}()) - @test e_w == boundary_restriction(g_2D,stencil_set,CartesianBoundary{1,Upper}()) + e_w = boundary_restriction(g_2D,stencil_set,CartesianBoundary{1,UpperBoundary}()) @test e_w isa InflatedTensor @test e_w isa LazyTensor{T,1,2} where T end @@ -37,8 +34,8 @@ @testset "Application" begin @testset "1D" begin - e_l, e_r = boundary_restriction.(Ref(g_1D), Ref(e_closure), boundary_identifiers(g_1D)) - v = evalOn(g_1D,x->1+x^2) + e_l, e_r = boundary_restriction.(Ref(g_1D), Ref(stencil_set), boundary_identifiers(g_1D)) + v = eval_on(g_1D,x->1+x^2) u = fill(3.124) @test (e_l*v)[] == v[1] @@ -47,7 +44,7 @@ end @testset "2D" begin - e_w, e_e, e_s, e_n = boundary_restriction.(Ref(g_2D), Ref(e_closure), boundary_identifiers(g_2D)) + e_w, e_e, e_s, e_n = boundary_restriction.(Ref(g_2D), Ref(stencil_set), boundary_identifiers(g_2D)) v = rand(11, 15) u = fill(3.124)
--- a/test/SbpOperators/boundaryops/normal_derivative_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/boundaryops/normal_derivative_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,31 +1,30 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors -using Sbplib.RegionIndices -import Sbplib.SbpOperators.BoundaryOperator +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices +import Diffinitive.SbpOperators.BoundaryOperator @testset "normal_derivative" begin - g_1D = EquidistantGrid(11, 0.0, 1.0) - g_2D = EquidistantGrid((11,12), (0.0, 0.0), (1.0,1.0)) + g_1D = equidistant_grid(0.0, 1.0, 11) + g_2D = equidistant_grid((0.0, 0.0), (1.0,1.0), 11, 12) @testset "normal_derivative" begin 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, d_closure, CartesianBoundary{1,Lower}()) - @test d_l == normal_derivative(g_1D, stencil_set, CartesianBoundary{1,Lower}()) - @test d_l isa BoundaryOperator{T,Lower} where T + d_l = normal_derivative(g_1D, stencil_set, LowerBoundary()) + @test d_l == normal_derivative(g_1D, stencil_set, LowerBoundary()) + @test d_l isa BoundaryOperator{T,LowerBoundary} where T @test d_l isa LazyTensor{T,0,1} where T end @testset "2D" begin - d_w = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}()) - d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}()) + d_w = normal_derivative(g_2D, stencil_set, CartesianBoundary{1,LowerBoundary}()) + d_n = normal_derivative(g_2D, stencil_set, CartesianBoundary{2,UpperBoundary}()) Ix = IdentityTensor{Float64}((size(g_2D)[1],)) Iy = IdentityTensor{Float64}((size(g_2D)[2],)) - d_l = normal_derivative(restrict(g_2D,1),d_closure,CartesianBoundary{1,Lower}()) - d_r = normal_derivative(restrict(g_2D,2),d_closure,CartesianBoundary{1,Upper}()) - @test d_w == normal_derivative(g_2D, stencil_set, CartesianBoundary{1,Lower}()) + d_l = normal_derivative(g_2D.grids[1], stencil_set, LowerBoundary()) + d_r = normal_derivative(g_2D.grids[2], stencil_set, UpperBoundary()) + @test d_w == normal_derivative(g_2D, stencil_set, CartesianBoundary{1,LowerBoundary}()) @test d_w == d_l⊗Iy @test d_n == Ix⊗d_r @test d_w isa LazyTensor{T,1,2} where T @@ -33,14 +32,13 @@ end end @testset "Accuracy" begin - v = evalOn(g_2D, (x,y)-> x^2 + (y-1)^2 + x*y) - v∂x = evalOn(g_2D, (x,y)-> 2*x + y) - v∂y = evalOn(g_2D, (x,y)-> 2*(y-1) + x) + v = eval_on(g_2D, (x,y)-> x^2 + (y-1)^2 + x*y) + v∂x = eval_on(g_2D, (x,y)-> 2*x + y) + v∂y = eval_on(g_2D, (x,y)-> 2*(y-1) + x) # TODO: Test for higher order polynomials? @testset "2nd order" begin stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) - d_closure = parse_stencil(stencil_set["d1"]["closure"]) - d_w, d_e, d_s, d_n = normal_derivative.(Ref(g_2D), Ref(d_closure), boundary_identifiers(g_2D)) + d_w, d_e, d_s, d_n = normal_derivative.(Ref(g_2D), Ref(stencil_set), boundary_identifiers(g_2D)) @test d_w*v ≈ -v∂x[1,:] atol = 1e-13 @test d_e*v ≈ v∂x[end,:] atol = 1e-13 @@ -50,8 +48,7 @@ @testset "4th order" begin stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) - d_closure = parse_stencil(stencil_set["d1"]["closure"]) - d_w, d_e, d_s, d_n = normal_derivative.(Ref(g_2D), Ref(d_closure), boundary_identifiers(g_2D)) + d_w, d_e, d_s, d_n = normal_derivative.(Ref(g_2D), Ref(stencil_set), boundary_identifiers(g_2D)) @test d_w*v ≈ -v∂x[1,:] atol = 1e-13 @test d_e*v ≈ v∂x[end,:] atol = 1e-13
--- a/test/SbpOperators/stencil_set_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/stencil_set_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,9 +1,10 @@ using Test using TOML -using Sbplib.SbpOperators +using Diffinitive.SbpOperators -import Sbplib.SbpOperators.Stencil +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.NestedStencil @testset "readoperator" begin toml_str = """ @@ -170,3 +171,18 @@ @test SbpOperators.parse_rational(2) isa Rational @test SbpOperators.parse_rational(2) == 2//1 end + +@testset "parse_nested_stencil" begin + toml = TOML.parse(""" + s1 = [["1/2", "1/2", "0"],[ "-1/2", "-1", "-1/2"],["0", "1/2", "1/2"]] + s2 = {s = [[ "2", "-1", "0"],[ "-3", "1", "0"],["1", "0", "0"]], c = 1} + s3 = {s = [[ "2", "-1", "0"],[ "-3", "1", "0"],["1", "0", "0"]], c = 2} + """) + + @test parse_nested_stencil(toml["s1"]) == CenteredNestedStencil((1//2, 1//2, 0//1),( -1//2, -1//1, -1//2),(0//1, 1//2, 1//2)) + @test parse_nested_stencil(toml["s2"]) == NestedStencil((2//1, -1//1, 0//1),( -3//1, 1//1, 0//1),(1//1, 0//1, 0//1), center = 1) + @test parse_nested_stencil(toml["s3"]) == NestedStencil((2//1, -1//1, 0//1),( -3//1, 1//1, 0//1),(1//1, 0//1, 0//1), center = 2) + + @test parse_nested_stencil(Float64, toml["s1"]) == CenteredNestedStencil((1/2, 1/2, 0.),( -1/2, -1., -1/2),(0., 1/2, 1/2)) + @test parse_nested_stencil(Int, toml["s2"]) == NestedStencil((2, -1, 0),( -3, 1, 0),(1, 0, 0), center = 1) +end
--- a/test/SbpOperators/stencil_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/stencil_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,8 +1,10 @@ using Test -using Sbplib.SbpOperators -import Sbplib.SbpOperators.Stencil -import Sbplib.SbpOperators.NestedStencil -import Sbplib.SbpOperators.scale +using Diffinitive.SbpOperators +using StaticArrays +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.NestedStencil +import Diffinitive.SbpOperators.scale +import Diffinitive.SbpOperators: apply_stencil, apply_stencil_backwards @testset "Stencil" begin s = Stencil(-2:2, (1.,2.,2.,3.,4.)) @@ -44,6 +46,52 @@ @test promote(Stencil(1,1;center=1), Stencil(2.,2.;center=2)) == (Stencil(1.,1.;center=1), Stencil(2.,2.;center=2)) end + @testset "apply_stencil" begin + v = [1, 2, 4, 8, 16, 32, 64, 128] + s = Stencil(1,2,3,4, center = 2) + @test apply_stencil(s,v, 2) == v[1] + 2*v[2] + 3*v[3] + 4*v[4] + @test apply_stencil(s,v, 4) == v[3] + 2*v[4] + 3*v[5] + 4*v[6] + @test apply_stencil_backwards(s,v, 3) == 4*v[1] + 3*v[2] + 2*v[3] + 1*v[4] + @test apply_stencil_backwards(s,v, 7) == 4*v[5] + 3*v[6] + 2*v[7] + 1*v[8] + @test apply_stencil(s,v, 2) isa Int + @test apply_stencil_backwards(s,v, 7) isa Int + + v = [1, 2, 4, 8, 16, 32, 64, 128] + s = Stencil(1.,2.,3.,4., center = 2) + @test apply_stencil(s,v, 4) == v[3] + 2. *v[4] + 3. *v[5] + 4. *v[6] + @test apply_stencil_backwards(s,v, 7) == 4. *v[5] + 3. *v[6] + 2. *v[7] + v[8] + @test apply_stencil(s,v, 2) isa Float64 + @test apply_stencil_backwards(s,v, 7) isa Float64 + + v = [1., 2., 4., 8., 16., 32., 64., 128.] + s = Stencil(1,2,3,4, center = 2) + @test apply_stencil(s,v, 2) == v[1] + 2*v[2] + 3*v[3] + 4*v[4] + @test apply_stencil_backwards(s,v, 3) == 4*v[1] + 3*v[2] + 2*v[3] + 1*v[4] + @test apply_stencil(s,v, 2) isa Float64 + @test apply_stencil_backwards(s,v, 3) isa Float64 + + v = [@SVector[1, 2], @SVector[3, 4], @SVector[5, 6], @SVector[7, 8]] + s = Stencil(1,2, center = 1) + @test apply_stencil(s,v,1) == @SVector[7, 10] + @test apply_stencil_backwards(s,v,3) == @SVector[11, 14] + @test apply_stencil(s,v,1) isa SVector{2, Int} + @test apply_stencil_backwards(s,v,3) isa SVector{2, Int} + + v = [@SVector[1., 2.], @SVector[3., 4.], @SVector[5., 6.], @SVector[7., 8.]] + s = Stencil(1,2, center = 1) + @test apply_stencil(s,v,1) == @SVector[7., 10.] + @test apply_stencil_backwards(s,v,3) == @SVector[11., 14.] + @test apply_stencil(s,v,1) isa SVector{2, Float64} + @test apply_stencil_backwards(s,v,3) isa SVector{2, Float64} + + v = [@SVector[1, 2], @SVector[3, 4], @SVector[5, 6], @SVector[7, 8]] + s = Stencil(1.,2., center = 1) + @test apply_stencil(s,v,1) == @SVector[7., 10.] + @test apply_stencil_backwards(s,v,3) == @SVector[11., 14.] + @test apply_stencil(s,v,1) isa SVector{2, Float64} + @test apply_stencil_backwards(s,v,3) isa SVector{2, Float64} + end + @testset "type stability" begin s_int = CenteredStencil(1,2,3) s_float = CenteredStencil(1.,2.,3.) @@ -62,6 +110,23 @@ end end +@testset "left_pad" begin + @test SbpOperators.left_pad(Stencil(1,1, center = 1), 2) == Stencil(1,1, center=1) + @test SbpOperators.left_pad(Stencil(1,1, center = 1), 3) == Stencil(0,1,1, center=2) + @test SbpOperators.left_pad(Stencil(2,3, center = 2), 4) == Stencil(0,0,2,3, center=4) + + @test SbpOperators.left_pad(Stencil(2.,3., center = 2), 4) == Stencil(0.,0.,2.,3., center=4) +end + +@testset "right_pad" begin + @test SbpOperators.right_pad(Stencil(1,1, center = 1), 2) == Stencil(1,1, center=1) + @test SbpOperators.right_pad(Stencil(1,1, center = 1), 3) == Stencil(1,1,0, center=1) + @test SbpOperators.right_pad(Stencil(2,3, center = 2), 4) == Stencil(2,3,0,0, center=2) + + @test SbpOperators.right_pad(Stencil(2.,3., center = 2), 4) == Stencil(2.,3.,0.,0., center=2) +end + + @testset "NestedStencil" begin @testset "Constructors" begin @@ -138,6 +203,27 @@ @test SbpOperators.apply_stencil(ns, c, v, 4) == 5*7 + 11*11 + 6*13 @test SbpOperators.apply_stencil_backwards(ns, c, v, 4) == -3*3 - 7*5 - 4*7 + + # Different types in vector and stencil + ns = NestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-2.,2.), center=2) + @test SbpOperators.apply_inner_stencils(ns, c, 4) isa Stencil{Float64, 3} + @test SbpOperators.apply_inner_stencils(ns, c, 4) == Stencil(4.,9.,10.; center=2) + @test SbpOperators.apply_inner_stencils_backwards(ns, c, 4) isa Stencil{Float64, 3} + @test SbpOperators.apply_inner_stencils_backwards(ns, c, 4) == Stencil(-5.,-9.,-8.; center=2) + + @test SbpOperators.apply_stencil(ns, c, v, 4) isa Float64 + @test SbpOperators.apply_stencil(ns, c, v, 4) == 193. + @test SbpOperators.apply_stencil_backwards(ns, c, v, 4) isa Float64 + @test SbpOperators.apply_stencil_backwards(ns, c, v, 4) == -158. + + # Arrays of vectors + ns = NestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-2.,2.), center=2) + c = [ 1, 3, 6, 10] + v = [@SVector[1, 2], @SVector[3, 4], @SVector[5, 6], @SVector[7, 8]] + @test SbpOperators.apply_stencil(ns, c, v, 2) isa SVector{2,Float64} + @test SbpOperators.apply_stencil(ns, c, v, 2) == 2v[1] + 5v[2] + 6v[3] + @test SbpOperators.apply_stencil_backwards(ns, c, v, 2) isa SVector{2,Float64} + @test SbpOperators.apply_stencil_backwards(ns, c, v, 2) == -4v[1] - 5v[2] - 3v[3] end @testset "type stability" begin @@ -170,5 +256,4 @@ @inferred SbpOperators.apply_stencil_backwards(s_int, c_float, v_float, 2) @inferred SbpOperators.apply_stencil_backwards(s_float, c_float, v_int, 2) end - end
--- a/test/SbpOperators/volumeops/constant_interior_scaling_operator_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/constant_interior_scaling_operator_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,9 +1,9 @@ using Test -using Sbplib.LazyTensors -using Sbplib.SbpOperators -import Sbplib.SbpOperators: ConstantInteriorScalingOperator -using Sbplib.Grids +using Diffinitive.LazyTensors +using Diffinitive.SbpOperators +import Diffinitive.SbpOperators: ConstantInteriorScalingOperator +using Diffinitive.Grids @testset "ConstantInteriorScalingOperator" begin @test ConstantInteriorScalingOperator(1, (2,3), 10) isa ConstantInteriorScalingOperator{Int,2} @@ -33,7 +33,7 @@ @test_throws DomainError ConstantInteriorScalingOperator(4,(2,3), 3) @testset "Grid constructor" begin - g = EquidistantGrid(11, 0., 2.) + g = equidistant_grid(0., 2., 11) @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/dissipation_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,219 @@ +using Test + +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors + +using Diffinitive.SbpOperators: Stencil + +using Diffinitive.SbpOperators: dissipation_interior_weights +using Diffinitive.SbpOperators: dissipation_interior_stencil, dissipation_transpose_interior_stencil +using Diffinitive.SbpOperators: midpoint, midpoint_transpose +using Diffinitive.SbpOperators: dissipation_lower_closure_size, dissipation_upper_closure_size +using Diffinitive.SbpOperators: dissipation_lower_closure_stencils,dissipation_upper_closure_stencils +using Diffinitive.SbpOperators: dissipation_transpose_lower_closure_stencils, dissipation_transpose_upper_closure_stencils + + +@testset "undivided_skewed04" begin + monomial(x,k) = k < 0 ? zero(x) : x^k/factorial(k) + g = equidistant_grid(0., 11., 20) + D,Dᵀ = undivided_skewed04(g, 1) + + @test D isa LazyTensor{Float64,1,1} + @test Dᵀ isa LazyTensor{Float64,1,1} + + @testset "Accuracy conditions" begin + N = 20 + g = equidistant_grid(0//1, 2//1, N) + h = only(spacing(g)) + @testset "D_$p" for p ∈ [1,2,3,4] + D,Dᵀ = undivided_skewed04(g, p) + + @testset "x^$k" for k ∈ 0:p + v = eval_on(g, x->monomial(x,k)) + vₚₓ = eval_on(g, x->monomial(x,k-p)) + + @test D*v == h^p * vₚₓ + end + end + end + + @testset "transpose equality" begin + function get_matrix(D) + N = only(range_size(D)) + M = only(domain_size(D)) + + Dmat = zeros(N,M) + e = zeros(M) + for i ∈ 1:M + if i > 1 + e[i-1] = 0. + end + e[i] = 1. + Dmat[:,i] = D*e + end + + return Dmat + end + + g = equidistant_grid(0., 1., 11) + @testset "D_$p" for p ∈ [1,2,3,4] + D,Dᵀ = undivided_skewed04(g, p) + + D̄ = get_matrix(D) + D̄ᵀ = get_matrix(Dᵀ) + + @test D̄ == D̄ᵀ' + end + end + + @testset "2D" begin + N = 20 + g = equidistant_grid((0,0), (2,1), N, 2N) + h = spacing.(g.grids) + + D,Dᵀ = undivided_skewed04(g, 3, 2) + + v = eval_on(g, x->monomial(x[1],4)*monomial(x[2],3)) + d³vdy³ = eval_on(g, x->monomial(x[1],4)*monomial(x[2],0)) + + @test D*v ≈ h[2]^3*d³vdy³ + end +end + +@testset "dissipation_interior_weights" begin + @test dissipation_interior_weights(1) == (-1, 1) + @test dissipation_interior_weights(2) == (1,-2, 1) + @test dissipation_interior_weights(3) == (-1, 3,-3, 1) + @test dissipation_interior_weights(4) == (1, -4, 6, -4, 1) +end + +@testset "dissipation_interior_stencil" begin + @test dissipation_interior_stencil(dissipation_interior_weights(1)) == Stencil(-1, 1, center=2) + @test dissipation_interior_stencil(dissipation_interior_weights(2)) == Stencil( 1,-2, 1, center=2) + @test dissipation_interior_stencil(dissipation_interior_weights(3)) == Stencil(-1, 3,-3, 1, center=3) + @test dissipation_interior_stencil(dissipation_interior_weights(4)) == Stencil( 1,-4, 6,-4, 1, center=3) +end + +@testset "dissipation_transpose_interior_stencil" begin + @test dissipation_transpose_interior_stencil(dissipation_interior_weights(1)) == Stencil(1,-1, center=1) + @test dissipation_transpose_interior_stencil(dissipation_interior_weights(2)) == Stencil(1,-2, 1, center=2) + @test dissipation_transpose_interior_stencil(dissipation_interior_weights(3)) == Stencil(1,-3, 3,-1, center=2) + @test dissipation_transpose_interior_stencil(dissipation_interior_weights(4)) == Stencil(1,-4, 6,-4, 1, center=3) +end + +@testset "midpoint" begin + @test midpoint((1,1)) == 2 + @test midpoint((1,1,1)) == 2 + @test midpoint((1,1,1,1)) == 3 + @test midpoint((1,1,1,1,1)) == 3 +end + +@testset "midpoint_transpose" begin + @test midpoint_transpose((1,1)) == 1 + @test midpoint_transpose((1,1,1)) == 2 + @test midpoint_transpose((1,1,1,1)) == 2 + @test midpoint_transpose((1,1,1,1,1)) == 3 +end + +@testset "dissipation_lower_closure_size" begin + @test dissipation_lower_closure_size((1,1)) == 1 + @test dissipation_lower_closure_size((1,1,1)) == 1 + @test dissipation_lower_closure_size((1,1,1,1)) == 2 + @test dissipation_lower_closure_size((1,1,1,1,1)) == 2 +end + +@testset "dissipation_upper_closure_size" begin + @test dissipation_upper_closure_size((1,1)) == 0 + @test dissipation_upper_closure_size((1,1,1)) == 1 + @test dissipation_upper_closure_size((1,1,1,1)) == 1 + @test dissipation_upper_closure_size((1,1,1,1,1)) == 2 +end + +@testset "dissipation_lower_closure_stencils" begin + cases = ( + (-1,1) => ( + Stencil(-1, 1, center=1), + ), + (1,-2,1) => ( + Stencil( 1,-2, 1, center=1), + ), + (-1,3,-3,1) => ( + Stencil(-1,3,-3,1, center=1), + Stencil(-1,3,-3,1, center=2), + ), + (1, -4, 6, -4, 1) => ( + Stencil(1, -4, 6, -4, 1, center=1), + Stencil(1, -4, 6, -4, 1, center=2), + ) + ) + @testset "interior_weights = $w" for (w, closure_stencils) ∈ cases + @test dissipation_lower_closure_stencils(w) == closure_stencils + end +end + +@testset "dissipation_upper_closure_stencils" begin + cases = ( + (-1,1) => (), + (1,-2,1) => ( + Stencil( 1,-2, 1, center=3), + ), + (-1,3,-3,1) => ( + Stencil(-1,3,-3,1, center=4), + ), + (1, -4, 6, -4, 1) => ( + Stencil(1, -4, 6, -4, 1, center=4), + Stencil(1, -4, 6, -4, 1, center=5), + ) + ) + @testset "interior_weights = $w" for (w, closure_stencils) ∈ cases + @test dissipation_upper_closure_stencils(w) == closure_stencils + end +end + + +@testset "dissipation_transpose_lower_closure_stencils" begin + cases = ( + (-1,1) => ( + Stencil(-1,-1, 0, center=1), + Stencil( 1, 1,-1, center=2), + ), + (1,-2,1) => ( + Stencil( 1, 1, 0, 0, center=1), + Stencil(-2,-2, 1, 0, center=2), + Stencil( 1, 1,-2, 1, center=3), + ), + (-1,3,-3,1) => ( + Stencil(-1,-1,-1, 0, 0, 0, center=1), + Stencil( 3, 3, 3,-1, 0, 0, center=2), + Stencil(-3,-3,-3, 3,-1, 0, center=3), + Stencil( 1, 1, 1,-3, 3,-1, center=4), + ), + ) + @testset "interior_weights = $w" for (w, closure_stencils) ∈ cases + @test dissipation_transpose_lower_closure_stencils(w) == closure_stencils + end +end + +@testset "dissipation_transpose_upper_closure_stencils" begin + cases = ( + (-1,1) => ( + Stencil( 1,-1, center = 1), + Stencil( 0, 1, center = 2), + ), + (1,-2,1) => ( + Stencil( 1,-2, 1, 1, center=2), + Stencil( 0, 1,-2,-2, center=3), + Stencil( 0, 0, 1, 1, center=4), + ), + (-1,3,-3,1) => ( + Stencil( 1,-3, 3,-1,-1, center=2), + Stencil( 0, 1,-3, 3, 3, center=3), + Stencil( 0, 0, 1,-3,-3, center=4), + Stencil( 0, 0, 0, 1, 1, center=5), + ), + ) + @testset "interior_weights = $w" for (w, closure_stencils) ∈ cases + @test dissipation_transpose_upper_closure_stencils(w) == closure_stencils + end +end
--- a/test/SbpOperators/volumeops/derivatives/first_derivative_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/derivatives/first_derivative_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,96 +1,83 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors - -using Sbplib.SbpOperators: closure_size, Stencil, VolumeOperator - -""" - monomial(x,k) +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors -Evaluates ``x^k/k!` with the convetion that it is ``0`` for all ``k<0``. -Has the property that ``d/dx monomial(x,k) = monomial(x,k-1)`` -""" -function monomial(x,k) - if k < 0 - return zero(x) - end - x^k/factorial(k) -end +using Diffinitive.SbpOperators: closure_size, Stencil, VolumeOperator @testset "first_derivative" begin @testset "Constructors" begin stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) - g₁ = EquidistantGrid(11, 0., 1.) - g₂ = EquidistantGrid((11,14), (0.,1.), (1.,3.)) + g₁ = equidistant_grid(0., 1., 11) + g₂ = equidistant_grid((0.,1.), (1.,3.), 11, 14) - @test first_derivative(g₁, stencil_set, 1) isa LazyTensor{Float64,1,1} + @test first_derivative(g₁, stencil_set) isa LazyTensor{Float64,1,1} @test first_derivative(g₂, stencil_set, 2) isa LazyTensor{Float64,2,2} - @test first_derivative(g₁, stencil_set, 1) == first_derivative(g₁, stencil_set) interior_stencil = CenteredStencil(-1,0,1) closure_stencils = [Stencil(-1,1, center=1)] - @test first_derivative(g₁, interior_stencil, closure_stencils, 1) isa LazyTensor{Float64,1,1} - @test first_derivative(g₁, interior_stencil, closure_stencils, 1) isa VolumeOperator - @test first_derivative(g₁, interior_stencil, closure_stencils, 1) == first_derivative(g₁, interior_stencil, closure_stencils) - @test first_derivative(g₂, interior_stencil, closure_stencils, 2) isa LazyTensor{Float64,2,2} + @test first_derivative(g₁, interior_stencil, closure_stencils) isa LazyTensor{Float64,1,1} end @testset "Accuracy conditions" begin N = 20 - g = EquidistantGrid(N, 0//1,2//1) + g = equidistant_grid(0//1, 2//1, N) + + monomial(x,k) = k < 0 ? zero(x) : x^k/factorial(k) @testset for order ∈ [2,4] stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order) - D₁ = first_derivative(g, stencil_set, 1) + D₁ = first_derivative(g, stencil_set) @testset "boundary x^$k" for k ∈ 0:order÷2 - v = evalOn(g, x->monomial(x,k)) + v = eval_on(g, x->monomial(x,k)) @testset for i ∈ 1:closure_size(D₁) - x, = points(g)[i] + x, = g[i] @test (D₁*v)[i] == monomial(x,k-1) end @testset for i ∈ (N-closure_size(D₁)+1):N - x, = points(g)[i] + x, = g[i] @test (D₁*v)[i] == monomial(x,k-1) end end @testset "interior x^$k" for k ∈ 0:order - v = evalOn(g, x->monomial(x,k)) + v = eval_on(g, x->monomial(x,k)) - x, = points(g)[10] + x, = g[10] @test (D₁*v)[10] == monomial(x,k-1) end end end @testset "Accuracy on function" begin - # 1D - g = EquidistantGrid(30, 0.,1.) - v = evalOn(g, x->exp(x)) - @testset for (order, tol) ∈ [(2, 6e-3),(4, 2e-4)] - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order) - D₁ = first_derivative(g, stencil_set, 1) + @testset "1D" begin + g = equidistant_grid(0., 1., 30) + v = eval_on(g, x->exp(x)) + @testset for (order, tol) ∈ [(2, 6e-3),(4, 2e-4)] + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order) + D₁ = first_derivative(g, stencil_set) - @test D₁*v ≈ v rtol=tol + @test D₁*v ≈ v rtol=tol + end end - # 2D - g = EquidistantGrid((30,60), (0.,0.),(1.,2.)) - v = evalOn(g, (x,y)->exp(0.8x+1.2*y)) - @testset for (order, tol) ∈ [(2, 6e-3),(4, 3e-4)] - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order) - Dx = first_derivative(g, stencil_set, 1) - Dy = first_derivative(g, stencil_set, 2) + @testset "2D" begin + g = equidistant_grid((0.,0.),(1.,2.), 30, 60) + v = eval_on(g, (x,y)->exp(0.8x+1.2*y)) + @testset for (order, tol) ∈ [(2, 6e-3),(4, 3e-4)] + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order) + Dx = first_derivative(g, stencil_set, 1) + Dy = first_derivative(g, stencil_set, 2) - @test Dx*v ≈ 0.8v rtol=tol - @test Dy*v ≈ 1.2v rtol=tol + @test Dx*v ≈ 0.8v rtol=tol + @test Dy*v ≈ 1.2v rtol=tol + end end end end
--- a/test/SbpOperators/volumeops/derivatives/second_derivative_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/derivatives/second_derivative_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,10 +1,12 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors -import Sbplib.SbpOperators.VolumeOperator +import Diffinitive.SbpOperators.VolumeOperator + +# TODO: Refactor these test to look more like the tests in first_derivative_test.jl. @testset "SecondDerivative" begin operator_path = sbp_operators_path()*"standard_diagonal.toml" @@ -13,24 +15,18 @@ 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)) + g_1D = equidistant_grid(0.0, Lx, 121) + g_2D = equidistant_grid((0.0, 0.0), (Lx, Ly), 121, 123) @testset "Constructors" begin @testset "1D" begin - Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils,1) - @test Dₓₓ == second_derivative(g_1D,inner_stencil,closure_stencils) - @test Dₓₓ == second_derivative(g_1D,stencil_set,1) - @test Dₓₓ == second_derivative(g_1D,stencil_set) - @test Dₓₓ isa VolumeOperator + Dₓₓ = second_derivative(g_1D, stencil_set) + @test Dₓₓ == second_derivative(g_1D, inner_stencil, closure_stencils) + @test Dₓₓ isa LazyTensor{Float64,1,1} end @testset "2D" begin - Dₓₓ = second_derivative(g_2D,inner_stencil,closure_stencils,1) - D2 = second_derivative(g_1D,inner_stencil,closure_stencils,1) - I = IdentityTensor{Float64}(size(g_2D)[2]) - @test Dₓₓ == D2⊗I - @test Dₓₓ == second_derivative(g_2D,stencil_set,1) - @test Dₓₓ isa LazyTensor{T,2,2} where T + Dₓₓ = second_derivative(g_2D,stencil_set,1) + @test Dₓₓ isa LazyTensor{Float64,2,2} end end @@ -43,10 +39,10 @@ maxOrder = 4; for i = 0:maxOrder-1 f_i(x) = 1/factorial(i)*x^i - monomials = (monomials...,evalOn(g_1D,f_i)) + monomials = (monomials...,eval_on(g_1D,f_i)) end - v = evalOn(g_1D,x -> sin(x)) - vₓₓ = evalOn(g_1D,x -> -sin(x)) + v = eval_on(g_1D,x -> sin(x)) + vₓₓ = eval_on(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. @@ -75,15 +71,15 @@ end @testset "2D" begin - l2(v) = sqrt(prod(spacing(g_2D))*sum(v.^2)); + l2(v) = sqrt(prod(spacing.(g_2D.grids))*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)) + binomials = (binomials...,eval_on(g_2D,f_i)) end - v = evalOn(g_2D, (x,y) -> sin(x)+cos(y)) - v_yy = evalOn(g_2D,(x,y) -> -cos(y)) + v = eval_on(g_2D, (x,y) -> sin(x)+cos(y)) + v_yy = eval_on(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. @@ -92,7 +88,7 @@ Dyy = second_derivative(g_2D,stencil_set,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*binomials[3] ≈ eval_on(g_2D,(x,y)->1.) atol = 5e-9 @test Dyy*v ≈ v_yy rtol = 5e-2 norm = l2 end @@ -105,8 +101,8 @@ # 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*binomials[3] ≈ eval_on(g_2D,(x,y)->1.) atol = 5e-9 + @test Dyy*binomials[4] ≈ eval_on(g_2D,(x,y)->y) atol = 5e-9 @test Dyy*v ≈ v_yy rtol = 5e-4 norm = l2 end end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/volumeops/derivatives/second_derivative_variable_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,247 @@ +using Test + +using Diffinitive.Grids +using Diffinitive.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.RegionIndices +using Diffinitive.SbpOperators: NestedStencil, CenteredNestedStencil, SecondDerivativeVariable + +using LinearAlgebra + +@testset "second_derivative_variable" begin + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) + + @testset "1D" begin + g = equidistant_grid(0., 1., 11) + c = [ 1., 3., 6., 10., 15., 21., 28., 36., 45., 55., 66.] + + @testset "checking c" begin + c_short = rand(5) + c_long = rand(16) + c_higher_dimension = rand(11,11) + + @test_throws DimensionMismatch("the size (5,) of the coefficient does not match the size (11,) of the grid") second_derivative_variable(g, c_short, stencil_set) + @test_throws DimensionMismatch("the size (16,) of the coefficient does not match the size (11,) of the grid") second_derivative_variable(g, c_long, stencil_set) + @test_throws ArgumentError("The coefficient has dimension 2 while the grid is dimension 1") second_derivative_variable(g, c_higher_dimension, stencil_set) + end + + @testset "application" begin + function apply_to_functions(; v, c) + g = equidistant_grid(0., 10., 11) # h = 1 + c̄ = eval_on(g,c) + v̄ = eval_on(g,v) + + D₂ᶜ = second_derivative_variable(g, c̄, stencil_set) + return D₂ᶜ*v̄ + end + + @test apply_to_functions(v=x->1., c=x-> -1.) == zeros(11) + @test apply_to_functions(v=x->1., c=x-> -x ) == zeros(11) + @test apply_to_functions(v=x->x, c=x-> 1.) == zeros(11) + @test apply_to_functions(v=x->x, c=x-> -x ) == -ones(11) + @test apply_to_functions(v=x->x^2, c=x-> 1.) == 2ones(11) + end + end + + @testset "2D" begin + g = equidistant_grid((0.,0.), (10.,8.), 11, 9) # h = 1 + c = eval_on(g, (x,y)->x+y) + + @testset "application" begin + function apply_to_functions(dir; v, c) + g = equidistant_grid((0.,0.), (10.,8.), 11, 9) # h = 1 + c̄ = eval_on(g,c) + v̄ = eval_on(g,v) + + D₂ᶜ = second_derivative_variable(g, c̄, stencil_set, dir) + return D₂ᶜ*v̄ + end + + # x-direction + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)-> -1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)->- x ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->x, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->x, c=(x,y)-> -x ) == -ones(11,9) + @test apply_to_functions(1,v=(x,y)->x^2, c=(x,y)-> 1.) == 2ones(11,9) + + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)->- y ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y, c=(x,y)-> -y ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y^2, c=(x,y)-> 1.) == zeros(11,9) + + # y-direction + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)-> -1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)->- y ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->y, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->y, c=(x,y)-> -y ) == -ones(11,9) + @test apply_to_functions(2,v=(x,y)->y^2, c=(x,y)-> 1.) == 2ones(11,9) + + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)->- x ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x, c=(x,y)-> -x ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x^2, c=(x,y)-> 1.) == zeros(11,9) + + + @testset "standard diagonal operators" begin + c(x,y) = exp(x) + exp(1.5(1-y)) + v(x,y) = sin(x) + cos(1.5(1-y)) + + Dxv(x,y) = cos(x)*exp(x) - (exp(x) + exp(1.5 - 1.5y))*sin(x) + Dyv(x,y) = -1.5(1.5exp(x) + 1.5exp(1.5 - 1.5y))*cos(1.5 - 1.5y) - 2.25exp(1.5 - 1.5y)*sin(1.5 - 1.5y) + + g₁ = equidistant_grid((0.,0.), (1.,2.), 60, 67) + g₂ = refine(g₁,2) + + c̄₁ = eval_on(g₁, c) + c̄₂ = eval_on(g₂, c) + + v̄₁ = eval_on(g₁, v) + v̄₂ = eval_on(g₂, v) + + + function convergence_rate_estimate(stencil_set, dir, Dv_true) + D₁ = second_derivative_variable(g₁, c̄₁, stencil_set, dir) + D₂ = second_derivative_variable(g₂, c̄₂, stencil_set, dir) + + Dv̄₁ = D₁*v̄₁ + Dv̄₂ = D₂*v̄₂ + + Dv₁ = eval_on(g₁,Dv_true) + Dv₂ = eval_on(g₂,Dv_true) + + e₁ = norm(Dv̄₁ - Dv₁)/norm(Dv₁) + e₂ = norm(Dv̄₂ - Dv₂)/norm(Dv₂) + + return log2(e₁/e₂) + end + + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order = 2) + @test convergence_rate_estimate(stencil_set, 1, Dxv) ≈ 1.5 rtol = 1e-1 + @test convergence_rate_estimate(stencil_set, 2, Dyv) ≈ 1.5 rtol = 1e-1 + + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order = 4) + @test convergence_rate_estimate(stencil_set, 1, Dxv) ≈ 2.5 rtol = 1e-1 + @test convergence_rate_estimate(stencil_set, 2, Dyv) ≈ 2.5 rtol = 2e-1 + end + end + end +end + + +@testset "SecondDerivativeVariable" begin + interior_stencil = CenteredNestedStencil((1/2, 1/2, 0.),(-1/2, -1., -1/2),( 0., 1/2, 1/2)) + closure_stencils = ( + NestedStencil(( 2., -1., 0.),(-3., 1., 0.), (1., 0., 0.), center = 1), + ) + + @testset "1D" begin + c = [ 1., 3., 6., 10., 15., 21., 28., 36., 45., 55., 66.] + @testset "Constructors" begin + @test SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) isa LazyTensor + + D₂ᶜ = SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) + @test range_dim(D₂ᶜ) == 1 + @test domain_dim(D₂ᶜ) == 1 + + end + + @testset "sizes" begin + D₂ᶜ = SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) + @test closure_size(D₂ᶜ) == 1 + @test range_size(D₂ᶜ) == (11,) + @test domain_size(D₂ᶜ) == (11,) + end + + @testset "application" begin + + function apply_to_functions(; v, c) + g = equidistant_grid(0., 10., 11) # h = 1 + c̄ = eval_on(g,c) + v̄ = eval_on(g,v) + + D₂ᶜ = SecondDerivativeVariable(c̄, interior_stencil, closure_stencils, 1) + return D₂ᶜ*v̄ + end + + @test apply_to_functions(v=x->1., c=x-> -1.) == zeros(11) + @test apply_to_functions(v=x->1., c=x-> -x ) == zeros(11) + @test apply_to_functions(v=x->x, c=x-> 1.) == zeros(11) + @test apply_to_functions(v=x->x, c=x-> -x ) == -ones(11) + @test apply_to_functions(v=x->x^2, c=x-> 1.) == 2ones(11) + end + + @testset "type stability" begin + g = equidistant_grid(0., 10., 11) # h = 1 + c̄ = eval_on(g,x-> -1) + v̄ = eval_on(g,x->1.) + + D₂ᶜ = SecondDerivativeVariable(c̄, interior_stencil, closure_stencils, 1) + + @inferred SbpOperators.apply_lower(D₂ᶜ, v̄, 1) + @inferred SbpOperators.apply_interior(D₂ᶜ, v̄, 5) + @inferred SbpOperators.apply_upper(D₂ᶜ, v̄, 11) + @inferred (D₂ᶜ*v̄)[Index(1,Lower)] + end + end + + @testset "2D" begin + g = equidistant_grid((0.,0.), (10.,8.), 11, 9) # h = 1 + c = eval_on(g, (x,y)->x+y) + @testset "Constructors" begin + @test SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) isa LazyTensor + @test SecondDerivativeVariable(c, interior_stencil, closure_stencils, 2) isa LazyTensor + + D₂ᶜ = SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) + @test range_dim(D₂ᶜ) == 2 + @test domain_dim(D₂ᶜ) == 2 + end + + @testset "sizes" begin + D₂ᶜ = SecondDerivativeVariable(c, interior_stencil, closure_stencils, 1) + @test range_size(D₂ᶜ) == (11,9) + @test domain_size(D₂ᶜ) == (11,9) + @test closure_size(D₂ᶜ) == 1 + + D₂ᶜ = SecondDerivativeVariable(c, interior_stencil, closure_stencils, 2) + @test range_size(D₂ᶜ) == (11,9) + @test domain_size(D₂ᶜ) == (11,9) + @test closure_size(D₂ᶜ) == 1 + end + + @testset "application" begin + function apply_to_functions(dir; v, c) + g = equidistant_grid((0.,0.), (10.,8.), 11, 9) # h = 1 + c̄ = eval_on(g,c) + v̄ = eval_on(g,v) + + D₂ᶜ = SecondDerivativeVariable(c̄, interior_stencil, closure_stencils, dir) + return D₂ᶜ*v̄ + end + + # x-direction + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)-> -1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)->- x ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->x, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->x, c=(x,y)-> -x ) == -ones(11,9) + @test apply_to_functions(1,v=(x,y)->x^2, c=(x,y)-> 1.) == 2ones(11,9) + + @test apply_to_functions(1,v=(x,y)->1., c=(x,y)->- y ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y, c=(x,y)-> -y ) == zeros(11,9) + @test apply_to_functions(1,v=(x,y)->y^2, c=(x,y)-> 1.) == zeros(11,9) + + # y-direction + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)-> -1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)->- y ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->y, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->y, c=(x,y)-> -y ) == -ones(11,9) + @test apply_to_functions(2,v=(x,y)->y^2, c=(x,y)-> 1.) == 2ones(11,9) + + @test apply_to_functions(2,v=(x,y)->1., c=(x,y)->- x ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x, c=(x,y)-> 1.) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x, c=(x,y)-> -x ) == zeros(11,9) + @test apply_to_functions(2,v=(x,y)->x^2, c=(x,y)-> 1.) == zeros(11,9) + end + end +end +
--- a/test/SbpOperators/volumeops/inner_products/inner_product_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/inner_products/inner_product_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,56 +1,48 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors -import Sbplib.SbpOperators.ConstantInteriorScalingOperator +import Diffinitive.SbpOperators.ConstantInteriorScalingOperator @testset "Diagonal-stencil inner_product" begin Lx = π/2. Ly = Float64(π) Lz = 1. - g_1D = EquidistantGrid(77, 0.0, Lx) - g_2D = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly)) - g_3D = EquidistantGrid((10,10, 10), (0.0, 0.0, 0.0), (Lx,Ly,Lz)) - integral(H,v) = sum(H*v) + g_1D = equidistant_grid(0.0, Lx, 77) + g_2D = equidistant_grid((0.0, 0.0), (Lx,Ly), 77, 66) + g_3D = equidistant_grid((0.0, 0.0, 0.0), (Lx,Ly,Lz), 10, 10, 10) @testset "inner_product" begin 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}(), quadrature_interior, quadrature_closure) - @test H == inner_product(EquidistantGrid{Float64}(), stencil_set) - @test H == IdentityTensor{Float64}() + H = inner_product(ZeroDimGrid(0.), stencil_set) @test H isa LazyTensor{T,0,0} where T end @testset "1D" begin - H = inner_product(g_1D, quadrature_interior, quadrature_closure) - @test H == inner_product(g_1D, stencil_set) - @test H isa ConstantInteriorScalingOperator + H = inner_product(g_1D, stencil_set) @test H isa LazyTensor{T,1,1} where T end @testset "2D" begin - 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 == inner_product(g_2D, stencil_set) + H = inner_product(g_2D, stencil_set) + H_x = inner_product(g_2D.grids[1], stencil_set) + H_y = inner_product(g_2D.grids[2], stencil_set) @test H == H_x⊗H_y @test H isa LazyTensor{T,2,2} where T end + + # TBD: Should there be more tests? end @testset "Sizes" begin 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, quadrature_interior, quadrature_closure) + H = inner_product(g_1D, stencil_set) @test domain_size(H) == size(g_1D) @test range_size(H) == size(g_1D) end @testset "2D" begin - H = inner_product(g_2D, quadrature_interior, quadrature_closure) + H = inner_product(g_2D, stencil_set) @test domain_size(H) == size(g_2D) @test range_size(H) == size(g_2D) end @@ -61,52 +53,44 @@ v = () for i = 0:4 f_i(x) = 1/factorial(i)*x^i - v = (v...,evalOn(g_1D,f_i)) + v = (v...,eval_on(g_1D,f_i)) end - u = evalOn(g_1D,x->sin(x)) + u = eval_on(g_1D,x->sin(x)) @testset "2nd order" begin 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) + H = inner_product(g_1D, stencil_set) for i = 1:2 - @test integral(H,v[i]) ≈ v[i+1][end] - v[i+1][1] rtol = 1e-14 + @test sum(H*v[i]) ≈ v[i+1][end] - v[i+1][1] rtol = 1e-14 end - @test integral(H,u) ≈ 1. rtol = 1e-4 + @test sum(H*u) ≈ 1. rtol = 1e-4 end @testset "4th order" begin 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) + H = inner_product(g_1D, stencil_set) for i = 1:4 - @test integral(H,v[i]) ≈ v[i+1][end] - v[i+1][1] rtol = 1e-14 + @test sum(H*v[i]) ≈ v[i+1][end] - v[i+1][1] rtol = 1e-14 end - @test integral(H,u) ≈ 1. rtol = 1e-8 + @test sum(H*u) ≈ 1. rtol = 1e-8 end end @testset "2D" begin b = 2.1 v = b*ones(Float64, size(g_2D)) - u = evalOn(g_2D,(x,y)->sin(x)+cos(y)) + u = eval_on(g_2D,(x,y)->sin(x)+cos(y)) @testset "2nd order" begin 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 + H = inner_product(g_2D, stencil_set) + @test sum(H*v) ≈ b*Lx*Ly rtol = 1e-13 + @test sum(H*u) ≈ π rtol = 1e-4 end @testset "4th order" begin 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 + H = inner_product(g_2D, stencil_set) + @test sum(H*v) ≈ b*Lx*Ly rtol = 1e-13 + @test sum(H*u) ≈ π rtol = 1e-8 end end end
--- a/test/SbpOperators/volumeops/inner_products/inverse_inner_product_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/inner_products/inverse_inner_product_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,37 +1,30 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors -import Sbplib.SbpOperators.ConstantInteriorScalingOperator +import Diffinitive.SbpOperators.ConstantInteriorScalingOperator @testset "Diagonal-stencil inverse_inner_product" begin Lx = π/2. Ly = Float64(π) - g_1D = EquidistantGrid(77, 0.0, Lx) - g_2D = EquidistantGrid((77,66), (0.0, 0.0), (Lx,Ly)) + g_1D = equidistant_grid(0.0, Lx, 77) + g_2D = equidistant_grid((0.0, 0.0), (Lx,Ly), 77, 66) @testset "inverse_inner_product" begin 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}(), quadrature_interior, quadrature_closure) - @test Hi == inverse_inner_product(EquidistantGrid{Float64}(), stencil_set) - @test Hi == IdentityTensor{Float64}() + Hi = inverse_inner_product(ZeroDimGrid(1.), stencil_set) @test Hi isa LazyTensor{T,0,0} where T end @testset "1D" begin - Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure) - @test Hi == inverse_inner_product(g_1D, stencil_set) - @test Hi isa ConstantInteriorScalingOperator + Hi = inverse_inner_product(g_1D, stencil_set) @test Hi isa LazyTensor{T,1,1} where T end @testset "2D" begin - 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 == inverse_inner_product(g_2D, stencil_set) + Hi = inverse_inner_product(g_2D, stencil_set) + Hi_x = inverse_inner_product(g_2D.grids[1], stencil_set) + Hi_y = inverse_inner_product(g_2D.grids[2], stencil_set) @test Hi == Hi_x⊗Hi_y @test Hi isa LazyTensor{T,2,2} where T end @@ -39,15 +32,13 @@ @testset "Sizes" begin 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, quadrature_interior, quadrature_closure) + Hi = inverse_inner_product(g_1D, stencil_set) @test domain_size(Hi) == size(g_1D) @test range_size(Hi) == size(g_1D) end @testset "2D" begin - Hi = inverse_inner_product(g_2D, quadrature_interior, quadrature_closure) + Hi = inverse_inner_product(g_2D, stencil_set) @test domain_size(Hi) == size(g_2D) @test range_size(Hi) == size(g_2D) end @@ -55,45 +46,37 @@ @testset "Accuracy" begin @testset "1D" begin - v = evalOn(g_1D,x->sin(x)) - u = evalOn(g_1D,x->x^3-x^2+1) + v = eval_on(g_1D,x->sin(x)) + u = eval_on(g_1D,x->x^3-x^2+1) @testset "2nd order" begin 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) + H = inner_product(g_1D, stencil_set) + Hi = inverse_inner_product(g_1D, stencil_set) @test Hi*H*v ≈ v rtol = 1e-15 @test Hi*H*u ≈ u rtol = 1e-15 end @testset "4th order" begin 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) + H = inner_product(g_1D, stencil_set) + Hi = inverse_inner_product(g_1D, stencil_set) @test Hi*H*v ≈ v rtol = 1e-15 @test Hi*H*u ≈ u rtol = 1e-15 end end @testset "2D" begin - v = evalOn(g_2D,(x,y)->sin(x)+cos(y)) - u = evalOn(g_2D,(x,y)->x*y + x^5 - sqrt(y)) + v = eval_on(g_2D,(x,y)->sin(x)+cos(y)) + u = eval_on(g_2D,(x,y)->x*y + x^5 - sqrt(y)) @testset "2nd order" begin 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) + H = inner_product(g_2D, stencil_set) + Hi = inverse_inner_product(g_2D, stencil_set) @test Hi*H*v ≈ v rtol = 1e-15 @test Hi*H*u ≈ u rtol = 1e-15 end @testset "4th order" begin 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) + H = inner_product(g_2D, stencil_set) + Hi = inverse_inner_product(g_2D, stencil_set) @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 Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/laplace/laplace_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,43 +1,43 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors - -# Default stencils (4th order) -operator_path = sbp_operators_path()*"standard_diagonal.toml" -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"]) -g_1D = EquidistantGrid(101, 0.0, 1.) -g_3D = EquidistantGrid((51,101,52), (0.0, -1.0, 0.0), (1., 1., 1.)) +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors @testset "Laplace" begin + # Default stencils (4th order) + operator_path = sbp_operators_path()*"standard_diagonal.toml" + stencil_set = read_stencil_set(operator_path; order=4) + g_1D = equidistant_grid(0.0, 1., 101) + g_3D = equidistant_grid((0.0, -1.0, 0.0), (1., 1., 1.), 51, 101, 52) + @testset "Constructors" begin @testset "1D" begin - Δ = laplace(g_1D, inner_stencil, closure_stencils) - @test Laplace(g_1D, stencil_set) == Laplace(Δ, stencil_set) - @test Laplace(g_1D, stencil_set) isa LazyTensor{T,1,1} where T + @test Laplace(g_1D, stencil_set) == Laplace(laplace(g_1D, stencil_set), stencil_set) + @test Laplace(g_1D, stencil_set) isa LazyTensor{Float64,1,1} end @testset "3D" begin - Δ = laplace(g_3D, inner_stencil, closure_stencils) - @test Laplace(g_3D, stencil_set) == Laplace(Δ,stencil_set) - @test Laplace(g_3D, stencil_set) isa LazyTensor{T,3,3} where T + @test Laplace(g_3D, stencil_set) == Laplace(laplace(g_3D, stencil_set),stencil_set) + @test Laplace(g_3D, stencil_set) isa LazyTensor{Float64,3,3} end end # Exact differentiation is measured point-wise. In other cases # the error is measured in the l2-norm. @testset "Accuracy" begin - l2(v) = sqrt(prod(spacing(g_3D))*sum(v.^2)); + l2(v) = sqrt(prod(spacing.(g_3D.grids))*sum(v.^2)); polynomials = () maxOrder = 4; for i = 0:maxOrder-1 f_i(x,y,z) = 1/factorial(i)*(y^i + x^i + z^i) - polynomials = (polynomials...,evalOn(g_3D,f_i)) + polynomials = (polynomials...,eval_on(g_3D,f_i)) end - v = evalOn(g_3D, (x,y,z) -> sin(x) + cos(y) + exp(z)) - Δv = evalOn(g_3D,(x,y,z) -> -sin(x) - cos(y) + exp(z)) + # v = eval_on(g_3D, (x,y,z) -> sin(x) + cos(y) + exp(z)) + # Δv = eval_on(g_3D,(x,y,z) -> -sin(x) - cos(y) + exp(z)) + + v = eval_on(g_3D, x̄ -> sin(x̄[1]) + cos(x̄[2]) + exp(x̄[3])) + Δv = eval_on(g_3D, x̄ -> -sin(x̄[1]) - cos(x̄[2]) + exp(x̄[3])) + @inferred v[1,2,3] # 2nd order interior stencil, 1st order boundary stencil, # implies that L*v should be exact for binomials up to order 2. @@ -67,19 +67,87 @@ end @testset "laplace" begin + operator_path = sbp_operators_path()*"standard_diagonal.toml" + stencil_set = read_stencil_set(operator_path; order=4) + g_1D = equidistant_grid(0.0, 1., 101) + g_3D = equidistant_grid((0.0, -1.0, 0.0), (1., 1., 1.), 51, 101, 52) + @testset "1D" begin - Δ = laplace(g_1D, inner_stencil, closure_stencils) - @test Δ == second_derivative(g_1D, inner_stencil, closure_stencils, 1) - @test Δ isa LazyTensor{T,1,1} where T + Δ = laplace(g_1D, stencil_set) + @test Δ == second_derivative(g_1D, stencil_set) + @test Δ isa LazyTensor{Float64,1,1} end @testset "3D" begin - Δ = laplace(g_3D, inner_stencil, closure_stencils) - @test Δ isa LazyTensor{T,3,3} where T - 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) + Δ = laplace(g_3D, stencil_set) + @test Δ isa LazyTensor{Float64,3,3} + Dxx = second_derivative(g_3D, stencil_set, 1) + Dyy = second_derivative(g_3D, stencil_set, 2) + Dzz = second_derivative(g_3D, stencil_set, 3) @test Δ == Dxx + Dyy + Dzz - @test Δ isa LazyTensor{T,3,3} where T + @test Δ isa LazyTensor{Float64,3,3} end end +@testset "sat_tensors" begin + # TODO: The following tests should be implemented + # 1. Symmetry D'H == H'D (test_broken below) + # 2. Test eigenvalues of and/or solution to Poisson + # 3. Test tuning of Dirichlet conditions + # + # These tests are likely easiest to implement once + # we have support for generating matrices from tensors. + + operator_path = sbp_operators_path()*"standard_diagonal.toml" + orders = (2,4) + tols = (5e-2,5e-4) + sz = (201,401) + g = equidistant_grid((0.,0.), (1.,1.), sz...) + + # Verify implementation of sat_tensors by testing accuracy and symmetry (TODO) + # of the operator D = Δ + SAT, where SAT is the tensor composition of the + # operators from sat_tensor. Note that SAT*u should approximate 0 for the + # conditions chosen. + + @testset "Dirichlet" begin + for (o, tol) ∈ zip(orders,tols) + stencil_set = read_stencil_set(operator_path; order=o) + Δ = Laplace(g, stencil_set) + H = inner_product(g, stencil_set) + u = collect(eval_on(g, (x,y) -> sin(π*x)sin(2*π*y))) + Δu = collect(eval_on(g, (x,y) -> -5*π^2*sin(π*x)sin(2*π*y))) + D = Δ + for id ∈ boundary_identifiers(g) + D = D + foldl(∘, sat_tensors(Δ, g, DirichletCondition(0., id))) + end + e = D*u .- Δu + # Accuracy + @test sqrt(sum(H*e.^2)) ≈ 0 atol = tol + # Symmetry + r = randn(size(u)) + @test_broken (D'∘H - H∘D)*r .≈ 0 atol = 1e-13 # TODO: Need to implement apply_transpose for D. + end + end + + @testset "Neumann" begin + @testset "Dirichlet" begin + for (o, tol) ∈ zip(orders,tols) + stencil_set = read_stencil_set(operator_path; order=o) + Δ = Laplace(g, stencil_set) + H = inner_product(g, stencil_set) + u = collect(eval_on(g, (x,y) -> cos(π*x)cos(2*π*y))) + Δu = collect(eval_on(g, (x,y) -> -5*π^2*cos(π*x)cos(2*π*y))) + D = Δ + for id ∈ boundary_identifiers(g) + D = D + foldl(∘, sat_tensors(Δ, g, NeumannCondition(0., id))) + end + e = D*u .- Δu + # Accuracy + @test sqrt(sum(H*e.^2)) ≈ 0 atol = tol + # Symmetry + r = randn(size(u)) + @test_broken (D'∘H - H∘D)*r .≈ 0 atol = 1e-13 # TODO: Need to implement apply_transpose for D. + end + end + end +end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/volumeops/stencil_operator_distinct_closures_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,51 @@ +using Test + +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors + +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.StencilOperatorDistinctClosures + +@testset "StencilOperatorDistinctClosures" begin + g = equidistant_grid(0., 1., 11) + + lower_closure = ( + Stencil(-1,1, center=1), + Stencil(-2,2, center=2), + ) + + inner_stencil = Stencil(-3,3, center=1) + + upper_closure = ( + Stencil(4,-4,4, center=1), + Stencil(5,-5,5, center=2), + Stencil(6,-6,6, center=3), + ) + + A = StencilOperatorDistinctClosures(g, inner_stencil, lower_closure, upper_closure) + @test A isa LazyTensor{T,1,1} where T + + @test SbpOperators.lower_closure_size(A) == 2 + @test SbpOperators.upper_closure_size(A) == 3 + + @test domain_size(A) == (11,) + @test range_size(A) == (11,) + + v = rand(11) + @testset "apply" begin + # Lower closure + @test LazyTensors.apply(A, v, 1) ≈ 1*(-v[1] + v[2]) + @test LazyTensors.apply(A, v, 2) ≈ 2*(-v[1] + v[2]) + + # Interior + @test LazyTensors.apply(A, v, 3) ≈ 3*(-v[3] + v[4]) + @test LazyTensors.apply(A, v, 4) ≈ 3*(-v[4] + v[5]) + @test LazyTensors.apply(A, v, 8) ≈ 3*(-v[8] + v[9]) + + # Upper closure + @test LazyTensors.apply(A, v, 9) ≈ 4*(v[9] - v[10] + v[11]) + @test LazyTensors.apply(A, v, 10) ≈ 5*(v[9] - v[10] + v[11]) + @test LazyTensors.apply(A, v, 11) ≈ 6*(v[9] - v[10] + v[11]) + end +end
--- a/test/SbpOperators/volumeops/volume_operator_test.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/SbpOperators/volumeops/volume_operator_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,126 +1,82 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.RegionIndices -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.RegionIndices +using Diffinitive.LazyTensors -import Sbplib.SbpOperators.Stencil -import Sbplib.SbpOperators.VolumeOperator -import Sbplib.SbpOperators.volume_operator -import Sbplib.SbpOperators.odd -import Sbplib.SbpOperators.even +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.VolumeOperator +import Diffinitive.SbpOperators.odd +import Diffinitive.SbpOperators.even + @testset "VolumeOperator" begin inner_stencil = CenteredStencil(1/4, 2/4, 1/4) - closure_stencils = (Stencil(1/2, 1/2; center=1), Stencil(0.,1.; center=2)) - g_1D = EquidistantGrid(11,0.,1.) - g_2D = EquidistantGrid((11,12),(0.,0.),(1.,1.)) - g_3D = EquidistantGrid((11,12,10),(0.,0.,0.),(1.,1.,1.)) + closure_stencils = (Stencil(1/2, 1/2; center=1), Stencil(2.,1.; center=2)) + g = equidistant_grid(0.,1., 11) + @testset "Constructors" begin - @testset "1D" begin - op = VolumeOperator(inner_stencil,closure_stencils,(11,),even) - @test op == VolumeOperator(g_1D,inner_stencil,closure_stencils,even) - @test op == volume_operator(g_1D,inner_stencil,closure_stencils,even,1) - @test op isa LazyTensor{T,1,1} where T - end - @testset "2D" begin - op_x = volume_operator(g_2D,inner_stencil,closure_stencils,even,1) - op_y = volume_operator(g_2D,inner_stencil,closure_stencils,even,2) - Ix = IdentityTensor{Float64}((11,)) - Iy = IdentityTensor{Float64}((12,)) - @test op_x == VolumeOperator(inner_stencil,closure_stencils,(11,),even)⊗Iy - @test op_y == Ix⊗VolumeOperator(inner_stencil,closure_stencils,(12,),even) - @test op_x isa LazyTensor{T,2,2} where T - @test op_y isa LazyTensor{T,2,2} where T - end - @testset "3D" begin - op_x = volume_operator(g_3D,inner_stencil,closure_stencils,even,1) - op_y = volume_operator(g_3D,inner_stencil,closure_stencils,even,2) - op_z = volume_operator(g_3D,inner_stencil,closure_stencils,even,3) - Ix = IdentityTensor{Float64}((11,)) - Iy = IdentityTensor{Float64}((12,)) - Iz = IdentityTensor{Float64}((10,)) - @test op_x == VolumeOperator(inner_stencil,closure_stencils,(11,),even)⊗Iy⊗Iz - @test op_y == Ix⊗VolumeOperator(inner_stencil,closure_stencils,(12,),even)⊗Iz - @test op_z == Ix⊗Iy⊗VolumeOperator(inner_stencil,closure_stencils,(10,),even) - @test op_x isa LazyTensor{T,3,3} where T - @test op_y isa LazyTensor{T,3,3} where T - @test op_z isa LazyTensor{T,3,3} where T - end + op = VolumeOperator(inner_stencil, closure_stencils, 11, even) + @test op == VolumeOperator(g,inner_stencil,closure_stencils,even) + @test op isa LazyTensor{T,1,1} where T end @testset "Sizes" begin - @testset "1D" begin - op = volume_operator(g_1D,inner_stencil,closure_stencils,even,1) - @test range_size(op) == domain_size(op) == size(g_1D) - end - - @testset "2D" begin - op_x = volume_operator(g_2D,inner_stencil,closure_stencils,even,1) - op_y = volume_operator(g_2D,inner_stencil,closure_stencils,even,2) - @test range_size(op_y) == domain_size(op_y) == - range_size(op_x) == domain_size(op_x) == size(g_2D) - end - @testset "3D" begin - op_x = volume_operator(g_3D,inner_stencil,closure_stencils,even,1) - op_y = volume_operator(g_3D,inner_stencil,closure_stencils,even,2) - op_z = volume_operator(g_3D,inner_stencil,closure_stencils,even,3) - @test range_size(op_z) == domain_size(op_z) == - range_size(op_y) == domain_size(op_y) == - range_size(op_x) == domain_size(op_x) == size(g_3D) - end + op = VolumeOperator(g,inner_stencil,closure_stencils,even) + @test range_size(op) == domain_size(op) == size(g) end - op_x = volume_operator(g_2D,inner_stencil,closure_stencils,even,1) - op_y = volume_operator(g_2D,inner_stencil,closure_stencils,odd,2) - v = zeros(size(g_2D)) - Nx = size(g_2D)[1] - Ny = size(g_2D)[2] - for i = 1:Nx - v[i,:] .= i + + op_even = VolumeOperator(g, inner_stencil, closure_stencils, even) + op_odd = VolumeOperator(g, inner_stencil, closure_stencils, odd) + + N = size(g)[1] + v = rand(N) + + r_even = copy(v) + r_odd = copy(v) + + r_even[1] = (v[1] + v[2])/2 + r_odd[1] = (v[1] + v[2])/2 + + r_even[2] = 2v[1] + v[2] + r_odd[2] = 2v[1] + v[2] + + for i ∈ 3:N-2 + r_even[i] = (v[i-1] + 2v[i] + v[i+1])/4 + r_odd[i] = (v[i-1] + 2v[i] + v[i+1])/4 end - rx = copy(v) - rx[1,:] .= 1.5 - rx[Nx,:] .= (2*Nx-1)/2 - ry = copy(v) - ry[:,Ny-1:Ny] = -v[:,Ny-1:Ny] + + r_even[N-1] = v[N-1] + 2v[N] + r_odd[N-1] = -v[N-1] - 2v[N] + + r_even[N] = (v[N-1] + v[N])/2 + r_odd[N] = -(v[N-1] + v[N])/2 + @testset "Application" begin - @test op_x*v ≈ rx rtol = 1e-14 - @test op_y*v ≈ ry rtol = 1e-14 + @test op_even*v ≈ r_even + @test op_odd*v ≈ r_odd - @test (op_x*rand(ComplexF64,size(g_2D)))[2,2] isa ComplexF64 + @test (op_even*rand(ComplexF64,size(g)))[2] isa ComplexF64 end @testset "Regions" begin - @test (op_x*v)[Index(1,Lower),Index(3,Interior)] ≈ rx[1,3] rtol = 1e-14 - @test (op_x*v)[Index(2,Lower),Index(3,Interior)] ≈ rx[2,3] rtol = 1e-14 - @test (op_x*v)[Index(6,Interior),Index(3,Interior)] ≈ rx[6,3] rtol = 1e-14 - @test (op_x*v)[Index(10,Upper),Index(3,Interior)] ≈ rx[10,3] rtol = 1e-14 - @test (op_x*v)[Index(11,Upper),Index(3,Interior)] ≈ rx[11,3] rtol = 1e-14 - - @test_throws BoundsError (op_x*v)[Index(3,Lower),Index(3,Interior)] - @test_throws BoundsError (op_x*v)[Index(9,Upper),Index(3,Interior)] + @test (op_even*v)[Index(1,Lower)] ≈ r_even[1] + @test (op_even*v)[Index(2,Lower)] ≈ r_even[2] + @test (op_even*v)[Index(6,Interior)] ≈ r_even[6] + @test (op_even*v)[Index(10,Upper)] ≈ r_even[10] + @test (op_even*v)[Index(11,Upper)] ≈ r_even[11] - @test (op_y*v)[Index(3,Interior),Index(1,Lower)] ≈ ry[3,1] rtol = 1e-14 - @test (op_y*v)[Index(3,Interior),Index(2,Lower)] ≈ ry[3,2] rtol = 1e-14 - @test (op_y*v)[Index(3,Interior),Index(6,Interior)] ≈ ry[3,6] rtol = 1e-14 - @test (op_y*v)[Index(3,Interior),Index(11,Upper)] ≈ ry[3,11] rtol = 1e-14 - @test (op_y*v)[Index(3,Interior),Index(12,Upper)] ≈ ry[3,12] rtol = 1e-14 - - @test_throws BoundsError (op_y*v)[Index(3,Interior),Index(10,Upper)] - @test_throws BoundsError (op_y*v)[Index(3,Interior),Index(3,Lower)] + @test_throws BoundsError (op_even*v)[Index(3,Lower)] + @test_throws BoundsError (op_even*v)[Index(9,Upper)] end @testset "Inferred" begin - @test_skip @inferred apply(op_x, v,1,1) - @inferred apply(op_x, v, Index(1,Lower),Index(1,Lower)) - @inferred apply(op_x, v, Index(6,Interior),Index(1,Lower)) - @inferred apply(op_x, v, Index(11,Upper),Index(1,Lower)) - @test_skip @inferred apply(op_y, v,1,1) - @inferred apply(op_y, v, Index(1,Lower),Index(1,Lower)) - @inferred apply(op_y, v, Index(1,Lower),Index(6,Interior)) - @inferred apply(op_y, v, Index(1,Lower),Index(11,Upper)) + @inferred apply(op_even, v, 1) + @inferred apply(op_even, v, Index(1,Lower)) + @inferred apply(op_even, v, Index(6,Interior)) + @inferred apply(op_even, v, Index(11,Upper)) end end
--- a/test/StaticDicts/StaticDicts_test.jl Mon May 23 07:20:27 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -using Test -using Sbplib.StaticDicts - -@testset "StaticDicts" begin - -@testset "StaticDict" begin - @testset "constructor" begin - @test (StaticDict{Int,Int,N} where N) <: AbstractDict - - d = StaticDict(1=>2, 3=>4) - @test d isa StaticDict{Int,Int} - @test d[1] == 2 - @test d[3] == 4 - - @test StaticDict((1=>2, 3=>4)) == d - - @test StaticDict() isa StaticDict - @test StaticDict{Int,String}() isa StaticDict{Int,String,0} - - @test StaticDict(1=>3, 2=>4.) isa StaticDict{Int,Real} - @test StaticDict(1. =>3, 2=>4) isa StaticDict{Real,Int} - @test StaticDict(1. =>3, 2=>4.) isa StaticDict{Real,Real} - - @test_throws DomainError StaticDict(1=>3, 1=>3) - end - - @testset "length" begin - @test length(StaticDict()) == 0 - @test length(StaticDict(1=>1)) == 1 - @test length(StaticDict(1=>1, 2=>2)) == 2 - end - - @testset "equality" begin - @test StaticDict(1=>1) == StaticDict(1=>1) - @test StaticDict(2=>1) != StaticDict(1=>1) - @test StaticDict(1=>2) != StaticDict(1=>1) - - @test StaticDict(1=>1) === StaticDict(1=>1) #not true for a regular Dict - @test StaticDict(2=>1) !== StaticDict(1=>1) - @test StaticDict(1=>2) !== StaticDict(1=>1) - end - - @testset "get" begin - d = StaticDict(1=>2, 3=>4) - - @test get(d,1,6) == 2 - @test get(d,3,6) == 4 - @test get(d,5,6) == 6 - end - - @testset "iterate" begin - pairs = [1=>2, 3=>4, 5=>6] - - d = StaticDict(pairs...) - @test collect(d) == pairs - end - - @testset "merge" begin - @test merge( - StaticDict(1=>3, 2=> 4), - StaticDict(3=>5,4=>6)) == StaticDict( - 1=>3, 2=>4, 3=>5, 4=>6 - ) - @test_throws DomainError merge(StaticDict(1=>3),StaticDict(1=>3)) - end -end - -end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ext/sparse_array_kit_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,35 @@ +using Test + +using Diffinitive +using Diffinitive.Grids +using Diffinitive.SbpOperators + +using SparseArrayKit +using Tokens +using Tullio + + +@testset "SparseArray" begin + g = equidistant_grid((0,0),(1,2), 20,30) + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) + + + @testset let Δ = laplace(g, stencil_set), M = SparseArray(Δ) + @test ndims(M) == 4 + @test size(M) == (20,30,20,30) + + v = rand(size(g)...) + @tullio Mv[i,j] := M[i,j,k,l]*v[k,l] + + @test Mv ≈ Δ*v + end + + @testset let dₙ = normal_derivative(g, stencil_set,CartesianBoundary{1,LowerBoundary}()), M = SparseArray(dₙ) + @test ndims(M) == 3 + @test size(M) == (30,20,30) + + v = rand(size(g)...) + @tullio Mv[i] := M[i,j,k]*v[j,k] + @test Mv ≈ dₙ*v + end +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/ext/sparse_arrays_test.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,34 @@ +using Test + +using Diffinitive +using Diffinitive.Grids +using Diffinitive.SbpOperators + +using SparseArrays +using Tokens + + +@testset "SparseArray" begin + g = equidistant_grid((0,0),(1,2), 20,30) + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) + + + @testset let Δ = laplace(g, stencil_set), M = sparse(Δ) + @test ndims(M) == 2 + @test size(M) == (20*30,20*30) + + v = rand(size(g)...) + + Mv = M*reshape(v,:) + @test Mv ≈ reshape(Δ*v,:) + end + + @testset let dₙ = normal_derivative(g, stencil_set,CartesianBoundary{1,LowerBoundary}()), M = sparse(dₙ) + @test ndims(M) == 2 + @test size(M) == (30,20*30) + + v = rand(size(g)...) + Mv = M*reshape(v,:) + @test Mv ≈ reshape(dₙ*v,:) + end +end
--- a/test/runtests.jl Mon May 23 07:20:27 2022 +0200 +++ b/test/runtests.jl Tue Feb 10 22:41:19 2026 +0100 @@ -1,4 +1,7 @@ +using Diffinitive using Test +using JET +using Aqua using Glob """ @@ -18,7 +21,7 @@ run_testfiles(".", globs) end -function run_testfiles(path, globs) +function run_testfiles(path, globs) for name ∈ readdir(path) filepath = joinpath(path, name) @@ -29,24 +32,44 @@ end if endswith(name, "_test.jl") && any(occursin.(globs, filepath)) - printstyled("Running "; bold=true, color=:green) - print(filepath) - - t_start = time() - @testset "$name" begin - include(filepath) + log_and_time(filepath) do + @testset "$name" begin + include(filepath) + end end - t_end = time() - - Δt = t_end - t_start - printstyled(" ($(round(Δt, digits=2)) s)"; color=:light_black) - println() end end end -testsetname = isempty(ARGS) ? "Sbplib.jl" : "["*join(ARGS, ", ")*"]" +function log_and_time(f, msg) + printstyled("Running "; bold=true, color=:green) + print(msg) + + t_start = time() + f() + t_end = time() + Δt = t_end - t_start + printstyled(" ($(round(Δt, digits=2)) s)"; color=:light_black) + println() +end + +testsetname = isempty(ARGS) ? "Diffinitive.jl" : "["*join(ARGS, ", ")*"]" @testset "$testsetname" begin + if isempty(ARGS) + log_and_time("code quality tests using Aqua.jl") do + @testset "Code quality (Aqua.jl)" begin + Aqua.test_all(Diffinitive) + end + end + + log_and_time("code linting using JET.jl") do + @testset "Code linting (JET.jl)" begin + JET.test_package(Diffinitive; target_modules = (Diffinitive,)) + end + end + end + run_testfiles(ARGS) + println() end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/update_manifest.jl Tue Feb 10 22:41:19 2026 +0100 @@ -0,0 +1,22 @@ +using Pkg + +function update_and_log(root, d) + printstyled("Updating '$d'"; color=:cyan, bold=true) + println() + dp = joinpath(root, d) + + update_directory(dp) + println() +end + +function update_directory(d) + Pkg.activate(d) + Pkg.update() +end + +root = @__DIR__ +update_and_log(root, ".") +update_and_log(root, "benchmark") +update_and_log(root, "docs") +update_and_log(root, "test") +
