Mercurial > repos > public > sbplib_julia
changeset 1858:4a9be96f2569 feature/documenter_logo
Merge default
author | Jonatan Werpers <jonatan@werpers.com> |
---|---|
date | Sun, 12 Jan 2025 21:18:44 +0100 |
parents | ffde7dad9da5 (current diff) 0d8d56eca0c8 (diff) |
children | 8124b87594a8 |
files | |
diffstat | 111 files changed, 8198 insertions(+), 2989 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Fri Jan 21 15:23:08 2022 +0100 +++ b/.hgignore Sun Jan 12 21:18:44 2025 +0100 @@ -1,3 +1,5 @@ syntax: glob docs/build/ docs/build-local/ +benchmark/results +benchmark/tune.json
--- a/Makefile Fri Jan 21 15:23:08 2022 +0100 +++ b/Makefile Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/Manifest.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,59 +1,92 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.0" +julia_version = "1.11.2" manifest_format = "2.0" - -[[deps.Adapt]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "84918055d15b3114ede17ac6a7182f68870c16f7" -uuid = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" -version = "3.3.1" +project_hash = "a8bfbeb10ca8c44ef39fdba75f02b23fc92b2c6a" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" [[deps.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - -[[deps.OffsetArrays]] -deps = ["Adapt"] -git-tree-sha1 = "043017e0bdeff61cfbb7afeb558ab29536bbb5ed" -uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" -version = "1.10.8" +version = "1.11.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.27+1" + +[[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.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "47091a0340a675c738b1304b58161f3b0839d454" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.10" + + [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 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" [[deps.TOML]] deps = ["Dates"] uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" - -[[deps.TiledIteration]] -deps = ["OffsetArrays"] -git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297" -uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" -version = "0.3.1" +version = "1.0.3" [[deps.Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" [[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.11.0+0"
--- a/Notes.md Fri Jan 21 15:23:08 2022 +0100 +++ b/Notes.md Sun Jan 12 21:18:44 2025 +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 @@ -71,59 +107,6 @@ dictionary-structure containing stencils, tuples, scalars and other types ready for input to the methods creating the operators. -## 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? @@ -132,27 +115,56 @@ * When doing specialised computations for different parts of the range/domain? * More? - 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 TensorMapping is applied to. + 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 TensorMapping har the same range and domain? - - [ ] Rename all the Tensor stuff to just LazyOperator, LazyApplication and so on? - [ ] 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. - - [ ] Specificera operatorer i TOML eller något liknande? - H.. H_gamma etc.) - - [ ] 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 TensorMapping 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? + - [ ] Can we have a trait to tell if a LazyTensor is transposable? ## Regions and tensormappings -- [ ] Use a trait to indicate if a TensorMapping uses indices with regions. +- [ ] Use a trait to indicate if a LazyTensor uses indices with regions. The default should be that they do NOT. - [ ] What to name this trait? Can we call it IndexStyle but not export it to avoid conflicts with Base.IndexStyle? - - [ ] Figure out repeated application of regioned TensorMappings. Maybe an instance of a tensor mapping needs to know the exact size of the range and domain for this to work? + - [ ] Figure out repeated application of regioned LazyTensors. Maybe an instance of a tensor mapping needs to know the exact size of the range and domain for this to work? + +### Ideas for information sharing functions +```julia +using StaticArrays + +function regions(op::SecondDerivativeVariable) + t = ntuple(i->(Interior(),),range_dim(op)) + return Base.setindex(t, (Lower(), Interior(), Upper()), derivative_direction(op)) +end + +function regionsizes(op::SecondDerivativeVariable) + sz = tuple.(range_size(op)) + + cl = closuresize(op) + return Base.setindex(sz, (cl, n-2cl, cl), derivative_direction(op)) +end + + +g = EquidistantGrid((11,9), (0.,0.), (10.,8.)) # h = 1 +c = evalOn(g, (x,y)->x+y) + +D₂ᶜ = SecondDerivativeVariable(g, c, interior_stencil, closure_stencils,1) +@test regions(D₂ᶜ) == ( + (Lower(), Interior(), Upper()), + (Interior(),), +) +@test regionsizes(D₂ᶜ) == ((1,9,1),(9,)) + + +D₂ᶜ = SecondDerivativeVariable(g, c, interior_stencil, closure_stencils,2) +@test regions(D₂ᶜ) == ( + (Interior(),), + (Lower(), Interior(), Upper()), +) +@test regionsizes(D₂ᶜ) == ((11,),(1,7,1)) +``` + ## Boundschecking and dimension checking Does it make sense to have boundschecking only in getindex methods? @@ -160,80 +172,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? @@ -241,93 +281,43 @@ Ä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. Vi kan också ha tensor-operatorer som agerar på ett vektorfält eller tensorfält och ger ett skalärt fält. -TBD: Just nu gör `apply_transpose` antagandet att domän-typen är samma som range-typen. Det behöver vi på något sätt bryta. Ett alternativ är låta en TensorMapping ha `T_domain` och `T_range` istället för bara `T`. Känns dock lite grötigt. Ett annat alternativ skulle vara någon typ av trait för transpose? Den skulle kunna innehålla typen som transponatet agerar på? Vet inte om det fungerar dock. +TBD: Just nu gör `apply_transpose` antagandet att domän-typen är samma som range-typen. Det behöver vi på något sätt bryta. Ett alternativ är låta en LazyTensor ha `T_domain` och `T_range` istället för bara `T`. Känns dock lite grötigt. Ett annat alternativ skulle vara någon typ av trait för transpose? Den skulle kunna innehålla typen som transponatet agerar på? Vet inte om det fungerar dock. -TBD: Vad är målet med `T`-parametern för en TensorMapping? Om vi vill kunna applicera en difference operator på vad som helst kan man inte anta att en `TensorMapping{T}` bara agerar på instanser av `T`. +TBD: Vad är målet med `T`-parametern för en LazyTensor? Om vi vill kunna applicera en difference operator på vad som helst kan man inte anta att en `LazyTensor{T}` bara agerar på instanser av `T`. Man kan implementera `∇` som en tensormapping som agerar på T och returnerar `StaticVector{N,T} where N`. (Man skulle eventuellt också kunna låta den agera på `StaticMatrix{N,T,D} where N` och returnera `StaticMatrix{M,T,D+1}`. Frågan är om man vinner något på det...) -Skulle kunna ha en funktion `range_type(::TensorMapping, ::Type{domain_type})` +Skulle kunna ha en funktion `range_type(::LazyTensor, ::Type{domain_type})` + +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. -Kanske kan man implementera `⋅(tm::TensorMapping{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. +### 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]]]. -### Ratade alternativ: +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 TensorMapping -* 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? @@ -348,4 +338,65 @@ ## Name of the `VolumeOperator` type for constant stencils -It seems that the name is too general. The name of the method `volume_operator` makes sense. It should return different types of `TensorMapping` specialized for the grid. A suggetion for a better name is `ConstantStencilVolumeOperator` +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/Project.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,11 +1,30 @@ -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.3" [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] +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" +DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] +DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] [compat] julia = "1.5" +StaticArrays = "1.0" +TOML = "1.0" +Makie = "0.21" +SparseArrayKit = "0.3" +Tokens = "0.1.1" +SparseArrays = "1.10" +LinearAlgebra = "1.5"
--- a/README.md Fri Jan 21 15:23:08 2022 +0100 +++ b/README.md Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/TODO.md Sun Jan 12 21:18:44 2025 +0100 @@ -1,34 +1,44 @@ # TODO -## Skämskudde - - [ ] Ändra namn på variabler och funktioner så att det följer style-guide - - [ ] Skriv tester +## Organization + - [ ] Split up Notes.md in several files ## Coding - - [ ] Add new Laplace operator to DiffOps, probably named WaveEqOp(?!!?) - [ ] Create a struct that bundles the necessary Tensor operators for solving the wave equation. - - [ ] Add a quick and simple way of running all tests for all subpackages. - - [ ] Replace getindex hack for flattening tuples with flatten_tuple. (eg. `getindex.(range_size.(L.D2),1)`) - - [ ] Use `@inferred` in a lot of tests. - [ ] Make sure we are setting tolerances in tests in a consistent way - - [ ] Add check for correct domain sizes to lazy tensor operations using SizeMismatch - [ ] Write down some coding guideline or checklist for code conventions. For example i,j,... for indices and I for multi-index - - [ ] Add boundschecking in TensorMappingApplication - - [ ] Start renaming things in LazyTensors - [ ] Clean up RegionIndices 1. [ ] Write tests for how things should work 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 -## Repo - - [ ] Rename repo to Sbplib.jl -# Wrap up tasks + - [ ] 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? - [ ] Kolla att vi har @inbounds och @propagate_inbounds på rätt ställen - [ ] 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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Sun Jan 12 21:18:44 2025 +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/Manifest.toml Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,329 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.11.2" +manifest_format = "2.0" +project_hash = "ecfc3e12aca5be17a874aba6134ff821abf61540" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[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 = "e38fbc49a620f5d0b660d7f543db1009fe0f8336" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.6.0" + +[[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.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.Diffinitive]] +deps = ["LinearAlgebra", "StaticArrays", "TOML"] +path = ".." +uuid = "5a373a26-915f-4769-bcab-bf03835de17b" +version = "0.1.3" + + [deps.Diffinitive.extensions] + DiffinitiveMakieExt = "Makie" + DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] + DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] + + [deps.Diffinitive.weakdeps] + Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + Tokens = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.LeftChildRightSiblingTrees]] +deps = ["AbstractTrees"] +git-tree-sha1 = "fb6803dafae4a5d62ea5cab204b1e657d9737e7f" +uuid = "1d6d02ad-be62-4b6b-8a6d-2f90e265016e" +version = "0.2.0" + +[[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.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" + +[[deps.Mustache]] +deps = ["Printf", "Tables"] +git-tree-sha1 = "3b2db451a872b20519ebb0cec759d3d81a1c6bcb" +uuid = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" +version = "1.0.20" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.27+1" + +[[deps.OrderedCollections]] +git-tree-sha1 = "12f1439c4f986bb868acda6ea33ebc78e19b95ad" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.7.0" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[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" + + [deps.Pkg.extensions] + REPLExt = "REPL" + + [deps.Pkg.weakdeps] + REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.PkgBenchmark]] +deps = ["BenchmarkTools", "Dates", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Pkg", "Printf", "TerminalLoggers", "UUIDs"] +git-tree-sha1 = "e4a10b7cdb7ec836850e43a4cee196f4e7b02756" +uuid = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d" +version = "0.2.12" + +[[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.Profile]] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.ProgressLogging]] +deps = ["Logging", "SHA", "UUIDs"] +git-tree-sha1 = "80d919dee55b9c50e8d9e2da5eeafff3fe58b539" +uuid = "33c8b6b6-d38a-422a-b730-caa89a2f386c" +version = "0.1.4" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "47091a0340a675c738b1304b58161f3b0839d454" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.10" + + [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 = "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" + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + + [deps.Statistics.weakdeps] + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[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.TerminalLoggers]] +deps = ["LeftChildRightSiblingTrees", "Logging", "Markdown", "Printf", "ProgressLogging", "UUIDs"] +git-tree-sha1 = "f133fab380933d042f6796eda4e130272ba520ca" +uuid = "5d786b92-1e48-4d6f-9151-6b4477ca9bed" +version = "0.1.7" + +[[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.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.11.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.59.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/benchmark/Project.toml Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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") + +""" + mainmain(;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_benchmark(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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/docs/Manifest.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,145 +1,347 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.1" +julia_version = "1.11.2" manifest_format = "2.0" +project_hash = "c04450bb5c379e77d137cc05c4c0ab58eb1bfae9" [[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.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.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.6" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" [[deps.Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.Diffinitive]] +deps = ["LinearAlgebra", "StaticArrays", "TOML"] +path = ".." +uuid = "5a373a26-915f-4769-bcab-bf03835de17b" +version = "0.1.3" + + [deps.Diffinitive.extensions] + DiffinitiveMakieExt = "Makie" + DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] + DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] + + [deps.Diffinitive.weakdeps] + Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" + SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + Tokens = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" +version = "0.9.3" [[deps.Documenter]] -deps = ["ANSIColoredPrinters", "Base64", "Dates", "DocStringExtensions", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "f425293f7e0acaf9144de6d731772de156676233" +deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] +git-tree-sha1 = "d0ea2c044963ed6f37703cead7e29f70cba13d7e" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.27.10" +version = "1.8.0" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e51db81749b0777b2147fbe7b783ee79045b8e99" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.4+3" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.Git]] +deps = ["Git_jll"] +git-tree-sha1 = "04eff47b1354d702c3a85e8ab23d539bb7d5957e" +uuid = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" +version = "1.3.1" + +[[deps.Git_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "399f4a308c804b446ae4c91eeafadb2fe2c54ff9" +uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" +version = "2.47.1+0" [[deps.IOCapture]] deps = ["Logging", "Random"] -git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" +git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" -version = "0.2.2" +version = "0.2.5" [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.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 = "8076680b162ada2a031f707ac7b4953e30667a37" +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -version = "0.21.2" +version = "0.21.4" + +[[deps.LazilyInitializedFields]] +git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" +uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" +version = "1.3.0" + +[[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", "NetworkOptions", "Printf", "SHA"] +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.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "61dfdba58e585066d8bce214c5a51eaa0539f269" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+1" [[deps.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" [[deps.Markdown]] deps = ["Base64"] 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.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" [[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" +version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.27+1" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7493f61f55a6cce7325f197443aa80d32554ba10" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.15+3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" [[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "d7fa6237da8004be601e19bd6666083056649918" +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.1.3" +version = "2.8.1" + +[[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.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.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +deps = ["InteractiveUtils", "Markdown", "Sockets", "StyledStrings", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" [[deps.Random]] -deps = ["SHA", "Serialization"] +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.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" - -[[deps.Sbplib]] -deps = ["TOML", "TiledIteration"] -path = ".." -uuid = "5a373a26-915f-4769-bcab-bf03835de17b" -version = "0.1.0" +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.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "47091a0340a675c738b1304b58161f3b0839d454" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.10" + + [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 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" [[deps.TOML]] deps = ["Dates"] uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" [[deps.Test]] deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" -[[deps.TiledIteration]] -deps = ["OffsetArrays"] -git-tree-sha1 = "5683455224ba92ef59db72d10690690f4a8dc297" -uuid = "06e1c1a7-607b-532d-9fad-de7d9aa2abac" -version = "0.3.1" +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[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.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" [[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.11.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.59.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2"
--- a/docs/Project.toml Fri Jan 21 15:23:08 2022 +0100 +++ b/docs/Project.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,3 +1,3 @@ [deps] +Diffinitive = "5a373a26-915f-4769-bcab-bf03835de17b" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -Sbplib = "5a373a26-915f-4769-bcab-bf03835de17b"
--- a/docs/make.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/docs/make.jl Sun Jan 12 21:18:44 2025 +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,18 @@ pages = [ "Home" => "index.md", "operator_file_format.md", + "grids_and_grid_functions.md", + "matrix_and_tensor_representations.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/grids_and_grid_functions.md Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/docs/src/index.md Sun Jan 12 21:18:44 2025 +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/matrix_and_tensor_representations.md Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/docs/src/operator_file_format.md Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Sun Jan 12 21:18:44 2025 +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/DiffinitiveSparseArrayKitExt.jl Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 - -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 Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -""" - EquidistantGrid(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) - EquidistantGrid{T}() - -`EquidistantGrid` is a grid with equidistant grid spacing per coordinat direction. - -`EquidistantGrid(size, limit_lower, limit_upper)` construct the grid with the -domain defined by the two points P1, and P2 given by `limit_lower` and -`limit_upper`. The length of the domain sides are given by the components of -(P2-P1). E.g for a 2D grid with P1=(-1,0) and P2=(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 `size`. - -`EquidistantGrid{T}()` constructs a 0-dimensional grid. - -""" -struct EquidistantGrid{Dim,T<:Real} <: AbstractGrid - size::NTuple{Dim, Int} - limit_lower::NTuple{Dim, T} - limit_upper::NTuple{Dim, T} - - # General constructor - function EquidistantGrid(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) where Dim where 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 - - # Specialized constructor for 0-dimensional grid - EquidistantGrid{T}() where T = new{0,T}((),(),()) -end -export EquidistantGrid - - -""" - 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 the grid points of the grid. -""" -spacing(grid::EquidistantGrid) = (grid.limit_upper.-grid.limit_lower)./(grid.size.-1) -export spacing - -""" - inverse_spacing(grid::EquidistantGrid) - -The reciprocal of the spacing between the grid points of the grid. -""" -inverse_spacing(grid::EquidistantGrid) = 1 ./ spacing(grid) -export inverse_spacing - -""" - 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 -export restrict - -""" - 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)))...)...,) -export boundary_identifiers - - -""" - 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 -export boundary_grid -boundary_grid(::EquidistantGrid{1,T},::CartesianBoundary{1}) where T = EquidistantGrid{T}()
--- a/src/Grids/Grids.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/Grids/Grids.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,19 +1,52 @@ module Grids -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using StaticArrays +using LinearAlgebra -export BoundaryIdentifier, CartesianBoundary +# 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 -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() +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 -include("AbstractGrid.jl") -include("EquidistantGrid.jl") +# MappedGrid +export MappedGrid +export jacobian +export logical_grid +export mapped_grid +export metric_tensor -# TODO: Rename AbstractGrid to Grid and move definition here. +include("grid.jl") +include("tensor_grid.jl") +include("equidistant_grid.jl") +include("zero_dim_grid.jl") +include("mapped_grid.jl") end # module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/equidistant_grid.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,166 @@ +""" + 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::T, limit_upper::T, size::Int) + +Constructs a 1D equidistant grid. +""" +function equidistant_grid(limit_lower::Number, limit_upper::Number, size::Int) + if any(size .<= 0) + throw(DomainError("size must be postive")) + end + + if any(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 + +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 Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,175 @@ +""" + 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 + +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...] +IndexStyle(::Type{<:ArrayComponentView{<:Any,<:Any,AT}}) where AT = IndexStyle(AT) + +# 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/mapped_grid.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,218 @@ +""" + 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 + +# TODO: Make sure all methods of `mapped_grid` are implemented correctly and tested. +""" + mapped_grid(x, J, size::Vararg{Int}) + +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) + lg = equidistant_grid(ntuple(i->0., D), ntuple(i->1., D), size...) # TODO: Clean this up with ParamaterSpace once feature/grids/manifolds is merged + return mapped_grid(x, J, lg) +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, 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, parameterspace, size::Vararg{Int}) + lg = equidistant_grid(parameterspace, 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) + b_indices = boundary_indices(g, boundary) + σ = _boundary_sign(component_type(g), boundary) + + # TODO: Refactor this when `boundary_indices(g, ...)` has been made iterable. + return map(jacobian(g)[b_indices...]) do ∂x∂ξ + ∂ξ∂x = inv(∂x∂ξ) + k = grid_id(boundary) + σ*∂ξ∂x[k,:]/norm(∂ξ∂x[k,:]) + 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/tensor_grid.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,145 @@ +""" + 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, id::TensorGridBoundary) + per_grid_ind = map(g.grids) do g + ntuple(i->:, ndims(g)) + end + + local_b_ind = boundary_indices(g.grids[grid_id(id)], boundary_id(id)) + b_ind = Base.setindex(per_grid_ind, local_b_ind, grid_id(id)) + + return LazyTensors.concatenate_tuples(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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/LazyTensors/LazyTensors.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,7 +1,48 @@ module LazyTensors -using Sbplib.RegionIndices -include("tensor_mapping.jl") + +export LazyTensor +export apply +export apply_transpose +export range_dim, domain_dim +export range_size, domain_size + +export TensorApplication +export TensorTranspose +export TensorComposition +export IdentityTensor +export ScalingTensor +export DiagonalTensor +export DenseTensor +export InflatedTensor +export LazyOuterProduct +export ⊗ +export DomainSizeMismatch +export RangeSizeMismatch + +export LazyArray +export LazyFunctionArray +export +̃, -̃, *̃, /̃ + +include("lazy_tensor.jl") +include("tensor_types.jl") include("lazy_array.jl") include("lazy_tensor_operations.jl") +include("tuple_manipulation.jl") + +# Applying lazy tensors to vectors +Base.:*(a::LazyTensor, v::AbstractArray) = TensorApplication(a,v) +Base.:*(a::LazyTensor, b::LazyTensor) = throw(MethodError(Base.:*,(a,b))) +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) + +# 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) end # module
--- a/src/LazyTensors/lazy_array.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/LazyTensors/lazy_array.jl Sun Jan 12 21:18:44 2025 +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 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. @@ -61,25 +59,17 @@ end LazyElementwiseOperation{T,D,Op}(a::AbstractArray{T,D},b::T) where {T,D,Op} = LazyElementwiseOperation{T,D,Op}(a, LazyConstantArray(b, size(a))) LazyElementwiseOperation{T,D,Op}(a::T,b::AbstractArray{T,D}) where {T,D,Op} = LazyElementwiseOperation{T,D,Op}(LazyConstantArray(a, size(b)), b) -# TODO: Move Op to be the first parameter? Compare to Binary operations Base.size(v::LazyElementwiseOperation) = size(v.a) -evaluate(leo::LazyElementwiseOperation{T,D,:+}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] + leo.b[I...] -evaluate(leo::LazyElementwiseOperation{T,D,:-}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] - leo.b[I...] -evaluate(leo::LazyElementwiseOperation{T,D,:*}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] * leo.b[I...] -evaluate(leo::LazyElementwiseOperation{T,D,:/}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] / leo.b[I...] +Base.@propagate_inbounds evaluate(leo::LazyElementwiseOperation{T,D,:+}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] + leo.b[I...] +Base.@propagate_inbounds evaluate(leo::LazyElementwiseOperation{T,D,:-}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] - leo.b[I...] +Base.@propagate_inbounds evaluate(leo::LazyElementwiseOperation{T,D,:*}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] * leo.b[I...] +Base.@propagate_inbounds evaluate(leo::LazyElementwiseOperation{T,D,:/}, I::Vararg{Int,D}) where {T,D} = leo.a[I...] / leo.b[I...] -# TODO: Make sure boundschecking is done properly and that the lenght of the vectors are equal -# NOTE: Boundschecking in getindex functions now assumes that the size of the -# vectors in the LazyElementwiseOperation are the same size. If we remove the -# size assertion in the constructor we might have to handle -# boundschecking differently. -Base.@propagate_inbounds @inline function Base.getindex(leo::LazyElementwiseOperation{T,D}, I::Vararg{Int,D}) where {T,D} - @boundscheck if !checkbounds(Bool, leo.a, I...) - throw(BoundsError([leo], I...)) - end - return evaluate(leo, I...) +function Base.getindex(leo::LazyElementwiseOperation{T,D}, I::Vararg{Int,D}) where {T,D} + @boundscheck checkbounds(leo.a, I...) + return @inbounds evaluate(leo, I...) end # Define lazy operations for AbstractArrays. Operations constructs a LazyElementwiseOperation which @@ -101,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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LazyTensors/lazy_tensor.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,73 @@ +""" + LazyTensor{T,R,D} + +Describes a mapping of a `D` dimension tensor to an `R` dimension tensor. +The action of the mapping is implemented through the method +```julia + apply(t::LazyTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg) where {R,D,T} +``` + +The size of the range and domain that the operator works with should be returned by +the functions +```julia + range_size(::LazyTensor) + domain_size(::LazyTensor) +``` +to allow querying for one or the other. + +Optionally the action of the transpose may be defined through +```julia + apply_transpose(t::LazyTensor{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T} +``` +""" +abstract type LazyTensor{T,R,D} end + +""" + apply(t::LazyTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg) where {R,D,T} + +Return the result of the mapping for a given index. +""" +function apply end + +""" + apply_transpose(t::LazyTensor{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg) where {R,D,T} + +Return the result of the transposed mapping for a given index. +""" +function apply_transpose end + +""" + range_dim(::LazyTensor) +Return the dimension of the range space of a given mapping +""" +range_dim(::LazyTensor{T,R,D}) where {T,R,D} = R + +""" + domain_dim(::LazyTensor) +Return the dimension of the domain space of a given mapping +""" +domain_dim(::LazyTensor{T,R,D}) where {T,R,D} = D + + +""" + range_size(M::LazyTensor) + +Return the range size for the mapping. +""" +function range_size end + +""" + domain_size(M::LazyTensor) + +Return the domain size for the mapping. +""" +function domain_size end + + +""" + eltype(::LazyTensor{T}) + +The type of elements the LazyTensor acts on. +""" +Base.eltype(::LazyTensor{T}) where T = T +
--- a/src/LazyTensors/lazy_tensor_operations.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/LazyTensors/lazy_tensor_operations.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,202 +1,139 @@ """ - LazyTensorMappingApplication{T,R,D} <: LazyArray{T,R} + TensorApplication{T,R,D} <: LazyArray{T,R} -Struct for lazy application of a TensorMapping. Created using `*`. +Struct for lazy application of a LazyTensor. Created using `*`. -Allows the result of a `TensorMapping` applied to a vector to be treated as an `AbstractArray`. -With a mapping `m` and a vector `v` the LazyTensorMappingApplication object can be created by `m*v`. -The actual result will be calcualted when indexing into `m*v`. +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 calculated when indexing into `m*v`. """ -struct LazyTensorMappingApplication{T,R,D, TM<:TensorMapping{T,R,D}, AA<:AbstractArray{T,D}} <: LazyArray{T,R} +struct TensorApplication{T,R,D, TM<:LazyTensor{<:Any,R,D}, AA<:AbstractArray{<:Any,D}} <: LazyArray{T,R} t::TM o::AA -end -# TODO: Do boundschecking on creation! -export LazyTensorMappingApplication - -Base.getindex(ta::LazyTensorMappingApplication{T,R}, I::Vararg{Any,R}) where {T,R} = apply(ta.t, ta.o, I...) -Base.size(ta::LazyTensorMappingApplication) = range_size(ta.t) -# TODO: What else is needed to implement the AbstractArray interface? -Base.:*(a::TensorMapping, v::AbstractArray) = LazyTensorMappingApplication(a,v) -Base.:*(a::TensorMapping, b::TensorMapping) = throw(MethodError(Base.:*,(a,b))) -Base.:*(a::TensorMapping, args::Union{TensorMapping, AbstractArray}...) = foldr(*,(a,args...)) + function TensorApplication(t::LazyTensor{<:Any,R,D}, o::AbstractArray{<:Any,D}) where {R,D} + @boundscheck check_domain_size(t, size(o)) + I = ntuple(i->1, range_dim(t)) + T = typeof(apply(t,o,I...)) + return new{T,R,D,typeof(t), typeof(o)}(t,o) + end +end -# # We need the associativity to be a→b→c = a→(b→c), which is the case for '→' -# # Should we overload some other infix binary opesrator? -# →(tm::TensorMapping{T,R,D}, o::AbstractArray{T,D}) where {T,R,D} = LazyTensorMappingApplication(tm,o) -# TODO: We need to be really careful about good error messages. -# For example what happens if you try to multiply LazyTensorMappingApplication with a TensorMapping(wrong order)? +function Base.getindex(ta::TensorApplication{T,R}, I::Vararg{Any,R}) where {T,R} + @boundscheck checkbounds(ta, Int.(I)...) + return @inbounds apply(ta.t, ta.o, I...) +end +Base.@propagate_inbounds Base.getindex(ta::TensorApplication{T,1} where T, I::CartesianIndex{1}) = ta[Tuple(I)...] # Would otherwise be caught in the previous method. +Base.size(ta::TensorApplication) = range_size(ta.t) + """ - LazyTensorMappingTranspose{T,R,D} <: TensorMapping{T,D,R} + TensorTranspose{T,R,D} <: LazyTensor{T,D,R} -Struct for lazy transpose of a TensorMapping. +Struct for lazy transpose of a LazyTensor. If a mapping implements the the `apply_transpose` method this allows working with -the transpose of mapping `m` by using `m'`. `m'` will work as a regular TensorMapping lazily calling +the transpose of mapping `m` by using `m'`. `m'` will work as a regular LazyTensor lazily calling the appropriate methods of `m`. """ -struct LazyTensorMappingTranspose{T,R,D, TM<:TensorMapping{T,R,D}} <: TensorMapping{T,D,R} +struct TensorTranspose{T,R,D, TM<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R} tm::TM end -export LazyTensorMappingTranspose # # TBD: Should this be implemented on a type by type basis or through a trait to provide earlier errors? -# Jonatan 2020-09-25: Is the problem that you can take the transpose of any TensorMapping even if it doesn't implement `apply_transpose`? -Base.adjoint(tm::TensorMapping) = LazyTensorMappingTranspose(tm) -Base.adjoint(tmt::LazyTensorMappingTranspose) = tmt.tm +# Jonatan 2020-09-25: Is the problem that you can take the transpose of any LazyTensor even if it doesn't implement `apply_transpose`? +Base.adjoint(tm::LazyTensor) = TensorTranspose(tm) +Base.adjoint(tmt::TensorTranspose) = tmt.tm -apply(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,R}, I::Vararg{Any,D}) where {T,R,D} = apply_transpose(tmt.tm, v, I...) -apply_transpose(tmt::LazyTensorMappingTranspose{T,R,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmt.tm, v, I...) +apply(tmt::TensorTranspose{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} = apply_transpose(tmt.tm, v, I...) +apply_transpose(tmt::TensorTranspose{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmt.tm, v, I...) -range_size(tmt::LazyTensorMappingTranspose) = domain_size(tmt.tm) -domain_size(tmt::LazyTensorMappingTranspose) = range_size(tmt.tm) +range_size(tmt::TensorTranspose) = domain_size(tmt.tm) +domain_size(tmt::TensorTranspose) = range_size(tmt.tm) -struct LazyTensorMappingBinaryOperation{Op,T,R,D,T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}} <: TensorMapping{T,D,R} +struct ElementwiseTensorOperation{Op,T,R,D,T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} <: LazyTensor{T,R,D} tm1::T1 tm2::T2 - @inline function LazyTensorMappingBinaryOperation{Op,T,R,D}(tm1::T1,tm2::T2) where {Op,T,R,D, T1<:TensorMapping{T,R,D},T2<:TensorMapping{T,R,D}} + 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) end end -# TODO: Boundschecking in constructor. -apply(tmBinOp::LazyTensorMappingBinaryOperation{:+,T,R,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) + apply(tmBinOp.tm2, v, I...) -apply(tmBinOp::LazyTensorMappingBinaryOperation{:-,T,R,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) - apply(tmBinOp.tm2, v, I...) +ElementwiseTensorOperation{Op}(s,t) where Op = ElementwiseTensorOperation{Op,eltype(s), range_dim(s), domain_dim(s)}(s,t) -range_size(tmBinOp::LazyTensorMappingBinaryOperation) = range_size(tmBinOp.tm1) -domain_size(tmBinOp::LazyTensorMappingBinaryOperation) = domain_size(tmBinOp.tm1) +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...) -Base.:+(tm1::TensorMapping{T,R,D}, tm2::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:+,T,R,D}(tm1,tm2) -Base.:-(tm1::TensorMapping{T,R,D}, tm2::TensorMapping{T,R,D}) where {T,R,D} = LazyTensorMappingBinaryOperation{:-,T,R,D}(tm1,tm2) +range_size(tmBinOp::ElementwiseTensorOperation) = range_size(tmBinOp.tm1) +domain_size(tmBinOp::ElementwiseTensorOperation) = domain_size(tmBinOp.tm1) + """ - TensorMappingComposition{T,R,K,D} + TensorComposition{T,R,K,D} -Lazily compose two `TensorMapping`s, so that they can be handled as a single `TensorMapping`. +Lazily compose two `LazyTensor`s, so that they can be handled as a single `LazyTensor`. """ -struct TensorMappingComposition{T,R,K,D, TM1<:TensorMapping{T,R,K}, TM2<:TensorMapping{T,K,D}} <: TensorMapping{T,R,D} +struct TensorComposition{T,R,K,D, TM1<:LazyTensor{T,R,K}, TM2<:LazyTensor{T,K,D}} <: LazyTensor{T,R,D} t1::TM1 t2::TM2 - @inline function TensorMappingComposition(t1::TensorMapping{T,R,K}, t2::TensorMapping{T,K,D}) where {T,R,K,D} + function TensorComposition(t1::LazyTensor{T,R,K}, t2::LazyTensor{T,K,D}) where {T,R,K,D} @boundscheck check_domain_size(t1, range_size(t2)) return new{T,R,K,D, typeof(t1), typeof(t2)}(t1,t2) end end -export TensorMappingComposition -range_size(tm::TensorMappingComposition) = range_size(tm.t1) -domain_size(tm::TensorMappingComposition) = domain_size(tm.t2) +range_size(tm::TensorComposition) = range_size(tm.t1) +domain_size(tm::TensorComposition) = domain_size(tm.t2) -function apply(c::TensorMappingComposition{T,R,K,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,K,D} +function apply(c::TensorComposition{T,R,K,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,K,D} apply(c.t1, c.t2*v, I...) end -function apply_transpose(c::TensorMappingComposition{T,R,K,D}, v::AbstractArray{T,R}, I::Vararg{Any,D}) where {T,R,K,D} +function apply_transpose(c::TensorComposition{T,R,K,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,K,D} apply_transpose(c.t2, c.t1'*v, I...) end -Base.@propagate_inbounds Base.:∘(s::TensorMapping, t::TensorMapping) = TensorMappingComposition(s,t) - """ - LazyLinearMap{T,R,D,...}(A, range_indicies, domain_indicies) - -TensorMapping defined by the AbstractArray A. `range_indicies` and `domain_indicies` define which indicies of A should -be considerd the range and domain of the TensorMapping. 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 LazyLinearMap performs the -standard matrix-vector product on vectors of size n. -""" -struct LazyLinearMap{T,R,D, RD, AA<:AbstractArray{T,RD}} <: TensorMapping{T,R,D} - A::AA - range_indicies::NTuple{R,Int} - domain_indicies::NTuple{D,Int} - - function LazyLinearMap(A::AA, range_indicies::NTuple{R,Int}, domain_indicies::NTuple{D,Int}) where {T,R,D, RD, AA<:AbstractArray{T,RD}} - if !issorted(range_indicies) || !issorted(domain_indicies) - throw(DomainError("range_indicies and domain_indicies must be sorted in ascending order")) - end - - return new{T,R,D,RD,AA}(A,range_indicies,domain_indicies) - end -end -export LazyLinearMap - -range_size(llm::LazyLinearMap) = size(llm.A)[[llm.range_indicies...]] -domain_size(llm::LazyLinearMap) = size(llm.A)[[llm.domain_indicies...]] + TensorComposition(tm, tmi::IdentityTensor) + TensorComposition(tmi::IdentityTensor, tm) -function apply(llm::LazyLinearMap{T,R,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,D} - view_index = ntuple(i->:,ndims(llm.A)) - for i ∈ 1:R - view_index = Base.setindex(view_index, Int(I[i]), llm.range_indicies[i]) - end - A_view = @view llm.A[view_index...] - return sum(A_view.*v) -end - -function apply_transpose(llm::LazyLinearMap{T,R,D}, v::AbstractArray{T,R}, I::Vararg{Any,D}) where {T,R,D} - apply(LazyLinearMap(llm.A, llm.domain_indicies, llm.range_indicies), v, I...) -end - - -""" - IdentityMapping{T,D} <: TensorMapping{T,D,D} - -The lazy identity TensorMapping for a given size. Usefull for building up higher dimensional tensor mappings from lower -dimensional ones through outer products. Also used in the Implementation for InflatedTensorMapping. +Composes a `LazyTensor` `tm` with an `IdentityTensor` `tmi`, by returning `tm` """ -struct IdentityMapping{T,D} <: TensorMapping{T,D,D} - size::NTuple{D,Int} -end -export IdentityMapping - -IdentityMapping{T}(size::NTuple{D,Int}) where {T,D} = IdentityMapping{T,D}(size) -IdentityMapping{T}(size::Vararg{Int,D}) where {T,D} = IdentityMapping{T,D}(size) -IdentityMapping(size::Vararg{Int,D}) where D = IdentityMapping{Float64,D}(size) - -range_size(tmi::IdentityMapping) = tmi.size -domain_size(tmi::IdentityMapping) = tmi.size - -apply(tmi::IdentityMapping{T,D}, v::AbstractArray{T,D}, I::Vararg{Any,D}) where {T,D} = v[I...] -apply_transpose(tmi::IdentityMapping{T,D}, v::AbstractArray{T,D}, I::Vararg{Any,D}) where {T,D} = v[I...] - -""" - Base.:∘(tm, tmi) - Base.:∘(tmi, tm) - -Composes a `Tensormapping` `tm` with an `IdentityMapping` `tmi`, by returning `tm` -""" -@inline function Base.:∘(tm::TensorMapping{T,R,D}, tmi::IdentityMapping{T,D}) where {T,R,D} +function TensorComposition(tm::LazyTensor{T,R,D}, tmi::IdentityTensor{T,D}) where {T,R,D} @boundscheck check_domain_size(tm, range_size(tmi)) return tm end -@inline function Base.:∘(tmi::IdentityMapping{T,R}, tm::TensorMapping{T,R,D}) where {T,R,D} +function TensorComposition(tmi::IdentityTensor{T,R}, tm::LazyTensor{T,R,D}) where {T,R,D} @boundscheck check_domain_size(tmi, range_size(tm)) return tm end -# Specialization for the case where tm is an IdentityMapping. Required to resolve ambiguity. -@inline function Base.:∘(tm::IdentityMapping{T,D}, tmi::IdentityMapping{T,D}) where {T,D} +# Specialization for the case where tm is an IdentityTensor. Required to resolve ambiguity. +function TensorComposition(tm::IdentityTensor{T,D}, tmi::IdentityTensor{T,D}) where {T,D} @boundscheck check_domain_size(tm, range_size(tmi)) return tmi end +Base.:*(a::T, tm::LazyTensor{T}) where T = TensorComposition(ScalingTensor{T,range_dim(tm)}(a,range_size(tm)), tm) +Base.:*(tm::LazyTensor{T}, a::T) where T = a*tm +Base.:-(tm::LazyTensor) = (-one(eltype(tm)))*tm """ - InflatedTensorMapping{T,R,D} <: TensorMapping{T,R,D} + InflatedTensor{T,R,D} <: LazyTensor{T,R,D} -An inflated `TensorMapping` with dimensions added before and afer its actual dimensions. +An inflated `LazyTensor` with dimensions added before and after its actual dimensions. """ -struct InflatedTensorMapping{T,R,D,D_before,R_middle,D_middle,D_after, TM<:TensorMapping{T,R_middle,D_middle}} <: TensorMapping{T,R,D} - before::IdentityMapping{T,D_before} +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} tm::TM - after::IdentityMapping{T,D_after} + after::IdentityTensor{T,D_after} - function InflatedTensorMapping(before, tm::TensorMapping{T}, after) where T + function InflatedTensor(before, tm::LazyTensor{T}, after) where T R_before = range_dim(before) R_middle = range_dim(tm) R_after = range_dim(after) @@ -209,158 +146,81 @@ return new{T,R,D,D_before,R_middle,D_middle,D_after, typeof(tm)}(before, tm, after) end end -export InflatedTensorMapping + """ - InflatedTensorMapping(before, tm, after) - InflatedTensorMapping(before,tm) - InflatedTensorMapping(tm,after) + InflatedTensor(before, tm, after) + InflatedTensor(before,tm) + InflatedTensor(tm,after) -The outer product of `before`, `tm` and `after`, where `before` and `after` are `IdentityMapping`s. +The outer product of `before`, `tm` and `after`, where `before` and `after` are `IdentityTensor`s. -If one of `before` or `after` is left out, a 0-dimensional `IdentityMapping` is used as the default value. +If one of `before` or `after` is left out, a 0-dimensional `IdentityTensor` is used as the default value. -If `tm` already is an `InflatedTensorMapping`, `before` and `after` will be extended instead of -creating a nested `InflatedTensorMapping`. +If `tm` already is an `InflatedTensor`, `before` and `after` will be extended instead of +creating a nested `InflatedTensor`. """ -InflatedTensorMapping(::IdentityMapping, ::TensorMapping, ::IdentityMapping) +InflatedTensor(::IdentityTensor, ::LazyTensor, ::IdentityTensor) -function InflatedTensorMapping(before, itm::InflatedTensorMapping, after) - return InflatedTensorMapping( - IdentityMapping(before.size..., itm.before.size...), +function InflatedTensor(before, itm::InflatedTensor, after) + return InflatedTensor( + IdentityTensor(before.size..., itm.before.size...), itm.tm, - IdentityMapping(itm.after.size..., after.size...), + IdentityTensor(itm.after.size..., after.size...), ) end -InflatedTensorMapping(before::IdentityMapping, tm::TensorMapping{T}) where T = InflatedTensorMapping(before,tm,IdentityMapping{T}()) -InflatedTensorMapping(tm::TensorMapping{T}, after::IdentityMapping) where T = InflatedTensorMapping(IdentityMapping{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 -InflatedTensorMapping(I1::IdentityMapping{T}, I2::IdentityMapping{T}) where T = InflatedTensorMapping(I1,I2,IdentityMapping{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 InflatedTensorMapping(I(3),B,I(2)) -> I(3)⊗B⊗I(2) +# 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::InflatedTensorMapping) - return flatten_tuple( +function range_size(itm::InflatedTensor) + return concatenate_tuples( range_size(itm.before), range_size(itm.tm), range_size(itm.after), ) end -function domain_size(itm::InflatedTensorMapping) - return flatten_tuple( +function domain_size(itm::InflatedTensor) + return concatenate_tuples( domain_size(itm.before), domain_size(itm.tm), domain_size(itm.after), ) end -function apply(itm::InflatedTensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg{Any,R}) where {T,R,D} +function apply(itm::InflatedTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} dim_before = range_dim(itm.before) dim_domain = domain_dim(itm.tm) 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...) end -function apply_transpose(itm::InflatedTensorMapping{T,R,D}, v::AbstractArray{T,R}, I::Vararg{Any,D}) where {T,R,D} +function apply_transpose(itm::InflatedTensor{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} dim_before = range_dim(itm.before) dim_domain = domain_dim(itm.tm) 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...) end -""" - split_index(::Val{dim_before}, ::Val{dim_view}, ::Val{dim_index}, ::Val{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) -``` - -`dim_view` controls how many colons are in the view, and `dim_index` controls -how many elements are extracted from the middle. -`dim_before` and `dim_after` decides the length of the index parts before and after the colons in the view index. - -Arguments should satisfy `length(I) == dim_before+B_domain+dim_after`. - -The returned values satisfy - * `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)) - - 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 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,) -``` -""" -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)) -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 -end - - -""" - flatten_tuple(t) - -Takes a nested tuple and flattens the whole structure -""" -flatten_tuple(t::NTuple{N, Number} where N) = t -flatten_tuple(t::Tuple) = ((flatten_tuple.(t)...)...,) # simplify? -flatten_tuple(ts::Vararg) = flatten_tuple(ts) - @doc raw""" LazyOuterProduct(tms...) -Creates a `TensorMappingComposition` 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 @@ -395,38 +255,73 @@ ``` """ function LazyOuterProduct end -export LazyOuterProduct -function LazyOuterProduct(tm1::TensorMapping{T}, tm2::TensorMapping{T}) where T - itm1 = InflatedTensorMapping(tm1, IdentityMapping{T}(range_size(tm2))) - itm2 = InflatedTensorMapping(IdentityMapping{T}(domain_size(tm1)),tm2) +function LazyOuterProduct(tm1::LazyTensor{T}, tm2::LazyTensor{T}) where T + itm1 = InflatedTensor(tm1, IdentityTensor{T}(range_size(tm2))) + itm2 = InflatedTensor(IdentityTensor{T}(domain_size(tm1)),tm2) return itm1∘itm2 end -LazyOuterProduct(t1::IdentityMapping{T}, t2::IdentityMapping{T}) where T = IdentityMapping{T}(t1.size...,t2.size...) -LazyOuterProduct(t1::TensorMapping, t2::IdentityMapping) = InflatedTensorMapping(t1, t2) -LazyOuterProduct(t1::IdentityMapping, t2::TensorMapping) = InflatedTensorMapping(t1, t2) +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{TensorMapping}) = foldl(LazyOuterProduct, tms) +LazyOuterProduct(tms::Vararg{LazyTensor}) = foldl(LazyOuterProduct, tms) -⊗(a::TensorMapping, b::TensorMapping) = LazyOuterProduct(a,b) -export ⊗ -function check_domain_size(tm::TensorMapping, sz) +""" + 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(SizeMismatch(tm,sz)) + throw(DomainSizeMismatch(tm,sz)) end end -struct SizeMismatch <: Exception - tm::TensorMapping +function check_range_size(tm::LazyTensor, sz) + if range_size(tm) != sz + throw(RangeSizeMismatch(tm,sz)) + end +end + +struct DomainSizeMismatch <: Exception + tm::LazyTensor sz end -export SizeMismatch + +function Base.showerror(io::IO, err::DomainSizeMismatch) + print(io, "DomainSizeMismatch: ") + print(io, "domain size $(domain_size(err.tm)) of LazyTensor not matching size $(err.sz)") +end + -function Base.showerror(io::IO, err::SizeMismatch) - print(io, "SizeMismatch: ") - print(io, "domain size $(domain_size(err.tm)) of TensorMapping not matching size $(err.sz)") +struct RangeSizeMismatch <: Exception + tm::LazyTensor + sz end + +function Base.showerror(io::IO, err::RangeSizeMismatch) + print(io, "RangeSizeMismatch: ") + print(io, "range size $(range_size(err.tm)) of LazyTensor not matching size $(err.sz)") +end
--- a/src/LazyTensors/tensor_mapping.jl Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -""" - TensorMapping{T,R,D} - -Describes a mapping of a `D` dimension tensor to an `R` dimension tensor. -The action of the mapping is implemented through the method -```julia - apply(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T} -``` - -The size of the range and domain that the operator works with should be returned by -the functions -```julia - range_size(::TensorMapping) - domain_size(::TensorMapping) -``` -to allow querying for one or the other. - -Optionally the action of the transpose may be defined through -```julia - apply_transpose(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T} -``` -""" -abstract type TensorMapping{T,R,D} end -export TensorMapping - -""" - apply(t::TensorMapping{T,R,D}, v::AbstractArray{T,D}, I::Vararg) where {R,D,T} - -Return the result of the mapping for a given index. -""" -function apply end -export apply - -""" - apply_transpose(t::TensorMapping{T,R,D}, v::AbstractArray{T,R}, I::Vararg) where {R,D,T} - -Return the result of the transposed mapping for a given index. -""" -function apply_transpose end -export apply_transpose - -""" - range_dim(::TensorMapping) -Return the dimension of the range space of a given mapping -""" -range_dim(::TensorMapping{T,R,D}) where {T,R,D} = R - -""" - domain_dim(::TensorMapping) -Return the dimension of the domain space of a given mapping -""" -domain_dim(::TensorMapping{T,R,D}) where {T,R,D} = D - -export range_dim, domain_dim - -""" - range_size(M::TensorMapping) - -Return the range size for the mapping. -""" -function range_size end - -""" - domain_size(M::TensorMapping) - -Return the domain size for the mapping. -""" -function domain_size end - -export range_size, domain_size - -""" - eltype(::TensorMapping{T}) - -The type of elements the TensorMapping acts on. -""" -Base.eltype(::TensorMapping{T}) where T = T - -# TODO: Think about boundschecking!
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LazyTensors/tensor_types.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,94 @@ +""" + IdentityTensor{T,D} <: LazyTensor{T,D,D} + +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} + size::NTuple{D,Int} +end + +IdentityTensor{T}(size::NTuple{D,Int}) where {T,D} = IdentityTensor{T,D}(size) +IdentityTensor{T}(size::Vararg{Int,D}) where {T,D} = IdentityTensor{T,D}(size) +IdentityTensor(size::Vararg{Int,D}) where D = IdentityTensor{Float64,D}(size) + +range_size(tmi::IdentityTensor) = tmi.size +domain_size(tmi::IdentityTensor) = tmi.size + +apply(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] +apply_transpose(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] + + +""" + ScalingTensor{T,D} <: LazyTensor{T,D,D} + +A lazy tensor that scales its input with `λ`. +""" +struct ScalingTensor{T,D} <: LazyTensor{T,D,D} + λ::T + size::NTuple{D,Int} +end + +LazyTensors.apply(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] +LazyTensors.apply_transpose(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] + +LazyTensors.range_size(m::ScalingTensor) = m.size +LazyTensors.domain_size(m::ScalingTensor) = m.size + + +""" + DiagonalTensor{T,D,...} <: LazyTensor{T,D,D} + DiagonalTensor(a::AbstractArray) + +A lazy tensor with diagonal `a`. +""" +struct DiagonalTensor{T,D,AT<:AbstractArray{T,D}} <: LazyTensor{T,D,D} + diagonal::AT +end + +range_size(tm::DiagonalTensor) = size(tm.diagonal) +domain_size(tm::DiagonalTensor) = size(tm.diagonal) + + +LazyTensors.apply(tm::DiagonalTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.diagonal[I...]*v[I...] +LazyTensors.apply_transpose(tm::DiagonalTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.diagonal[I...]*v[I...] + + +""" + DenseTensor{T,R,D,...}(A, range_indicies, domain_indicies) + +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. +""" +struct DenseTensor{T,R,D, RD, AA<:AbstractArray{T,RD}} <: LazyTensor{T,R,D} + A::AA + range_indicies::NTuple{R,Int} + domain_indicies::NTuple{D,Int} + + function DenseTensor(A::AA, range_indicies::NTuple{R,Int}, domain_indicies::NTuple{D,Int}) where {T,R,D, RD, AA<:AbstractArray{T,RD}} + if !issorted(range_indicies) || !issorted(domain_indicies) + throw(DomainError("range_indicies and domain_indicies must be sorted in ascending order")) + end + + return new{T,R,D,RD,AA}(A,range_indicies,domain_indicies) + end +end + +range_size(llm::DenseTensor) = size(llm.A)[[llm.range_indicies...]] +domain_size(llm::DenseTensor) = size(llm.A)[[llm.domain_indicies...]] + +function apply(llm::DenseTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} + view_index = ntuple(i->:,ndims(llm.A)) + for i ∈ 1:R + view_index = Base.setindex(view_index, Int(I[i]), llm.range_indicies[i]) + end + A_view = @view llm.A[view_index...] + return sum(A_view.*v) +end + +function apply_transpose(llm::DenseTensor{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} + apply(DenseTensor(llm.A, llm.domain_indicies, llm.range_indicies), v, I...) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LazyTensors/tuple_manipulation.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,96 @@ +""" + 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. +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 +how many elements are extracted from the middle. +`dim_before` and `dim_after` decides the length of the index parts before and after the colons in the view index. + +Arguments should satisfy `length(I) == dim_before+B_domain+dim_after`. + +The returned values satisfy + * `length(view_index) == dim_before + dim_view + dim_after` + * `length(I_middle) == 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 + + +""" + 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)`. + +E.g +```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, 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 + +function sizes_to_ranges(szs) + cum_szs = cumsum((0, szs...)) + return ntuple(i->cum_szs[i]+1:cum_szs[i+1], length(szs)) +end + + +""" + concatenate_tuples(t...) + +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`. +""" +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/RegionIndices/RegionIndices.jl Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/SbpOperators.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,19 +1,67 @@ module SbpOperators -using Sbplib.RegionIndices -using Sbplib.LazyTensors -using Sbplib.Grids +# 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_restriction +export inner_product +export inverse_inner_product +export Laplace +export laplace +export normal_derivative +export first_derivative +export second_derivative +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("readoperator.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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/boundaryops/boundary_operator.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,89 +1,61 @@ """ - 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`. + BoundaryOperator{T,B,N} <: LazyTensor{T,0,1} -When `Dim=1`, the corresponding `BoundaryOperator` tensor mapping is returned. -When `Dim>1`, the `BoundaryOperator` `op` is inflated by the outer product -of `IdentityMappings` in orthogonal coordinate directions, e.g for `Dim=3`, -the boundary restriction operator in the y-direction direction is `Ix⊗op⊗Iz`. -""" -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) +Implements the boundary operator `op` for 1D as a `LazyTensor` - # Create 1D IdentityMappings for each coordinate direction - one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid))) - Is = IdentityMapping{eltype(grid)}.(size.(one_d_grids)) - - # Formulate the correct outer product sequence of the identity mappings and - # the boundary operator - parts = Base.setindex(Is, op, d) - return foldl(⊗, parts) -end - +`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. """ - BoundaryOperator{T,R,N} <: TensorMapping{T,0,1} - -Implements the boundary operator `op` for 1D as a `TensorMapping` - -`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. -""" -struct BoundaryOperator{T,R<:Region,N} <: TensorMapping{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{T,Lower}, v::AbstractVector{T}) where T +function LazyTensors.apply(op::BoundaryOperator{<:Any,LowerBoundary}, v::AbstractVector) apply_stencil(op.stencil,v,1) end -function LazyTensors.apply(op::BoundaryOperator{T,Upper}, v::AbstractVector{T}) where T +function LazyTensors.apply(op::BoundaryOperator{<:Any,UpperBoundary}, v::AbstractVector) apply_stencil_backwards(op.stencil,v,op.size) end -function LazyTensors.apply_transpose(op::BoundaryOperator{T,Lower}, v::AbstractArray{T,0}, i::Index{Lower}) where T +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{T,Upper}, v::AbstractArray{T,0}, i::Index{Upper}) where T +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 # Catch all combinations of Lower, Upper and Interior not caught by the two previous methods. -function LazyTensors.apply_transpose(op::BoundaryOperator{T}, v::AbstractArray{T,0}, i::Index) where T - return zero(T) +function LazyTensors.apply_transpose(op::BoundaryOperator, v::AbstractArray{<:Any,0}, i::Index) + return zero(eltype(v)) end -function LazyTensors.apply_transpose(op::BoundaryOperator{T}, v::AbstractArray{T,0}, i) where T +function LazyTensors.apply_transpose(op::BoundaryOperator, v::AbstractArray{<:Any,0}, i) r = getregion(i, closure_size(op), op.size) apply_transpose(op, v, Index(i,r)) end
--- a/src/SbpOperators/boundaryops/boundary_restriction.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/boundaryops/boundary_restriction.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,18 +1,27 @@ """ - boundary_restriction(grid::EquidistantGrid, closure_stencil::Stencil, boundary::CartesianBoundary) - boundary_restriction(grid::EquidistantGrid{1}, closure_stencil::Stencil, region::Region) + 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` -Creates the boundary restriction operator `e` as a `TensorMapping` +`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`. -`e` is the restriction of a grid function to the boundary specified by `boundary` or `region` using some `closure_stencil`. -`e'` is the prolongation of a grid function on the 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`. Also see the documentation of `SbpOperators.boundary_operator(...)` for more details. +See also: [`BoundaryOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -function boundary_restriction(grid::EquidistantGrid, closure_stencil, boundary::CartesianBoundary) - converted_stencil = convert(Stencil{eltype(grid)}, closure_stencil) - return SbpOperators.boundary_operator(grid, converted_stencil, boundary) +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::EquidistantGrid{1}, closure_stencil, region::Region) = boundary_restriction(grid, closure_stencil, CartesianBoundary{1,typeof(region)}()) -export boundary_restriction +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/boundaryops/normal_derivative.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,18 +1,30 @@ """ - normal_derivative(grid::EquidistantGrid, closure_stencil::Stencil, boundary::CartesianBoundary) - normal_derivative(grid::EquidistantGrid{1}, closure_stencil::Stencil, region::Region) + 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` -Creates the normal derivative boundary operator `d` as a `TensorMapping` +`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`. -`d` is the normal derivative of a grid function at the boundary specified by `boundary` or `region` using some `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`. Also see the documentation of `SbpOperators.boundary_operator(...)` for more details. +See also: [`BoundaryOperator`](@ref), [`LazyTensors.inflate`](@ref). """ -function normal_derivative(grid::EquidistantGrid, closure_stencil, boundary::CartesianBoundary) - direction = dim(boundary) - h_inv = inverse_spacing(grid)[direction] - return SbpOperators.boundary_operator(grid, scale(closure_stencil,h_inv), boundary) +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::EquidistantGrid{1}, closure_stencil, region::Region) = normal_derivative(grid, closure_stencil, CartesianBoundary{1,typeof(region)}()) -export normal_derivative + +function normal_derivative(g::EquidistantGrid, stencil_set::StencilSet, boundary) + closure_stencil = parse_stencil(stencil_set["d1"]["closure"]) + h_inv = inverse_spacing(g) + + scaled_stencil = scale(closure_stencil,h_inv) + return BoundaryOperator(g, scaled_stencil, boundary) +end
--- a/src/SbpOperators/operators/standard_diagonal.toml Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/operators/standard_diagonal.toml Sun Jan 12 21:18:44 2025 +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/readoperator.jl Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,160 +0,0 @@ -using TOML - -export read_stencil_set -export get_stencil_set - -export parse_stencil -export parse_scalar -export parse_tuple - -export sbp_operators_path - - -""" - read_stencil_set(filename; filters) - -Picks out a stencil set from a TOML file based on some key-value -filters. If more than one set matches the filters an error is raised. The -returned stencil set contains parsed TOML intended for functions like -`parse_scalar` and `parse_stencil`. - -The stencil set is not parsed beyond the inital TOML parse. To get usable -stencils use the `parse_stencil` functions on the fields of the stencil set. - -The reason for this is that since stencil sets are intended to be very -general, and currently do not include any way to specify how to parse a given -section, the exact parsing is left to the user. - -For more information see [Operator file format](@ref) in the documentation. - -See also [`sbp_operators_path`](@ref), [`get_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref),. -""" -read_stencil_set(filename; filters...) = get_stencil_set(TOML.parsefile(filename); filters...) - -""" - get_stencil_set(parsed_toml; filters...) - -Picks out a stencil set from an already parsed TOML based on some key-value -filters. - -See also [`read_stencil_set`](@ref). -""" -function get_stencil_set(parsed_toml; filters...) - matches = findall(parsed_toml["stencil_set"]) do set - for (key, val) ∈ filters - if set[string(key)] != val - return false - end - end - - return true - end - - if length(matches) != 1 - throw(ArgumentError("filters must pick out a single stencil set")) - end - - i = matches[1] - return parsed_toml["stencil_set"][i] -end - -""" - parse_stencil(parsed_toml) - -Accepts parsed TOML and reads it as a stencil. - -See also [`read_stencil_set`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref). -""" -function parse_stencil(parsed_toml) - check_stencil_toml(parsed_toml) - - if parsed_toml isa Array - weights = parse_rational.(parsed_toml) - return CenteredStencil(weights...) - end - - weights = parse_rational.(parsed_toml["s"]) - return Stencil(weights..., center = parsed_toml["c"]) -end - -""" - parse_stencil(T, parsed_toml) - -Parses the input as a stencil with element type `T`. -""" -parse_stencil(T, parsed_toml) = Stencil{T}(parse_stencil(parsed_toml)) - -function check_stencil_toml(parsed_toml) - if !(parsed_toml isa Dict || parsed_toml isa Vector{String}) - throw(ArgumentError("the TOML for a stencil must be a vector of strings or a table.")) - end - - if parsed_toml isa Vector{String} - return - end - - if !(haskey(parsed_toml, "s") && haskey(parsed_toml, "c")) - throw(ArgumentError("the table form of a stencil must have fields `s` and `c`.")) - end - - if !(parsed_toml["s"] isa Vector{String}) - throw(ArgumentError("a stencil must be specified as a vector of strings.")) - end - - if !(parsed_toml["c"] isa Int) - throw(ArgumentError("the center of a stencil must be specified as an integer.")) - end -end - -""" - parse_scalar(parsed_toml) - -Parse a scalar, represented as a string or a number in the TOML, and return it as a `Rational` - -See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref) [`parse_tuple`](@ref). -""" -function parse_scalar(parsed_toml) - try - return parse_rational(parsed_toml) - catch e - throw(ArgumentError("must be a number or a string representing a number.")) - end -end - -""" - parse_tuple(parsed_toml) - -Parse an array as a tuple of scalars. - -See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref). -""" -function parse_tuple(parsed_toml) - if !(parsed_toml isa Array) - throw(ArgumentError("argument must be an array")) - end - return Tuple(parse_scalar.(parsed_toml)) -end - - -""" - parse_rational(parsed_toml) - -Parse a string or a number as a rational. -""" -function parse_rational(parsed_toml) - if parsed_toml isa String - expr = Meta.parse(replace(parsed_toml, "/"=>"//")) - return eval(:(Rational($expr))) - else - return Rational(parsed_toml) - end -end - -""" - sbp_operators_path() - -Calculate the path for the operators folder with included stencil sets. - -See also [`read_stencil_set`](@ref) -""" -sbp_operators_path() = (@__DIR__) * "/operators/"
--- a/src/SbpOperators/stencil.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/stencil.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,41 +1,44 @@ -export CenteredStencil - struct Stencil{T,N} - range::Tuple{Int,Int} + range::UnitRange{Int64} weights::NTuple{N,T} - function Stencil(range::Tuple{Int,Int},weights::NTuple{N,T}) where {T, N} - @assert range[2]-range[1]+1 == 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. """ -function Stencil(weights::Vararg{T}; center::Int) where T # Type parameter T makes sure the weights are valid for the Stencil constuctors and throws an earlier, more readable, error +function Stencil(weights...; center::Int) + weights = promote(weights...) N = length(weights) - range = (1, N) .- center + range = (1:N) .- center return Stencil(range, weights) end -function Stencil{T}(s::Stencil) where T - return Stencil(s.range, T.(s.weights)) -end +Stencil{T,N}(s::Stencil{S,N}) where {T,S,N} = Stencil(s.range, T.(s.weights)) +Stencil{T}(s::Stencil) where T = Stencil{T,length(s)}(s) -Base.convert(::Type{Stencil{T}}, stencil) where T = Stencil{T}(stencil) +Base.convert(::Type{Stencil{T1,N}}, s::Stencil{T2,N}) where {T1,T2,N} = Stencil{T1,N}(s) +Base.convert(::Type{Stencil{T1}}, s::Stencil{T2,N}) where {T1,T2,N} = Stencil{T1,N}(s) -function CenteredStencil(weights::Vararg) +Base.promote_rule(::Type{Stencil{T1,N}}, ::Type{Stencil{T2,N}}) where {T1,T2,N} = Stencil{promote_type(T1,T2),N} + +function CenteredStencil(weights...) if iseven(length(weights)) throw(ArgumentError("a centered stencil must have an odd number of weights.")) end r = length(weights) ÷ 2 - return Stencil((-r, r), weights) + return Stencil(-r:r, weights) end @@ -48,7 +51,8 @@ return Stencil(s.range, a.*s.weights) end -Base.eltype(::Stencil{T}) where T = T +Base.eltype(::Stencil{T,N}) where {T,N} = T +Base.length(::Stencil{T,N}) where {T,N} = N function flip(s::Stencil) range = (-s.range[2], -s.range[1]) @@ -57,24 +61,128 @@ # Provides index into the Stencil based on offset for the root element @inline function Base.getindex(s::Stencil, i::Int) - @boundscheck if i < s.range[1] || s.range[2] < i + @boundscheck if i ∉ s.range return zero(eltype(s)) end return s.weights[1 + i - s.range[1]] end -Base.@propagate_inbounds @inline function apply_stencil(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N} - w = s.weights[1]*v[i + s.range[1]] - @simd for k ∈ 2:N - w += s.weights[k]*v[i + s.range[1] + k-1] +Base.@propagate_inbounds @inline function apply_stencil(s::Stencil, v::AbstractVector, i::Int) + return sum(enumerate(s.weights)) do (k,w) #TBD: Which optimizations are needed here? + w*v[i + @inbounds s.range[k]] + end +end + +Base.@propagate_inbounds @inline function apply_stencil_backwards(s::Stencil, v::AbstractVector, i::Int) + 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} where N}; center) + inner_stencils = map(w -> Stencil(w...; center), weights) + return NestedStencil(Stencil(inner_stencils... ; center)) end -Base.@propagate_inbounds @inline function apply_stencil_backwards(s::Stencil{T,N}, v::AbstractVector, i::Int) where {T,N} - w = s.weights[N]*v[i - s.range[2]] - @simd for k ∈ N-1:-1:1 - w += s.weights[k]*v[i - s.range[1] - k + 1] - end - return w +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)) +end + +function NestedStencil{T}(ns::NestedStencil{S,N,M}) where {T,S,N,M} + NestedStencil{T,N,M}(ns) +end + +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::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} +end + +Base.eltype(::NestedStencil{T}) where T = T + +function scale(ns::NestedStencil, a) + range = ns.s.range + weights = ns.s.weights + + return NestedStencil(Stencil(range, scale.(weights,a))) end + +function flip(ns::NestedStencil) + s_flip = flip(ns.s) + return NestedStencil(Stencil(s_flip.range, flip.(s_flip.weights))) +end + +Base.getindex(ns::NestedStencil, i::Int) = ns.s[i] + +"Apply inner stencils to `c` and get a concrete stencil" +Base.@propagate_inbounds function apply_inner_stencils(ns::NestedStencil, c::AbstractVector, i::Int) + weights = apply_stencil.(ns.s.weights, Ref(c), i) + return Stencil(ns.s.range, weights) +end + +"Apply the whole nested stencil" +Base.@propagate_inbounds function apply_stencil(ns::NestedStencil, c::AbstractVector, v::AbstractVector, i::Int) + s = apply_inner_stencils(ns,c,i) + return apply_stencil(s, v, i) +end + +"Apply inner stencils backwards to `c` and get a concrete stencil" +Base.@propagate_inbounds @inline function apply_inner_stencils_backwards(ns::NestedStencil, c::AbstractVector, i::Int) + weights = apply_stencil_backwards.(ns.s.weights, Ref(c), i) + return Stencil(ns.s.range, weights) +end + +"Apply the whole nested stencil backwards" +Base.@propagate_inbounds @inline function apply_stencil_backwards(ns::NestedStencil, c::AbstractVector, v::AbstractVector, i::Int) + s = apply_inner_stencils_backwards(ns,c,i) + return apply_stencil_backwards(s, v, i) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/stencil_set.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,191 @@ +using TOML + + +""" + StencilSet + +A `StencilSet` contains a set of associated stencils. The stencils +are are stored in a table, and can be accessed by indexing into the `StencilSet`. +""" +struct StencilSet + table +end +Base.getindex(set::StencilSet,I...) = set.table[I...] + + +""" + 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 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 +general, and currently do not include any way to specify how to parse a given +section, the exact parsing is left to the user. + +For more information see [Operator file format](@ref) in the documentation. + +See also [`StencilSet`](@ref), [`sbp_operators_path`](@ref), [`get_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref). +""" +read_stencil_set(filename; filters...) = StencilSet(get_stencil_set(TOML.parsefile(filename); filters...)) + + +""" + get_stencil_set(parsed_toml; filters...) + +Picks out a stencil set from an already parsed TOML based on some key-value +filters. + +See also [`read_stencil_set`](@ref). +""" +function get_stencil_set(parsed_toml; filters...) + matches = findall(parsed_toml["stencil_set"]) do set + for (key, val) ∈ filters + if set[string(key)] != val + return false + end + end + + return true + end + + if length(matches) != 1 + throw(ArgumentError("filters must pick out a single stencil set")) + end + + i = matches[1] + return parsed_toml["stencil_set"][i] +end + +""" + parse_stencil(parsed_toml) + +Accepts parsed TOML and reads it as a stencil. + +See also [`read_stencil_set`](@ref), [`parse_scalar`](@ref), [`parse_tuple`](@ref). +""" +function parse_stencil(parsed_toml) + check_stencil_toml(parsed_toml) + + if parsed_toml isa Array + weights = parse_rational.(parsed_toml) + return CenteredStencil(weights...) + end + + weights = parse_rational.(parsed_toml["s"]) + return Stencil(weights..., center = parsed_toml["c"]) +end + +""" + parse_stencil(T, parsed_toml) + +Parses the input as a stencil with element type `T`. +""" +parse_stencil(T, parsed_toml) = Stencil{T}(parse_stencil(parsed_toml)) + +function check_stencil_toml(parsed_toml) + if !(parsed_toml isa Dict || parsed_toml isa Vector{String}) + throw(ArgumentError("the TOML for a stencil must be a vector of strings or a table.")) + end + + if parsed_toml isa Vector{String} + return + end + + if !(haskey(parsed_toml, "s") && haskey(parsed_toml, "c")) + throw(ArgumentError("the table form of a stencil must have fields `s` and `c`.")) + end + + if !(parsed_toml["s"] isa Vector{String}) + throw(ArgumentError("a stencil must be specified as a vector of strings.")) + end + + if !(parsed_toml["c"] isa Int) + throw(ArgumentError("the center of a stencil must be specified as an integer.")) + end +end + + +""" + parse_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) + +Parse a scalar, represented as a string or a number in the TOML, and return it as a `Rational` + +See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref) [`parse_tuple`](@ref). +""" +function parse_scalar(parsed_toml) + try + return parse_rational(parsed_toml) + catch e + throw(ArgumentError("must be a number or a string representing a number.")) + end +end + +""" + parse_tuple(parsed_toml) + +Parse an array as a tuple of scalars. + +See also [`read_stencil_set`](@ref), [`parse_stencil`](@ref), [`parse_scalar`](@ref). +""" +function parse_tuple(parsed_toml) + if !(parsed_toml isa Array) + throw(ArgumentError("argument must be an array")) + end + return Tuple(parse_scalar.(parsed_toml)) +end + + +""" + parse_rational(parsed_toml) + +Parse a string or a number as a rational. +""" +function parse_rational(parsed_toml) + if parsed_toml isa String + expr = Meta.parse(replace(parsed_toml, "/"=>"//")) + return eval(:(Rational($expr))) + else + return Rational(parsed_toml) + end +end + +""" + sbp_operators_path() + +Calculate the path for the operators folder with included stencil sets. + +See also [`StencilSet`](@ref), [`read_stencil_set`](@ref). +""" +sbp_operators_path() = (@__DIR__) * "/operators/"
--- a/src/SbpOperators/volumeops/constant_interior_scaling_operator.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/constant_interior_scaling_operator.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,10 +1,11 @@ """ - ConstantInteriorScalingOperator{T,N} <: TensorMapping{T,1,1} + 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} <: TensorMapping{T,1,1} +struct ConstantInteriorScalingOperator{T,N} <: LazyTensor{T,1,1} interior_weight::T closure_weights::NTuple{N,T} size::Int @@ -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 @@ -28,19 +29,19 @@ LazyTensors.domain_size(op::ConstantInteriorScalingOperator) = (op.size,) # TBD: @inbounds in apply methods? -function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Lower}) where T +function LazyTensors.apply(op::ConstantInteriorScalingOperator, v::AbstractVector, i::Index{Lower}) return op.closure_weights[Int(i)]*v[Int(i)] end -function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Interior}) where T +function LazyTensors.apply(op::ConstantInteriorScalingOperator, v::AbstractVector, i::Index{Interior}) return op.interior_weight*v[Int(i)] end -function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i::Index{Upper}) where T +function LazyTensors.apply(op::ConstantInteriorScalingOperator, v::AbstractVector, i::Index{Upper}) return op.closure_weights[op.size[1]-Int(i)+1]*v[Int(i)] end -function LazyTensors.apply(op::ConstantInteriorScalingOperator{T}, v::AbstractVector{T}, i) where T +function LazyTensors.apply(op::ConstantInteriorScalingOperator, v::AbstractVector, i) r = getregion(i, closure_size(op), op.size[1]) return LazyTensors.apply(op, v, Index(i, r)) end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/derivatives/dissipation.jl Sun Jan 12 21:18:44 2025 +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/derivatives/first_derivative.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,42 @@ +""" + first_derivative(g, ..., [direction]) + +The first derivative operator `D1` as a `LazyTensor` on the given grid. + +`D1` approximates the first-derivative d/dξ on `g` along the coordinate +dimension specified by `direction`. +""" +function first_derivative end + +""" + first_derivative(g::TensorGrid, stencil_set, direction) + +See also: [`VolumeOperator`](@ref), [`LazyTensors.inflate`](@ref). +""" +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(g::EquidistantGrid, stencil_set::StencilSet) + +The first derivative operator on an `EquidistantGrid`. +Uses the `D1` stencil in `stencil_set`. +""" +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(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) + +The first derivative operator on an `EquidistantGrid` given an +`inner_stencil` and `closure_stencils`. +""" +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
--- a/src/SbpOperators/volumeops/derivatives/second_derivative.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/derivatives/second_derivative.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,19 +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 `g` along the coordinate +dimension specified by `direction`. -Creates the second-derivative operator `D2` as a `TensorMapping` +See also: [`VolumeOperator`](@ref), [`LazyTensors.inflate`](@ref). +""" +function second_derivative(g::TensorGrid, stencil_set, direction) + D₂ = second_derivative(g.grids[direction], stencil_set) + return LazyTensors.inflate(D₂, size(g), direction) +end -`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. +""" + second_derivative(g::EquidistantGrid, stencil_set::::StencilSet) -On a one-dimensional `grid`, `D2` is a `VolumeOperator`. On a multi-dimensional `grid`, `D2` is the outer product of the -one-dimensional operator with the `IdentityMapping`s in orthogonal coordinate dirrections. -Also see the documentation of `SbpOperators.volume_operator(...)` for more details. +The second derivative operator on an `EquidistantGrid`. +Uses the `D2` stencil in `stencil_set`. +""" +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 + """ -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) + second_derivative(g::EquidistantGrid, inner_stencil::Stencil, closure_stencils) + +The second derivative operator on an `EquidistantGrid`, given `inner_stencil` and +`closure_stencils`. +""" +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::EquidistantGrid{1}, inner_stencil, closure_stencils) = second_derivative(grid,inner_stencil,closure_stencils,1) -export second_derivative
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/SbpOperators/volumeops/derivatives/second_derivative_variable.jl Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/inner_products/inner_product.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,34 +1,52 @@ """ - inner_product(grid::EquidistantGrid, interior_weight, closure_weights) - -Creates the discrete inner product operator `H` as a `TensorMapping` on an -equidistant grid, defined as `(u,v) = u'Hv` for grid functions `u,v`. + inner_product(grid, ...) -`inner_product` creates `H` on `grid` using the `interior_weight` for the -interior points and the `closure_weights` for the points close to the -boundary. - -On a 1-dimensional grid, `H` is a `ConstantInteriorScalingOperator`. On a -N-dimensional grid, `H` is the outer product of the 1-dimensional inner -product operators for each coordinate direction. Also see the documentation of -On a 0-dimensional grid, `H` is a 0-dimensional `IdentityMapping`. +The inner product on a given grid with weights from a stencils set or given +explicitly. """ -function inner_product(grid::EquidistantGrid, interior_weight, closure_weights) - Hs = () +function inner_product end - for i ∈ 1:dimension(grid) - Hs = (Hs..., inner_product(restrict(grid, i), interior_weight, closure_weights)) - end +""" + inner_product(tg::TensorGrid, stencil_set::StencilSet) - return foldl(⊗, Hs) -end -export inner_product - -function inner_product(grid::EquidistantGrid{1}, interior_weight, closure_weights) - h = spacing(grid)[1] - - H = SbpOperators.ConstantInteriorScalingOperator(grid, h*interior_weight, h.*closure_weights) - return H +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 -inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityMapping{eltype(grid)}() +""" + 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(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 + +""" + inner_product(g::EquidistantGrid, interior_weight, closure_weights) + +The inner product on `g` with explicit weights. + +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(g::ZeroDimGrid, stencil_set::StencilSet) + +The identity tensor with the correct type parameters. + +Implemented to simplify 1D code for SBP operators. +""" +inner_product(g::ZeroDimGrid, stencil_set::StencilSet) = IdentityTensor{component_type(g)}() +
--- a/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/inner_products/inverse_inner_product.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,30 +1,51 @@ """ - inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights) + inverse_inner_product(grid, ...) -Constructs the inverse inner product operator `H⁻¹` as a `TensorMapping` using -the weights of `H`, `interior_weight`, `closure_weights`. `H⁻¹` is inverse of -the inner product operator `H`. +The inverse inner product on a given grid with weights from a stencils set or given +explicitly. +""" +function inverse_inner_product 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 `IdentityMapping`. """ -function inverse_inner_product(grid::EquidistantGrid, interior_weight, closure_weights) - H⁻¹s = () + inverse_inner_product(tg::TensorGrid, stencil_set::StencilSet) - for i ∈ 1:dimension(grid) - H⁻¹s = (H⁻¹s..., inverse_inner_product(restrict(grid, i), interior_weight, closure_weights)) - end - - return foldl(⊗, H⁻¹s) +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 -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, 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(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 -export inverse_inner_product + +""" + inverse_inner_product(g::EquidistantGrid, interior_weight, closure_weights) + +The inverse inner product on `g` with explicit weights. -inverse_inner_product(grid::EquidistantGrid{0}, interior_weight, closure_weights) = IdentityMapping{eltype(grid)}() +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(g::ZeroDimGrid, stencil_set::StencilSet) + +The identity tensor with the correct type parameters. + +Implemented to simplify 1D code for SBP operators. +""" +inverse_inner_product(g::ZeroDimGrid, stencil_set::StencilSet) = IdentityTensor{component_type(g)}()
--- a/src/SbpOperators/volumeops/laplace/laplace.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/laplace/laplace.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,21 +1,127 @@ """ - laplace(grid::EquidistantGrid{Dim}, inner_stencil, closure_stencils) + Laplace{T, Dim, TM} <: LazyTensor{T, Dim, Dim} + +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 + stencil_set::StencilSet # Stencil set of the operator +end -Creates the Laplace operator operator `Δ` as a `TensorMapping` +""" + Laplace(g::Grid, stencil_set::StencilSet) + +Creates the `Laplace` operator `Δ` on `g` given `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. +See also [`laplace`](@ref). +""" +function Laplace(g::Grid, stencil_set::StencilSet) + Δ = laplace(g, stencil_set) + return Laplace(Δ, stencil_set) +end -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. +LazyTensors.range_size(L::Laplace) = LazyTensors.range_size(L.D) +LazyTensors.domain_size(L::Laplace) = LazyTensors.domain_size(L.D) +LazyTensors.apply(L::Laplace, v::AbstractArray, I...) = LazyTensors.apply(L.D,v,I...) + +# TODO: Implement pretty printing of Laplace once pretty printing of LazyTensors is implemented. +# Base.show(io::IO, L::Laplace) = ... + """ -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) + laplace(g::Grid, stencil_set) + +Creates the Laplace operator operator `Δ` as a `LazyTensor` on `g`. + +`Δ` 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 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 -export laplace +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/src/SbpOperators/volumeops/volume_operator.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,61 +1,43 @@ """ - volume_operator(grid, inner_stencil, closure_stencils, parity, direction) + VolumeOperator{T,N,M,K} <: LazyTensor{T,1,1} -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 `IdentityMapping`s, e.g for `Dim=3` the volume operator in the -y-direction is `I⊗op⊗I`. +A one-dimensional constant coefficients stencil operator. """ -function volume_operator(grid::EquidistantGrid, inner_stencil, closure_stencils, parity, direction) - #TODO: Check that direction <= Dim? +struct VolumeOperator{T,N,M,K} <: LazyTensor{T,1,1} + inner_stencil::Stencil{T,N} + closure_stencils::NTuple{M,Stencil{T,K}} + size::Int + parity::Parity - # Create 1D volume operator in along coordinate direction - op = VolumeOperator(restrict(grid, direction), inner_stencil, closure_stencils, parity) - # Create 1D IdentityMappings for each coordinate direction - one_d_grids = restrict.(Ref(grid), Tuple(1:dimension(grid))) - Is = IdentityMapping{eltype(grid)}.(size.(one_d_grids)) - # Formulate the correct outer product sequence of the identity mappings and - # the volume operator - parts = Base.setindex(Is, op, direction) - return foldl(⊗, parts) + 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 -""" - VolumeOperator{T,N,M,K} <: TensorOperator{T,1} -Implements a one-dimensional constant coefficients volume operator -""" -struct VolumeOperator{T,N,M,K} <: TensorMapping{T,1,1} - inner_stencil::Stencil{T,N} - closure_stencils::NTuple{M,Stencil{T,K}} - size::NTuple{1,Int} - parity::Parity -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{T}, v::AbstractVector{T}, i::Index{Lower}) where T +function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i::Index{Lower}) return @inbounds apply_stencil(op.closure_stencils[Int(i)], v, Int(i)) end -function LazyTensors.apply(op::VolumeOperator{T}, v::AbstractVector{T}, i::Index{Interior}) where T +function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i::Index{Interior}) return apply_stencil(op.inner_stencil, v, Int(i)) end -function LazyTensors.apply(op::VolumeOperator{T}, v::AbstractVector{T}, i::Index{Upper}) where T - return @inbounds Int(op.parity)*apply_stencil_backwards(op.closure_stencils[op.size[1]-Int(i)+1], v, Int(i)) +function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i::Index{Upper}) + 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{T}, v::AbstractVector{T}, i) where T - r = getregion(i, closure_size(op), op.size[1]) +function LazyTensors.apply(op::VolumeOperator, v::AbstractVector, i) + 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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 TensorMapping{T,2,1} where T -# @test e_w' isa TensorMapping{T,1,2} where T -# -# @test domain_size(e_w, (3,2)) == (2,) -# @test domain_size(e_e, (3,2)) == (2,) -# @test domain_size(e_s, (3,2)) == (3,) -# @test domain_size(e_n, (3,2)) == (3,) -# -# @test size(e_w'*v) == (5,) -# @test size(e_e'*v) == (5,) -# @test size(e_s'*v) == (4,) -# @test size(e_n'*v) == (4,) -# -# @test collect(e_w'*v) == [10,7,4,1.0,1] -# @test collect(e_e'*v) == [13,10,7,4,4.0] -# @test collect(e_s'*v) == [10,11,12,13.0] -# @test collect(e_n'*v) == [1,2,3,4.0] -# -# g_x = [1,2,3,4.0] -# g_y = [5,4,3,2,1.0] -# -# G_w = zeros(Float64, (4,5)) -# G_w[1,:] = g_y -# -# G_e = zeros(Float64, (4,5)) -# G_e[4,:] = g_y -# -# G_s = zeros(Float64, (4,5)) -# G_s[:,1] = g_x -# -# G_n = zeros(Float64, (4,5)) -# G_n[:,5] = g_x -# -# @test size(e_w*g_y) == (UnknownDim,5) -# @test size(e_e*g_y) == (UnknownDim,5) -# @test size(e_s*g_x) == (4,UnknownDim) -# @test size(e_n*g_x) == (4,UnknownDim) -# -# # These tests should be moved to where they are possible (i.e we know what the grid should be) -# @test_broken collect(e_w*g_y) == G_w -# @test_broken collect(e_e*g_y) == G_e -# @test_broken collect(e_s*g_x) == G_s -# @test_broken collect(e_n*g_x) == G_n -# end -# -# @testset "NormalDerivative" begin -# op = 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 TensorMapping{T,2,1} where T -# @test d_w' isa TensorMapping{T,1,2} where T -# -# @test domain_size(d_w, (3,2)) == (2,) -# @test domain_size(d_e, (3,2)) == (2,) -# @test domain_size(d_s, (3,2)) == (3,) -# @test domain_size(d_n, (3,2)) == (3,) -# -# @test size(d_w'*v) == (6,) -# @test size(d_e'*v) == (6,) -# @test size(d_s'*v) == (5,) -# @test size(d_n'*v) == (5,) -# -# @test collect(d_w'*v) ≈ v∂x[1,:] -# @test collect(d_e'*v) ≈ v∂x[5,:] -# @test collect(d_s'*v) ≈ v∂y[:,1] -# @test collect(d_n'*v) ≈ v∂y[:,6] -# -# -# d_x_l = zeros(Float64, 5) -# d_x_u = zeros(Float64, 5) -# for i ∈ eachindex(d_x_l) -# d_x_l[i] = op.dClosure[i-1] -# d_x_u[i] = -op.dClosure[length(d_x_u)-i] -# end -# -# d_y_l = zeros(Float64, 6) -# d_y_u = zeros(Float64, 6) -# for i ∈ eachindex(d_y_l) -# d_y_l[i] = op.dClosure[i-1] -# d_y_u[i] = -op.dClosure[length(d_y_u)-i] -# end -# -# function prod_matrix(x,y) -# G = zeros(Float64, length(x), length(y)) -# for I ∈ CartesianIndices(G) -# G[I] = x[I[1]]*y[I[2]] -# end -# -# return G -# end -# -# g_x = [1,2,3,4.0,5] -# g_y = [5,4,3,2,1.0,11] -# -# G_w = prod_matrix(d_x_l, g_y) -# G_e = prod_matrix(d_x_u, g_y) -# G_s = prod_matrix(g_x, d_y_l) -# G_n = prod_matrix(g_x, d_y_u) -# -# -# @test size(d_w*g_y) == (UnknownDim,6) -# @test size(d_e*g_y) == (UnknownDim,6) -# @test size(d_s*g_x) == (5,UnknownDim) -# @test size(d_n*g_x) == (5,UnknownDim) -# -# # These tests should be moved to where they are possible (i.e we know what the grid should be) -# @test_broken collect(d_w*g_y) ≈ G_w -# @test_broken collect(d_e*g_y) ≈ G_e -# @test_broken collect(d_s*g_x) ≈ G_s -# @test_broken collect(d_n*g_x) ≈ G_n -# end -# -# @testset "BoundaryQuadrature" begin -# op = 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 Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,101 +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 -end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/equidistant_grid_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,167 @@ +using Diffinitive.Grids +using Test +using Diffinitive.LazyTensors + + +@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,) + + 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 +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 Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,127 @@ +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 +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/mapped_grid_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,381 @@ +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((0,0), (1,1), 10, 11) + @test logical_grid(mg) == lg + @test collect(mg) == map(x̄, lg) + + @test mapped_grid(x̄, J, lg) == 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/tensor_grid_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,263 @@ +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]) + + @test boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, LowerBoundary}()) == (1,:) + @test boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{1, UpperBoundary}()) == (11,:) + @test boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, LowerBoundary}()) == (:,1) + @test boundary_indices(TensorGrid(g₁, g₂), TensorGridBoundary{2, UpperBoundary}()) == (:,6) + @test boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, LowerBoundary}()) == (1,) + @test boundary_indices(TensorGrid(g₁, g₄), TensorGridBoundary{1, UpperBoundary}()) == (11,) + @test boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, LowerBoundary}()) == (1,) + @test boundary_indices(TensorGrid(g₄,g₁), TensorGridBoundary{2, UpperBoundary}()) == (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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/LazyTensors/lazy_array_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,6 +1,6 @@ using Test -using Sbplib.LazyTensors -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices @testset "LazyArray" begin @@ -59,8 +59,6 @@ end @test_throws BoundsError (v1 +̃ v2)[4] v2 = [1., 2, 3, 4] - # Test that size of arrays is asserted when not specified inbounds - # TODO: Replace these errors with SizeMismatch @test_throws DimensionMismatch v1 +̃ v2 # Test operations on LazyArray @@ -69,15 +67,20 @@ @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 that size of arrays is asserted when not specified inbounds - # TODO: Replace these errors with SizeMismatch @test_throws DimensionMismatch v1 + v2 end @@ -99,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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/LazyTensors/lazy_tensor_operations_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,20 +1,31 @@ using Test -using Sbplib.LazyTensors -using Sbplib.RegionIndices +using Diffinitive.LazyTensors +using Diffinitive.RegionIndices using Tullio -@testset "Mapping transpose" begin - struct DummyMapping{T,R,D} <: TensorMapping{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::TransposableDummyMapping) = :range_size +LazyTensors.domain_size(m::TransposableDummyMapping) = :domain_size + - LazyTensors.range_size(m::DummyMapping) = :range_size - LazyTensors.domain_size(m::DummyMapping) = :domain_size +struct SizeDoublingMapping{T,R,D} <: LazyTensor{T,R,D} + domain_size::NTuple{D,Int} +end - m = DummyMapping{Float64,2,3}() - @test m' isa TensorMapping{Float64, 3,2} +LazyTensors.apply(m::SizeDoublingMapping{T,R}, v, i::Vararg{Any,R}) where {T,R} = (:apply,v,i) +LazyTensors.range_size(m::SizeDoublingMapping) = 2 .* m.domain_size +LazyTensors.domain_size(m::SizeDoublingMapping) = m.domain_size + + + +@testset "Mapping transpose" begin + 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 @test apply(m'',zeros(Float64,(0,0,0)), 0, 0) == :apply @@ -24,71 +35,103 @@ @test domain_size(m') == :range_size end + @testset "TensorApplication" begin - struct SizeDoublingMapping{T,R,D} <: TensorMapping{T,R,D} - domain_size::NTuple{D,Int} - end - - LazyTensors.apply(m::SizeDoublingMapping{T,R}, v, i::Vararg{Any,R}) where {T,R} = (:apply,v,i) - LazyTensors.range_size(m::SizeDoublingMapping) = 2 .* m.domain_size - LazyTensors.domain_size(m::SizeDoublingMapping) = m.domain_size - - m = SizeDoublingMapping{Int, 1, 1}((3,)) + mm = SizeDoublingMapping{Int, 1, 1}((6,)) v = [0,1,2] - @test m*v isa AbstractVector{Int} @test size(m*v) == 2 .*size(v) - @test (m*v)[0] == (:apply,v,(0,)) - @test m*m*v isa AbstractVector{Int} - @test (m*m*v)[1] == (:apply,m*v,(1,)) - @test (m*m*v)[3] == (:apply,m*v,(3,)) - @test (m*m*v)[6] == (:apply,m*v,(6,)) - @test_broken BoundsError == (m*m*v)[0] - @test_broken BoundsError == (m*m*v)[7] + @test (m*v)[1] == (:apply,v,(1,)) + @test (mm*m*v)[1] == (:apply,m*v,(1,)) + @test (mm*m*v)[3] == (:apply,m*v,(3,)) + @test (mm*m*v)[6] == (:apply,m*v,(6,)) @test_throws MethodError m*m - m = SizeDoublingMapping{Int, 2, 1}((3,)) - @test_throws MethodError m*ones(Int,2,2) - @test_throws MethodError m*m*v + @test (m*v)[CartesianIndex(2)] == (:apply,v,(2,)) + @test (mm*m*v)[CartesianIndex(2)] == (:apply,m*v,(2,)) m = SizeDoublingMapping{Float64, 2, 2}((3,3)) + mm = SizeDoublingMapping{Float64, 2, 2}((6,6)) v = ones(3,3) @test size(m*v) == 2 .*size(v) @test (m*v)[1,2] == (:apply,v,(1,2)) - struct ScalingOperator{T,D} <: TensorMapping{T,D,D} - λ::T - size::NTuple{D,Int} - end + @test (m*v)[CartesianIndex(2,3)] == (:apply,v,(2,3)) + @test (mm*m*v)[CartesianIndex(4,3)] == (:apply,m*v,(4,3)) - LazyTensors.apply(m::ScalingOperator{T,D}, v, I::Vararg{Any,D}) where {T,D} = m.λ*v[I...] - LazyTensors.range_size(m::ScalingOperator) = m.size - LazyTensors.domain_size(m::ScalingOperator) = m.size - - m = ScalingOperator{Int,1}(2,(3,)) + m = ScalingTensor(2,(3,)) v = [1,2,3] @test m*v isa AbstractVector @test m*v == [2,4,6] - m = ScalingOperator{Int,2}(2,(2,2)) + m = ScalingTensor(2,(2,2)) v = [[1 2];[3 4]] @test m*v == [[2 4];[6 8]] @test (m*v)[2,1] == 6 + + @testset "Error on index out of bounds" begin + m = SizeDoublingMapping{Int, 1, 1}((3,)) + v = [0,1,2] + + @test_throws BoundsError (m*v)[0] + @test_throws BoundsError (m*v)[7] + end + + @testset "Error on unmatched dimensions" begin + v = [0,1,2] + m = SizeDoublingMapping{Int, 2, 1}((3,)) + @test_throws MethodError m*ones(Int,2,2) + @test_throws MethodError m*m*v + end + + @testset "Error on unmatched sizes" begin + @test_throws DomainSizeMismatch ScalingTensor(2,(2,))*ones(3) + @test_throws DomainSizeMismatch ScalingTensor(2,(2,))*ScalingTensor(2,(3,))*ones(3) + end + + + @testset "Type calculation" begin + m = ScalingTensor(2,(3,)) + v = [1.,2.,3.] + @test m*v isa AbstractVector{Float64} + @test m*v == [2.,4.,6.] + @inferred m*v + @inferred (m*v)[1] + + m = ScalingTensor(2,(2,2)) + v = [[1. 2.];[3. 4.]] + @test m*v == [[2. 4.];[6. 8.]] + @test (m*v)[2,1] == 6. + @inferred m*v + @inferred (m*v)[1] + + m = ScalingTensor(2. +2. *im,(3,)) + v = [1.,2.,3.] + @test m*v isa AbstractVector{ComplexF64} + @test m*v == [2. + 2. *im, 4. + 4. *im, 6. + 6. *im] + @inferred m*v + @inferred (m*v)[1] + + m = ScalingTensor(1,(3,)) + v = [2. + 2. *im, 4. + 4. *im, 6. + 6. *im] + @test m*v isa AbstractVector{ComplexF64} + @test m*v == [2. + 2. *im, 4. + 4. *im, 6. + 6. *im] + @inferred m*v + @inferred (m*v)[1] + + m = ScalingTensor(2., (3,)) + v = [[1,2,3], [3,2,1],[1,3,1]] + @test m*v isa AbstractVector{Vector{Float64}} + @test m*v == [[2.,4.,6.], [6.,4.,2.],[2.,6.,2.]] + @inferred m*v + @inferred (m*v)[1] + end end -@testset "TensorMapping binary operations" begin - struct ScalarMapping{T,R,D} <: TensorMapping{T,R,D} - λ::T - range_size::NTuple{R,Int} - domain_size::NTuple{D,Int} - end - LazyTensors.apply(m::ScalarMapping{T,R}, v, I::Vararg{Any,R}) where {T,R} = m.λ*v[I...] - LazyTensors.range_size(m::ScalarMapping) = m.domain_size - LazyTensors.domain_size(m::ScalarMapping) = m.range_size - - A = ScalarMapping{Float64,1,1}(2.0, (3,), (3,)) - B = ScalarMapping{Float64,1,1}(3.0, (3,), (3,)) +@testset "LazyTensor binary operations" begin + A = ScalingTensor(2.0, (3,)) + B = ScalingTensor(3.0, (3,)) v = [1.1,1.2,1.3] for i ∈ eachindex(v) @@ -99,22 +142,34 @@ @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) + + @test ((A+B)*ComplexF64[1.1,1.2,1.3])[3] isa ComplexF64 + + @testset "Error on unmatched sizes" begin + @test_throws Union{DomainSizeMismatch, RangeSizeMismatch} ScalingTensor(2.0, (3,)) + ScalingTensor(2.0, (4,)) + + @test_throws DomainSizeMismatch ScalingTensor(2.0, (4,)) + SizeDoublingMapping{Float64,1,1}((2,)) + @test_throws DomainSizeMismatch SizeDoublingMapping{Float64,1,1}((2,)) + ScalingTensor(2.0, (4,)) + @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 end -@testset "TensorMappingComposition" begin +@testset "TensorComposition" begin A = rand(2,3) B = rand(3,4) - à = LazyLinearMap(A, (1,), (2,)) - B̃ = LazyLinearMap(B, (1,), (2,)) + à = DenseTensor(A, (1,), (2,)) + B̃ = DenseTensor(B, (1,), (2,)) - @test Ã∘B̃ isa TensorMappingComposition + @test Ã∘B̃ isa TensorComposition @test range_size(Ã∘B̃) == (2,) @test domain_size(Ã∘B̃) == (4,) - @test_throws SizeMismatch B̃∘à + @test_throws DomainSizeMismatch B̃∘à # @test @inbounds B̃∘à # Should not error even though dimensions don't match. (Since ]test runs with forced boundschecking this is currently not testable 2020-10-16) @@ -123,210 +178,125 @@ v = rand(2) @test (Ã∘B̃)'*v ≈ B'*A'*v rtol=1e-14 -end -@testset "LazyLinearMap" begin - # Test a standard matrix-vector product - # mapping vectors of size 4 to vectors of size 3. - A = rand(3,4) - à = LazyLinearMap(A, (1,), (2,)) - v = rand(4) - w = rand(3) - - @test à isa LazyLinearMap{T,1,1} where T - @test à isa TensorMapping{T,1,1} where T - @test range_size(Ã) == (3,) - @test domain_size(Ã) == (4,) - - @test Ã*ones(4) ≈ A*ones(4) atol=5e-13 - @test Ã*v ≈ A*v atol=5e-13 - @test Ã'*w ≈ A'*w - - A = rand(2,3,4) - @test_throws DomainError LazyLinearMap(A, (3,1), (2,)) + @test (Ã∘B̃*ComplexF64[1.,2.,3.,4.])[1] isa ComplexF64 + @test ((Ã∘B̃)'*ComplexF64[1.,2.])[1] isa ComplexF64 - # Test more exotic mappings - B = rand(3,4,2) - # Map vectors of size 2 to matrices of size (3,4) - B̃ = LazyLinearMap(B, (1,2), (3,)) - v = rand(2) - - @test range_size(B̃) == (3,4) - @test domain_size(B̃) == (2,) - @test B̃ isa TensorMapping{T,2,1} where T - @test B̃*ones(2) ≈ B[:,:,1] + B[:,:,2] atol=5e-13 - @test B̃*v ≈ B[:,:,1]*v[1] + B[:,:,2]*v[2] atol=5e-13 - - # Map matrices of size (3,2) to vectors of size 4 - B̃ = LazyLinearMap(B, (2,), (1,3)) - v = rand(3,2) - - @test range_size(B̃) == (4,) - @test domain_size(B̃) == (3,2) - @test B̃ isa TensorMapping{T,1,2} where T - @test B̃*ones(3,2) ≈ B[1,:,1] + B[2,:,1] + B[3,:,1] + - B[1,:,2] + B[2,:,2] + B[3,:,2] atol=5e-13 - @test B̃*v ≈ B[1,:,1]*v[1,1] + B[2,:,1]*v[2,1] + B[3,:,1]*v[3,1] + - B[1,:,2]v[1,2] + B[2,:,2]*v[2,2] + B[3,:,2]*v[3,2] atol=5e-13 - - - # TODO: - # @inferred (B̃*v)[2] + a = 2. + v = rand(3) + @test a*à isa TensorComposition + @test a*à == Ã*a + @test range_size(a*Ã) == range_size(Ã) + @test domain_size(a*Ã) == domain_size(Ã) + @test a*Ã*v ≈ a.*A*v rtol=1e-14 end -@testset "IdentityMapping" begin - @test IdentityMapping{Float64}((4,5)) isa IdentityMapping{T,2} where T - @test IdentityMapping{Float64}((4,5)) isa TensorMapping{T,2,2} where T - @test IdentityMapping{Float64}((4,5)) == IdentityMapping{Float64}(4,5) - - @test IdentityMapping(3,2) isa IdentityMapping{Float64,2} - - for sz ∈ [(4,5),(3,),(5,6,4)] - I = IdentityMapping{Float64}(sz) - v = rand(sz...) - @test I*v == v - @test I'*v == v - - @test range_size(I) == sz - @test domain_size(I) == sz - end - - I = IdentityMapping{Float64}((4,5)) - v = rand(4,5) - @inferred (I*v)[3,2] - @inferred (I'*v)[3,2] - @inferred range_size(I) - - @inferred range_dim(I) - @inferred domain_dim(I) - - à = rand(4,2) - A = LazyLinearMap(Ã,(1,),(2,)) - I1 = IdentityMapping{Float64}(2) - I2 = IdentityMapping{Float64}(4) - @test A∘I1 == A - @test I2∘A == A - @test I1∘I1 == I1 - @test_throws SizeMismatch I1∘A - @test_throws SizeMismatch A∘I2 - @test_throws SizeMismatch I1∘I2 -end - -@testset "InflatedTensorMapping" begin - I(sz...) = IdentityMapping(sz...) +@testset "InflatedTensor" begin + I(sz...) = IdentityTensor(sz...) à = rand(4,2) B̃ = rand(4,2,3) C̃ = rand(4,2,3) - A = LazyLinearMap(Ã,(1,),(2,)) - B = LazyLinearMap(B̃,(1,2),(3,)) - C = LazyLinearMap(C̃,(1,),(2,3)) + A = DenseTensor(Ã,(1,),(2,)) + B = DenseTensor(B̃,(1,2),(3,)) + C = DenseTensor(C̃,(1,),(2,3)) @testset "Constructors" begin - @test InflatedTensorMapping(I(3,2), A, I(4)) isa TensorMapping{Float64, 4, 4} - @test InflatedTensorMapping(I(3,2), B, I(4)) isa TensorMapping{Float64, 5, 4} - @test InflatedTensorMapping(I(3), C, I(2,3)) isa TensorMapping{Float64, 4, 5} - @test InflatedTensorMapping(C, I(2,3)) isa TensorMapping{Float64, 3, 4} - @test InflatedTensorMapping(I(3), C) isa TensorMapping{Float64, 2, 3} - @test InflatedTensorMapping(I(3), I(2,3)) isa TensorMapping{Float64, 3, 3} + @test InflatedTensor(I(3,2), A, I(4)) isa LazyTensor{Float64, 4, 4} + @test InflatedTensor(I(3,2), B, I(4)) isa LazyTensor{Float64, 5, 4} + @test InflatedTensor(I(3), C, I(2,3)) isa LazyTensor{Float64, 4, 5} + @test InflatedTensor(C, I(2,3)) isa LazyTensor{Float64, 3, 4} + @test InflatedTensor(I(3), C) isa LazyTensor{Float64, 2, 3} + @test InflatedTensor(I(3), I(2,3)) isa LazyTensor{Float64, 3, 3} end @testset "Range and domain size" begin - @test range_size(InflatedTensorMapping(I(3,2), A, I(4))) == (3,2,4,4) - @test domain_size(InflatedTensorMapping(I(3,2), A, I(4))) == (3,2,2,4) + @test range_size(InflatedTensor(I(3,2), A, I(4))) == (3,2,4,4) + @test domain_size(InflatedTensor(I(3,2), A, I(4))) == (3,2,2,4) - @test range_size(InflatedTensorMapping(I(3,2), B, I(4))) == (3,2,4,2,4) - @test domain_size(InflatedTensorMapping(I(3,2), B, I(4))) == (3,2,3,4) + @test range_size(InflatedTensor(I(3,2), B, I(4))) == (3,2,4,2,4) + @test domain_size(InflatedTensor(I(3,2), B, I(4))) == (3,2,3,4) - @test range_size(InflatedTensorMapping(I(3), C, I(2,3))) == (3,4,2,3) - @test domain_size(InflatedTensorMapping(I(3), C, I(2,3))) == (3,2,3,2,3) + @test range_size(InflatedTensor(I(3), C, I(2,3))) == (3,4,2,3) + @test domain_size(InflatedTensor(I(3), C, I(2,3))) == (3,2,3,2,3) - @inferred range_size(InflatedTensorMapping(I(3,2), A, I(4))) == (3,2,4,4) - @inferred domain_size(InflatedTensorMapping(I(3,2), A, I(4))) == (3,2,2,4) + @inferred range_size(InflatedTensor(I(3,2), A, I(4))) == (3,2,4,4) + @inferred domain_size(InflatedTensor(I(3,2), A, I(4))) == (3,2,2,4) end @testset "Application" begin # Testing regular application and transposed application with inflation "before", "after" and "before and after". # The inflated tensor mappings are chosen to preserve, reduce and increase the dimension of the result compared to the input. - tests = [ + cases = [ ( - InflatedTensorMapping(I(3,2), A, I(4)), + InflatedTensor(I(3,2), A, I(4)), (v-> @tullio res[a,b,c,d] := Ã[c,i]*v[a,b,i,d]), # Expected result of apply (v-> @tullio res[a,b,c,d] := Ã[i,c]*v[a,b,i,d]), # Expected result of apply_transpose ), ( - InflatedTensorMapping(I(3,2), B, I(4)), + InflatedTensor(I(3,2), B, I(4)), (v-> @tullio res[a,b,c,d,e] := B̃[c,d,i]*v[a,b,i,e]), (v-> @tullio res[a,b,c,d] := B̃[i,j,c]*v[a,b,i,j,d]), ), ( - InflatedTensorMapping(I(3,2), C, I(4)), + InflatedTensor(I(3,2), C, I(4)), (v-> @tullio res[a,b,c,d] := C̃[c,i,j]*v[a,b,i,j,d]), (v-> @tullio res[a,b,c,d,e] := C̃[i,c,d]*v[a,b,i,e]), ), ( - InflatedTensorMapping(I(3,2), A), + InflatedTensor(I(3,2), A), (v-> @tullio res[a,b,c] := Ã[c,i]*v[a,b,i]), (v-> @tullio res[a,b,c] := Ã[i,c]*v[a,b,i]), ), ( - InflatedTensorMapping(I(3,2), B), + InflatedTensor(I(3,2), B), (v-> @tullio res[a,b,c,d] := B̃[c,d,i]*v[a,b,i]), (v-> @tullio res[a,b,c] := B̃[i,j,c]*v[a,b,i,j]), ), ( - InflatedTensorMapping(I(3,2), C), + InflatedTensor(I(3,2), C), (v-> @tullio res[a,b,c] := C̃[c,i,j]*v[a,b,i,j]), (v-> @tullio res[a,b,c,d] := C̃[i,c,d]*v[a,b,i]), ), ( - InflatedTensorMapping(A,I(4)), + InflatedTensor(A,I(4)), (v-> @tullio res[a,b] := Ã[a,i]*v[i,b]), (v-> @tullio res[a,b] := Ã[i,a]*v[i,b]), ), ( - InflatedTensorMapping(B,I(4)), + InflatedTensor(B,I(4)), (v-> @tullio res[a,b,c] := B̃[a,b,i]*v[i,c]), (v-> @tullio res[a,b] := B̃[i,j,a]*v[i,j,b]), ), ( - InflatedTensorMapping(C,I(4)), + InflatedTensor(C,I(4)), (v-> @tullio res[a,b] := C̃[a,i,j]*v[i,j,b]), (v-> @tullio res[a,b,c] := C̃[i,a,b]*v[i,c]), ), ] - @testset "apply" begin - for i ∈ 1:length(tests) - tm = tests[i][1] - v = rand(domain_size(tm)...) - true_value = tests[i][2](v) - @test tm*v ≈ true_value rtol=1e-14 - end + @testset "$tm" for (tm, true_apply, true_apply_transpose) ∈ cases + v = rand(domain_size(tm)...) + @test tm*v ≈ true_apply(v) rtol=1e-14 + + v = rand(range_size(tm)...) + @test tm'*v ≈ true_apply_transpose(v) rtol=1e-14 end - @testset "apply_transpose" begin - for i ∈ 1:length(tests) - tm = tests[i][1] - v = rand(range_size(tm)...) - true_value = tests[i][3](v) - @test tm'*v ≈ true_value rtol=1e-14 - end + @testset "application to other type" begin + tm = InflatedTensor(I(3,2), A, I(4)) + + v = rand(ComplexF64, domain_size(tm)...) + @test (tm*v)[1,2,3,1] isa ComplexF64 + + v = rand(ComplexF64, domain_size(tm')...) + @test (tm'*v)[1,2,2,1] isa ComplexF64 end @testset "Inference of application" begin - struct ScalingOperator{T,D} <: TensorMapping{T,D,D} - λ::T - size::NTuple{D,Int} - end - - LazyTensors.apply(m::ScalingOperator{T,D}, v, I::Vararg{Any,D}) where {T,D} = m.λ*v[I...] - LazyTensors.range_size(m::ScalingOperator) = m.size - LazyTensors.domain_size(m::ScalingOperator) = m.size - - tm = InflatedTensorMapping(I(2,3),ScalingOperator(2.0, (3,2)),I(3,4)) + tm = InflatedTensor(I(2,3),ScalingTensor(2.0, (3,2)),I(3,4)) v = rand(domain_size(tm)...) @inferred apply(tm,v,1,2,3,2,2,4) @@ -334,94 +304,24 @@ end end - @testset "InflatedTensorMapping of InflatedTensorMapping" begin - A = ScalingOperator(2.0,(2,3)) - itm = InflatedTensorMapping(I(3,2), A, I(4)) - @test InflatedTensorMapping(I(4), itm, I(2)) == InflatedTensorMapping(I(4,3,2), A, I(4,2)) - @test InflatedTensorMapping(itm, I(2)) == InflatedTensorMapping(I(3,2), A, I(4,2)) - @test InflatedTensorMapping(I(4), itm) == InflatedTensorMapping(I(4,3,2), A, I(4)) + @testset "InflatedTensor of InflatedTensor" begin + A = ScalingTensor(2.0,(2,3)) + itm = InflatedTensor(I(3,2), A, I(4)) + @test InflatedTensor(I(4), itm, I(2)) == InflatedTensor(I(4,3,2), A, I(4,2)) + @test InflatedTensor(itm, I(2)) == InflatedTensor(I(3,2), A, I(4,2)) + @test InflatedTensor(I(4), itm) == InflatedTensor(I(4,3,2), A, I(4)) - @test InflatedTensorMapping(I(2), I(2), I(2)) isa InflatedTensorMapping # The constructor should always return its type. + @test InflatedTensor(I(2), I(2), I(2)) isa InflatedTensor # The constructor should always return its type. end end -@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(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 - -@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) -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,)) - - @inferred LazyTensors.split_tuple((1,2,3,4),Val(3)) - @inferred LazyTensors.split_tuple((1,2,true,4),Val(3)) - end - - @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)) - 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) -end - - @testset "LazyOuterProduct" begin - struct ScalingOperator{T,D} <: TensorMapping{T,D,D} - λ::T - size::NTuple{D,Int} - end - - LazyTensors.apply(m::ScalingOperator{T,D}, v, I::Vararg{Any,D}) where {T,D} = m.λ*v[I...] - LazyTensors.range_size(m::ScalingOperator) = m.size - LazyTensors.domain_size(m::ScalingOperator) = m.size - - A = ScalingOperator(2.0, (5,)) - B = ScalingOperator(3.0, (3,)) - C = ScalingOperator(5.0, (3,2)) + A = ScalingTensor(2.0, (5,)) + B = ScalingTensor(3.0, (3,)) + C = ScalingTensor(5.0, (3,2)) AB = LazyOuterProduct(A,B) - @test AB isa TensorMapping{T,2,2} where T + @test AB isa LazyTensor{T,2,2} where T @test range_size(AB) == (5,3) @test domain_size(AB) == (5,3) @@ -430,7 +330,7 @@ ABC = LazyOuterProduct(A,B,C) - @test ABC isa TensorMapping{T,4,4} where T + @test ABC isa LazyTensor{T,4,4} where T @test range_size(ABC) == (5,3,3,2) @test domain_size(ABC) == (5,3,3,2) @@ -443,8 +343,8 @@ v₁ = rand(2,4,3) v₂ = rand(4,3,2) - à = LazyLinearMap(A,(1,),(2,)) - B̃ = LazyLinearMap(B,(1,),(2,3)) + à = DenseTensor(A,(1,),(2,)) + B̃ = DenseTensor(B,(1,),(2,3)) ÃB̃ = LazyOuterProduct(Ã,B̃) @tullio ABv[i,k] := A[i,j]*B[k,l,m]*v₁[j,l,m] @@ -455,15 +355,28 @@ @test B̃Ã*v₂ ≈ BAv @testset "Indentity mapping arguments" begin - @test LazyOuterProduct(IdentityMapping(3,2), IdentityMapping(1,2)) == IdentityMapping(3,2,1,2) + @test LazyOuterProduct(IdentityTensor(3,2), IdentityTensor(1,2)) == IdentityTensor(3,2,1,2) + + à = DenseTensor(A,(1,),(2,)) + @test LazyOuterProduct(IdentityTensor(3,2), Ã) == InflatedTensor(IdentityTensor(3,2),Ã) + @test LazyOuterProduct(Ã, IdentityTensor(3,2)) == InflatedTensor(Ã,IdentityTensor(3,2)) - à = LazyLinearMap(A,(1,),(2,)) - @test LazyOuterProduct(IdentityMapping(3,2), Ã) == InflatedTensorMapping(IdentityMapping(3,2),Ã) - @test LazyOuterProduct(Ã, IdentityMapping(3,2)) == InflatedTensorMapping(Ã,IdentityMapping(3,2)) + I1 = IdentityTensor(3,2) + I2 = IdentityTensor(4) + @test I1⊗Ã⊗I2 == InflatedTensor(I1, Ã, I2) + end +end - I1 = IdentityMapping(3,2) - I2 = IdentityMapping(4) - @test I1⊗Ã⊗I2 == InflatedTensorMapping(I1, Ã, I2) - 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/LazyTensors/lazy_tensor_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,12 @@ +using Test +using Diffinitive.LazyTensors + +@testset "Generic Mapping methods" begin + struct DummyMapping{T,R,D} <: LazyTensor{T,R,D} end + LazyTensors.apply(m::DummyMapping{T,R,D}, v, I::Vararg{Any,R}) where {T,R,D} = :apply + @test range_dim(DummyMapping{Int,2,3}()) == 2 + @test domain_dim(DummyMapping{Int,2,3}()) == 3 + @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),0,0) == :apply + @test eltype(DummyMapping{Int,2,3}()) == Int + @test eltype(DummyMapping{Float64,2,3}()) == Float64 +end
--- a/test/LazyTensors/tensor_mapping_test.jl Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -using Test -using Sbplib.LazyTensors - -@testset "Generic Mapping methods" begin - struct DummyMapping{T,R,D} <: TensorMapping{T,R,D} end - LazyTensors.apply(m::DummyMapping{T,R,D}, v, I::Vararg{Any,R}) where {T,R,D} = :apply - @test range_dim(DummyMapping{Int,2,3}()) == 2 - @test domain_dim(DummyMapping{Int,2,3}()) == 3 - @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),0,0) == :apply - @test eltype(DummyMapping{Int,2,3}()) == Int - @test eltype(DummyMapping{Float64,2,3}()) == Float64 -end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/LazyTensors/tensor_types_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,151 @@ +using Test +using Diffinitive.LazyTensors +using BenchmarkTools + +@testset "IdentityTensor" begin + @test IdentityTensor{Float64}((4,5)) isa IdentityTensor{T,2} where T + @test IdentityTensor{Float64}((4,5)) isa LazyTensor{T,2,2} where T + @test IdentityTensor{Float64}((4,5)) == IdentityTensor{Float64}(4,5) + + @test IdentityTensor(3,2) isa IdentityTensor{Float64,2} + + for sz ∈ [(4,5),(3,),(5,6,4)] + I = IdentityTensor{Float64}(sz) + v = rand(sz...) + @test I*v == v + @test I'*v == v + + v = rand(ComplexF64,sz...) + @test I*v == v + @test I'*v == v + + @test range_size(I) == sz + @test domain_size(I) == sz + end + + I = IdentityTensor{Float64}((4,5)) + v = rand(4,5) + @inferred (I*v)[3,2] + @inferred (I'*v)[3,2] + @inferred range_size(I) + + @inferred range_dim(I) + @inferred domain_dim(I) + + à = rand(4,2) + A = DenseTensor(Ã,(1,),(2,)) + I1 = IdentityTensor{Float64}(2) + I2 = IdentityTensor{Float64}(4) + @test A∘I1 == A + @test I2∘A == A + @test I1∘I1 == I1 + @test_throws DomainSizeMismatch I1∘A + @test_throws DomainSizeMismatch A∘I2 + @test_throws DomainSizeMismatch I1∘I2 +end + + +@testset "ScalingTensor" begin + st = ScalingTensor(2.,(3,4)) + @test st isa LazyTensor{Float64, 2, 2} + @test range_size(st) == (3,4) + @test domain_size(st) == (3,4) + + v = rand(3,4) + @test st*v == 2.0 .* v + @test st'*v == 2.0 .* v + + @inferred (st*v)[2,2] + @inferred (st'*v)[2,2] +end + +@testset "DiagonalTensor" begin + @test DiagonalTensor([1,2,3,4]) isa LazyTensor{Int,1,1} + @test DiagonalTensor([1 2 3; 4 5 6]) isa LazyTensor{Int,2,2} + @test DiagonalTensor([1. 2. 3.; 4. 5. 6.]) isa LazyTensor{Float64,2,2} + + @test range_size(DiagonalTensor([1,2,3,4])) == (4,) + @test domain_size(DiagonalTensor([1,2,3,4])) == (4,) + + @test range_size(DiagonalTensor([1 2 3; 4 5 6])) == (2,3) + @test domain_size(DiagonalTensor([1 2 3; 4 5 6])) == (2,3) + + @testset "apply size=$sz" for sz ∈ [(4,),(3,2),(3,4,2)] + diag = rand(sz...) + tm = DiagonalTensor(diag) + + v = rand(sz...) + + @test tm*v == diag.*v + @test tm'*v == diag.*v + end + + + @testset "allocations size=$sz" for sz ∈ [(4,),(3,2),(3,4,2)] + diag = rand(sz...) + tm = DiagonalTensor(diag) + + v = rand(sz...) + + @test tm*v == diag.*v + @test tm'*v == diag.*v + end + + sz = (3,2) + diag = rand(sz...) + tm = DiagonalTensor(diag) + + v = rand(sz...) + LazyTensors.apply(tm,v, 2,1) + @test (@ballocated LazyTensors.apply($tm,$v, 2,1)) == 0 +end + + +@testset "DenseTensor" begin + # Test a standard matrix-vector product + # mapping vectors of size 4 to vectors of size 3. + A = rand(3,4) + à = DenseTensor(A, (1,), (2,)) + v = rand(4) + w = rand(3) + + @test à isa DenseTensor{T,1,1} where T + @test à isa LazyTensor{T,1,1} where T + @test range_size(Ã) == (3,) + @test domain_size(Ã) == (4,) + + @test Ã*ones(4) ≈ A*ones(4) atol=5e-13 + @test Ã*v ≈ A*v atol=5e-13 + @test Ã'*w ≈ A'*w + + A = rand(2,3,4) + @test_throws DomainError DenseTensor(A, (3,1), (2,)) + + # Test more exotic mappings + B = rand(3,4,2) + # Map vectors of size 2 to matrices of size (3,4) + B̃ = DenseTensor(B, (1,2), (3,)) + v = rand(2) + + @test range_size(B̃) == (3,4) + @test domain_size(B̃) == (2,) + @test B̃ isa LazyTensor{T,2,1} where T + @test B̃*ones(2) ≈ B[:,:,1] + B[:,:,2] atol=5e-13 + @test B̃*v ≈ B[:,:,1]*v[1] + B[:,:,2]*v[2] atol=5e-13 + + # Map matrices of size (3,2) to vectors of size 4 + B̃ = DenseTensor(B, (2,), (1,3)) + v = rand(3,2) + + @test range_size(B̃) == (4,) + @test domain_size(B̃) == (3,2) + @test B̃ isa LazyTensor{T,1,2} where T + @test B̃*ones(3,2) ≈ B[1,:,1] + B[2,:,1] + B[3,:,1] + + B[1,:,2] + B[2,:,2] + B[3,:,2] atol=5e-13 + @test B̃*v ≈ B[1,:,1]*v[1,1] + B[2,:,1]*v[2,1] + B[3,:,1]*v[3,1] + + B[1,:,2]v[1,2] + B[2,:,2]*v[2,2] + B[3,:,2]*v[3,2] atol=5e-13 + + + # TODO: + # @inferred (B̃*v)[2] +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/LazyTensors/tuple_manipulation_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,83 @@ +using Test +using Diffinitive.LazyTensors + +@testset "split_index" begin + @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(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)) + + 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 "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,)) + + 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)) + + 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 "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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/Manifest.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,264 +1,504 @@ # This file is machine-generated - editing it directly is not advised -julia_version = "1.7.0" +julia_version = "1.11.2" manifest_format = "2.0" +project_hash = "b23d6f58220d898029330f7adcac8132668171ec" + +[[deps.Aqua]] +deps = ["Compat", "Pkg", "Test"] +git-tree-sha1 = "49b1d7a9870c87ba13dc63f8ccfcf578cb266f95" +uuid = "4c88cf16-eb10-579e-8560-4a9242c79595" +version = "0.8.9" [[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.ChainRulesCore]] -deps = ["Compat", "LinearAlgebra", "SparseArrays"] -git-tree-sha1 = "4c26b4e9e91ca528ea212927326ece5918a04b47" -uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" -version = "1.11.2" +[[deps.BenchmarkTools]] +deps = ["Compat", "JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] +git-tree-sha1 = "e38fbc49a620f5d0b660d7f543db1009fe0f8336" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.6.0" -[[deps.ChangesOfVariables]] -deps = ["ChainRulesCore", "LinearAlgebra", "Test"] -git-tree-sha1 = "bf98fa45a0a4cee295de98d4c1462be26345b9a1" -uuid = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" -version = "0.1.2" +[[deps.CodeTracking]] +deps = ["InteractiveUtils", "UUIDs"] +git-tree-sha1 = "7eee164f122511d3e4e1ebadb7956939ea7e1c77" +uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" +version = "1.3.6" [[deps.Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582" +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "8ae8d32e09f0dcf42a36b90d4e17f5dd2e4c4215" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.41.0" +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.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.DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - [[deps.DiffRules]] -deps = ["LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "9bc5dac3c8b6706b58ad5ce24cffd9861f07c94f" +deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] +git-tree-sha1 = "23163d55f885173722d1e4cf0f6110cdbaf7e272" uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.9.0" +version = "1.15.1" [[deps.Distributed]] deps = ["Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" +version = "1.11.0" [[deps.DocStringExtensions]] deps = ["LibGit2"] -git-tree-sha1 = "b19534d1895d702889b219c382a6e18010797f0b" +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.6" +version = "0.9.3" [[deps.Downloads]] -deps = ["ArgTools", "LibCURL", "NetworkOptions"] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" [[deps.Glob]] -git-tree-sha1 = "4df9f7e06108728ebf00a0a11edee4b29a482bb2" +git-tree-sha1 = "97285bbd5230dd766e9ef6749b80fc617126d496" uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" -version = "1.3.0" +version = "1.3.1" [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" - -[[deps.InverseFunctions]] -deps = ["Test"] -git-tree-sha1 = "a7254c0acd8e62f1ac75ad24d5db43f5f19f3c65" -uuid = "3587e190-3f89-42d0-90ee-14403ec27112" -version = "0.1.2" +version = "1.11.0" [[deps.IrrationalConstants]] -git-tree-sha1 = "7fd44fd4ff43fc60815f8e764c0f352b83c49151" +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" -version = "0.1.1" +version = "0.2.2" + +[[deps.JET]] +deps = ["CodeTracking", "InteractiveUtils", "JuliaInterpreter", "LoweredCodeUtils", "MacroTools", "Pkg", "PrecompileTools", "Preferences", "Test"] +git-tree-sha1 = "5c5ac91e775b585864015c5c1703cee283071a47" +uuid = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" +version = "0.9.12" + + [deps.JET.extensions] + JETCthulhuExt = "Cthulhu" + ReviseExt = "Revise" + + [deps.JET.weakdeps] + Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" + Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" [[deps.JLLWrappers]] -deps = ["Preferences"] -git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6" uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" -version = "1.3.0" +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.JuliaInterpreter]] +deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] +git-tree-sha1 = "10da5154188682e5c0726823c2b5125957ec3778" +uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" +version = "0.9.38" + +[[deps.LRUCache]] +git-tree-sha1 = "b3cc6698599b10e652832c2f23db3cab99d51b59" +uuid = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" +version = "1.6.1" +weakdeps = ["Serialization"] + + [deps.LRUCache.extensions] + SerializationExt = ["Serialization"] [[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", "NetworkOptions", "Printf", "SHA"] +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.LinearAlgebra]] -deps = ["Libdl", "libblastrampoline_jll"] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.11.0" [[deps.LogExpFunctions]] -deps = ["ChainRulesCore", "ChangesOfVariables", "DocStringExtensions", "InverseFunctions", "IrrationalConstants", "LinearAlgebra"] -git-tree-sha1 = "e5718a00af0ab9756305a0392832c8952c7426c1" +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" -version = "0.3.6" +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 = ["JuliaInterpreter"] +git-tree-sha1 = "688d6d9e098109051ae33d126fcfc88c4ce4a021" +uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" +version = "3.1.0" + +[[deps.MacroTools]] +git-tree-sha1 = "72aebe0b5051e5143a079a4685a46da330a40472" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.15" [[deps.Markdown]] deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+0" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.12.12" [[deps.NaNMath]] -git-tree-sha1 = "f755f36b19a5116bb580de457cda0c140153f283" +deps = ["OpenLibm_jll"] +git-tree-sha1 = "030ea22804ef91648f29b7ad3fc15fa49d0e6e71" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "0.3.6" +version = "1.0.3" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.27+1" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.1+2" [[deps.OpenSpecFun_jll]] -deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "13652491f6856acfd2db29360e1bbcd4565d04f1" +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" -version = "0.5.5+0" +version = "0.5.6+0" + +[[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 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" [[deps.Pkg]] -deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +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" + + [deps.Pkg.extensions] + REPLExt = "REPL" + + [deps.Pkg.weakdeps] + REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[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 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a" +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" uuid = "21216c6a-2e73-6563-6e65-726566657250" -version = "1.2.2" +version = "1.4.3" [[deps.Printf]] deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" -[[deps.REPL]] -deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] -uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +[[deps.Profile]] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.PtrArrays]] +git-tree-sha1 = "77a42d78b6a92df47ab37e177b2deac405e1c88f" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.2.1" [[deps.Random]] -deps = ["SHA", "Serialization"] +deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" [[deps.Requires]] deps = ["UUIDs"] -git-tree-sha1 = "8f82019e525f4d5c669692772a6f4b0a58b06a6a" +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" uuid = "ae029012-a4dd-5104-9daa-d747884805df" -version = "1.2.0" +version = "1.3.0" [[deps.SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" - -[[deps.SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" +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 = "77769a2a6fefd92fc7c0367c0771a2a9d2b6f8e1" +uuid = "a9a3c162-d163-4c15-8926-b8794fbefed2" +version = "0.4.0" +weakdeps = ["SparseArrays"] + + [deps.SparseArrayKit.extensions] + SparseArrayKitSparseArrays = "SparseArrays" [[deps.SparseArrays]] -deps = ["LinearAlgebra", "Random"] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.11.0" [[deps.SpecialFunctions]] -deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "e08890d19787ec25029113e88c34ec20cac1c91e" +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "64cca0c26b4f31ba18f13f6c12af7c85f478cfde" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "2.0.0" +version = "2.5.0" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "47091a0340a675c738b1304b58161f3b0839d454" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.10" + + [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 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" [[deps.Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] +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 = "f9ce8284e6eec72a21de3603493eb5355fcf7f39" +uuid = "5e0ebb24-38b0-5f93-81fe-25c709ecae67" +version = "2.2.0" + +[[deps.StridedViews]] +deps = ["LinearAlgebra", "PackageExtensionCompat"] +git-tree-sha1 = "b60baf1998bcdccc57e1cc2c6703df1f619a3754" +uuid = "4db3bf67-4bd7-4b4e-b153-31dc3fb37143" +version = "0.3.2" + + [deps.StridedViews.extensions] + StridedViewsCUDAExt = "CUDA" + + [deps.StridedViews.weakdeps] + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + +[[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.Tar]] deps = ["ArgTools", "SHA"] uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TensorOperations]] +deps = ["LRUCache", "LinearAlgebra", "PackageExtensionCompat", "PtrArrays", "Strided", "StridedViews", "TupleTools", "VectorInterface"] +git-tree-sha1 = "d08a24e2cb67aa0cbfcd68d0acdf2879e571126f" +uuid = "6aa20fa7-93e2-5fca-9bc0-fbd0db3c71a2" +version = "5.1.3" + + [deps.TensorOperations.extensions] + TensorOperationsBumperExt = "Bumper" + TensorOperationsChainRulesCoreExt = "ChainRulesCore" + TensorOperationscuTENSORExt = ["cuTENSOR", "CUDA"] + + [deps.TensorOperations.weakdeps] + Bumper = "8ce10254-0962-460f-a3d8-1f77fea1446e" + CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + cuTENSOR = "011b41b2-24ef-40a8-b3eb-fa098493e9e1" [[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 = "3a2919a78b04c29a1a57b05e1618e473162b15d0" +git-tree-sha1 = "ccebd99935be339d2ad907589708ba1f0d62bab3" uuid = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" -version = "2.0.0" +version = "3.0.0" + +[[deps.Tokens]] +deps = ["SparseArrayKit", "SparseArrays"] +git-tree-sha1 = "c4f40125383ce3bfcfcd49a1b206080b7afd9a34" +uuid = "040c2ec2-8d69-4aca-bf03-7d3a7092f2f6" +version = "0.1.1" [[deps.Tullio]] -deps = ["ChainRulesCore", "DiffRules", "LinearAlgebra", "Requires"] -git-tree-sha1 = "0288b7a395fc412952baf756fac94e4f28bfec65" +deps = ["DiffRules", "LinearAlgebra", "Requires"] +git-tree-sha1 = "6d476962ba4e435d7f4101a403b1d3d72afe72f3" uuid = "bc48ee85-29a4-5162-ae0b-a64e1601d4bc" -version = "0.3.2" +version = "0.3.7" + + [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 = "cea8abaa6e43f72f97a09cf95b80c9eb53ff75cf" +uuid = "409d34a3-91d5-4945-b6ec-7529ddf182d8" +version = "0.4.9" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" [[deps.libblastrampoline_jll]] -deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] +deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.59.0+0" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2"
--- a/test/Project.toml Fri Jan 21 15:23:08 2022 +0100 +++ b/test/Project.toml Sun Jan 12 21:18:44 2025 +0100 @@ -1,7 +1,14 @@ [deps] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" 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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/RegionIndices/RegionIndices_test.jl Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/boundaryops/boundary_operator_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,131 +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((0,2), (2.,1.,3.)) - g_1D = EquidistantGrid(11, 0.0, 1.0) - g_2D = EquidistantGrid((11,15), (0.0, 0.0), (1.0,1.0)) + closure_stencil = Stencil(2.,1.,3.; center = 1) + 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 TensorMapping{T,0,1} where T - - 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 TensorMapping{T,0,1} where T - end - - @testset "2D" begin - e_w = boundary_operator(g_2D,closure_stencil,CartesianBoundary{1,Upper}()) - @test e_w isa InflatedTensorMapping - @test e_w isa TensorMapping{T,1,2} where T - end + @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_l = boundary_operator(g_1D, closure_stencil, CartesianBoundary{1,Lower}()) - op_r = boundary_operator(g_1D, closure_stencil, CartesianBoundary{1,Upper}()) - - op_w = boundary_operator(g_2D, closure_stencil, CartesianBoundary{1,Lower}()) - op_e = boundary_operator(g_2D, closure_stencil, CartesianBoundary{1,Upper}()) - op_s = boundary_operator(g_2D, closure_stencil, CartesianBoundary{2,Lower}()) - op_n = boundary_operator(g_2D, closure_stencil, CartesianBoundary{2,Upper}()) + 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[]] - end + 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[]] - @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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/boundaryops/boundary_restriction_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,46 +1,41 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.RegionIndices -using Sbplib.LazyTensors - -import Sbplib.SbpOperators.BoundaryOperator +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,Lower()) - @test e_l == boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Lower}()) - @test e_l == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Lower()) - @test e_l isa BoundaryOperator{T,Lower} where T - @test e_l isa TensorMapping{T,0,1} where T + e_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,Upper()) - @test e_r == boundary_restriction(g_1D,e_closure,CartesianBoundary{1,Upper}()) - @test e_r == BoundaryOperator(g_1D,Stencil{Float64}(e_closure),Upper()) - @test e_r isa BoundaryOperator{T,Upper} where T - @test e_r isa TensorMapping{T,0,1} where T + 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 isa InflatedTensorMapping - @test e_w isa TensorMapping{T,1,2} where T + 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 end @testset "Application" begin @testset "1D" begin - e_l = boundary_restriction(g_1D, e_closure, CartesianBoundary{1,Lower}()) - e_r = boundary_restriction(g_1D, e_closure, CartesianBoundary{1,Upper}()) - - v = evalOn(g_1D,x->1+x^2) + 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] @@ -49,11 +44,7 @@ end @testset "2D" begin - e_w = boundary_restriction(g_2D, e_closure, CartesianBoundary{1,Lower}()) - e_e = boundary_restriction(g_2D, e_closure, CartesianBoundary{1,Upper}()) - e_s = boundary_restriction(g_2D, e_closure, CartesianBoundary{2,Lower}()) - e_n = boundary_restriction(g_2D, e_closure, CartesianBoundary{2,Upper}()) - + 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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/boundaryops/normal_derivative_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,68 +1,59 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.RegionIndices -using Sbplib.LazyTensors - -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, Lower()) - @test d_l == normal_derivative(g_1D, d_closure, CartesianBoundary{1,Lower}()) - @test d_l isa BoundaryOperator{T,Lower} where T - @test d_l isa TensorMapping{T,0,1} where T + 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}()) - Ix = IdentityMapping{Float64}((size(g_2D)[1],)) - Iy = IdentityMapping{Float64}((size(g_2D)[2],)) - d_l = normal_derivative(restrict(g_2D,1),d_closure,Lower()) - d_r = normal_derivative(restrict(g_2D,2),d_closure,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(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 TensorMapping{T,1,2} where T - @test d_n isa TensorMapping{T,1,2} where T + @test d_w isa LazyTensor{T,1,2} where T + @test d_n isa LazyTensor{T,1,2} where T 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 = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}()) - d_e = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Upper}()) - d_s = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Lower}()) - d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}()) + 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 - @test d_s*v ≈ v∂y[:,1] atol = 1e-13 - @test d_n*v ≈ -v∂y[:,end] atol = 1e-13 + @test d_w*v ≈ -v∂x[1,:] atol = 1e-13 + @test d_e*v ≈ v∂x[end,:] atol = 1e-13 + @test d_s*v ≈ -v∂y[:,1] atol = 1e-13 + @test d_n*v ≈ v∂y[:,end] atol = 1e-13 end @testset "4th 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 = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Lower}()) - d_e = normal_derivative(g_2D, d_closure, CartesianBoundary{1,Upper}()) - d_s = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Lower}()) - d_n = normal_derivative(g_2D, d_closure, CartesianBoundary{2,Upper}()) - - @test d_w*v ≈ v∂x[1,:] atol = 1e-13 - @test d_e*v ≈ -v∂x[end,:] atol = 1e-13 - @test d_s*v ≈ v∂y[:,1] atol = 1e-13 - @test d_n*v ≈ -v∂y[:,end] atol = 1e-13 + stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) + 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 + @test d_s*v ≈ -v∂y[:,1] atol = 1e-13 + @test d_n*v ≈ v∂y[:,end] atol = 1e-13 end end end
--- a/test/SbpOperators/readoperator_test.jl Fri Jan 21 15:23:08 2022 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -using Test - -using TOML -using Sbplib.SbpOperators - -import Sbplib.SbpOperators.Stencil - -@testset "readoperator" begin - toml_str = """ - [meta] - authors = "Ken Mattson" - description = "Standard operators for equidistant grids" - type = "equidistant" - cite = "A paper a long time ago in a galaxy far far away." - - [[stencil_set]] - - order = 2 - test = 2 - - H.inner = ["1"] - H.closure = ["1/2"] - - D1.inner_stencil = ["-1/2", "0", "1/2"] - D1.closure_stencils = [ - {s = ["-1", "1"], c = 1}, - ] - - D2.inner_stencil = ["1", "-2", "1"] - D2.closure_stencils = [ - {s = ["1", "-2", "1"], c = 1}, - ] - - e.closure = ["1"] - d1.closure = {s = ["-3/2", "2", "-1/2"], c = 1} - - [[stencil_set]] - - order = 4 - test = 1 - H.inner = ["1"] - H.closure = ["17/48", "59/48", "43/48", "49/48"] - - D2.inner_stencil = ["-1/12","4/3","-5/2","4/3","-1/12"] - D2.closure_stencils = [ - {s = [ "2", "-5", "4", "-1", "0", "0"], c = 1}, - {s = [ "1", "-2", "1", "0", "0", "0"], c = 2}, - {s = [ "-4/43", "59/43", "-110/43", "59/43", "-4/43", "0"], c = 3}, - {s = [ "-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"], c = 4}, - ] - - e.closure = ["1"] - d1.closure = {s = ["-11/6", "3", "-3/2", "1/3"], c = 1} - - [[stencil_set]] - order = 4 - test = 2 - - H.closure = ["-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"] - """ - - parsed_toml = TOML.parse(toml_str) - - @testset "get_stencil_set" begin - @test get_stencil_set(parsed_toml; order = 2) isa Dict - @test get_stencil_set(parsed_toml; order = 2) == parsed_toml["stencil_set"][1] - @test get_stencil_set(parsed_toml; test = 1) == parsed_toml["stencil_set"][2] - @test get_stencil_set(parsed_toml; order = 4, test = 2) == parsed_toml["stencil_set"][3] - - @test_throws ArgumentError get_stencil_set(parsed_toml; test = 2) - @test_throws ArgumentError get_stencil_set(parsed_toml; order = 4) - end - - @testset "parse_stencil" begin - toml = """ - s1 = ["-1/12","4/3","-5/2","4/3","-1/12"] - s2 = {s = ["2", "-5", "4", "-1", "0", "0"], c = 1} - s3 = {s = ["1", "-2", "1", "0", "0", "0"], c = 2} - s4 = "not a stencil" - s5 = [-1, 4, 3] - s6 = {k = ["1", "-2", "1", "0", "0", "0"], c = 2} - s7 = {s = [-1, 4, 3], c = 2} - s8 = {s = ["1", "-2", "1", "0", "0", "0"], c = [2,2]} - """ - - @test parse_stencil(TOML.parse(toml)["s1"]) == CenteredStencil(-1//12, 4//3, -5//2, 4//3, -1//12) - @test parse_stencil(TOML.parse(toml)["s2"]) == Stencil(2//1, -5//1, 4//1, -1//1, 0//1, 0//1; center=1) - @test parse_stencil(TOML.parse(toml)["s3"]) == Stencil(1//1, -2//1, 1//1, 0//1, 0//1, 0//1; center=2) - - @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s4"]) - @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s5"]) - @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s6"]) - @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s7"]) - @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s8"]) - - stencil_set = get_stencil_set(parsed_toml; order = 4, test = 1) - - @test parse_stencil.(stencil_set["D2"]["closure_stencils"]) == [ - Stencil( 2//1, -5//1, 4//1, -1//1, 0//1, 0//1; center=1), - Stencil( 1//1, -2//1, 1//1, 0//1, 0//1, 0//1; center=2), - Stencil(-4//43, 59//43, -110//43, 59//43, -4//43, 0//1; center=3), - Stencil(-1//49, 0//1, 59//49, -118//49, 64//49, -4//49; center=4), - ] - - - @test parse_stencil(Float64, TOML.parse(toml)["s1"]) == CenteredStencil(-1/12, 4/3, -5/2, 4/3, -1/12) - @test parse_stencil(Float64, TOML.parse(toml)["s2"]) == Stencil(2/1, -5/1, 4/1, -1/1, 0/1, 0/1; center=1) - @test parse_stencil(Float64, TOML.parse(toml)["s3"]) == Stencil(1/1, -2/1, 1/1, 0/1, 0/1, 0/1; center=2) - end - - @testset "parse_scalar" begin - toml = TOML.parse(""" - a1 = 1 - a2 = 1.5 - a3 = 1.0 - a4 = 10 - a5 = "1/2" - a6 = "1.5" - - e1 = [1,2,3] - e2 = "a string value" - """) - - @test parse_scalar(toml["a1"]) == 1//1 - @test parse_scalar(toml["a2"]) == 3//2 - @test parse_scalar(toml["a3"]) == 1//1 - @test parse_scalar(toml["a4"]) == 10//1 - @test parse_scalar(toml["a5"]) == 1//2 - @test parse_scalar(toml["a6"]) == 3//2 - - @test_throws ArgumentError parse_scalar(toml["e1"]) - @test_throws ArgumentError parse_scalar(toml["e2"]) - end - - @testset "parse_tuple" begin - toml = TOML.parse(""" - t1 = [1,3,4] - t2 = ["1/2","3/4","2/1"] - - e1 = "not a tuple" - e2.a="1" - e3 = 1 - e4 = ["1/2","3/4","not a number"] - """) - - @test parse_tuple(toml["t1"]) == (1//1,3//1,4//1) - @test parse_tuple(toml["t2"]) == (1//2,3//4,2//1) - - @test_throws ArgumentError parse_tuple(toml["e1"]) - @test_throws ArgumentError parse_tuple(toml["e2"]) - @test_throws ArgumentError parse_tuple(toml["e3"]) - @test_throws ArgumentError parse_tuple(toml["e4"]) - end -end - -@testset "parse_rational" begin - @test SbpOperators.parse_rational("1") isa Rational - @test SbpOperators.parse_rational("1") == 1//1 - @test SbpOperators.parse_rational("1/2") isa Rational - @test SbpOperators.parse_rational("1/2") == 1//2 - @test SbpOperators.parse_rational("37/13") isa Rational - @test SbpOperators.parse_rational("37/13") == 37//13 - - @test SbpOperators.parse_rational(0.5) isa Rational - @test SbpOperators.parse_rational(0.5) == 1//2 - - @test SbpOperators.parse_rational("0.5") isa Rational - @test SbpOperators.parse_rational("0.5") == 1//2 - - @test SbpOperators.parse_rational(2) isa Rational - @test SbpOperators.parse_rational(2) == 2//1 -end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/stencil_set_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,188 @@ +using Test + +using TOML +using Diffinitive.SbpOperators + +import Diffinitive.SbpOperators.Stencil +import Diffinitive.SbpOperators.NestedStencil + +@testset "readoperator" begin + toml_str = """ + [meta] + authors = "Ken Mattson" + description = "Standard operators for equidistant grids" + type = "equidistant" + cite = "A paper a long time ago in a galaxy far far away." + + [[stencil_set]] + + order = 2 + test = 2 + + H.inner = ["1"] + H.closure = ["1/2"] + + D1.inner_stencil = ["-1/2", "0", "1/2"] + D1.closure_stencils = [ + {s = ["-1", "1"], c = 1}, + ] + + D2.inner_stencil = ["1", "-2", "1"] + D2.closure_stencils = [ + {s = ["1", "-2", "1"], c = 1}, + ] + + e.closure = ["1"] + d1.closure = {s = ["-3/2", "2", "-1/2"], c = 1} + + [[stencil_set]] + + order = 4 + test = 1 + H.inner = ["1"] + H.closure = ["17/48", "59/48", "43/48", "49/48"] + + D2.inner_stencil = ["-1/12","4/3","-5/2","4/3","-1/12"] + D2.closure_stencils = [ + {s = [ "2", "-5", "4", "-1", "0", "0"], c = 1}, + {s = [ "1", "-2", "1", "0", "0", "0"], c = 2}, + {s = [ "-4/43", "59/43", "-110/43", "59/43", "-4/43", "0"], c = 3}, + {s = [ "-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"], c = 4}, + ] + + e.closure = ["1"] + d1.closure = {s = ["-11/6", "3", "-3/2", "1/3"], c = 1} + + [[stencil_set]] + order = 4 + test = 2 + + H.closure = ["-1/49", "0", "59/49", "-118/49", "64/49", "-4/49"] + """ + + parsed_toml = TOML.parse(toml_str) + + @testset "get_stencil_set" begin + @test get_stencil_set(parsed_toml; order = 2) isa Dict + @test get_stencil_set(parsed_toml; order = 2) == parsed_toml["stencil_set"][1] + @test get_stencil_set(parsed_toml; test = 1) == parsed_toml["stencil_set"][2] + @test get_stencil_set(parsed_toml; order = 4, test = 2) == parsed_toml["stencil_set"][3] + + @test_throws ArgumentError get_stencil_set(parsed_toml; test = 2) + @test_throws ArgumentError get_stencil_set(parsed_toml; order = 4) + end + + @testset "parse_stencil" begin + toml = """ + s1 = ["-1/12","4/3","-5/2","4/3","-1/12"] + s2 = {s = ["2", "-5", "4", "-1", "0", "0"], c = 1} + s3 = {s = ["1", "-2", "1", "0", "0", "0"], c = 2} + s4 = "not a stencil" + s5 = [-1, 4, 3] + s6 = {k = ["1", "-2", "1", "0", "0", "0"], c = 2} + s7 = {s = [-1, 4, 3], c = 2} + s8 = {s = ["1", "-2", "1", "0", "0", "0"], c = [2,2]} + """ + + @test parse_stencil(TOML.parse(toml)["s1"]) == CenteredStencil(-1//12, 4//3, -5//2, 4//3, -1//12) + @test parse_stencil(TOML.parse(toml)["s2"]) == Stencil(2//1, -5//1, 4//1, -1//1, 0//1, 0//1; center=1) + @test parse_stencil(TOML.parse(toml)["s3"]) == Stencil(1//1, -2//1, 1//1, 0//1, 0//1, 0//1; center=2) + + @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s4"]) + @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s5"]) + @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s6"]) + @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s7"]) + @test_throws ArgumentError parse_stencil(TOML.parse(toml)["s8"]) + + stencil_set = get_stencil_set(parsed_toml; order = 4, test = 1) + + @test parse_stencil.(stencil_set["D2"]["closure_stencils"]) == [ + Stencil( 2//1, -5//1, 4//1, -1//1, 0//1, 0//1; center=1), + Stencil( 1//1, -2//1, 1//1, 0//1, 0//1, 0//1; center=2), + Stencil(-4//43, 59//43, -110//43, 59//43, -4//43, 0//1; center=3), + Stencil(-1//49, 0//1, 59//49, -118//49, 64//49, -4//49; center=4), + ] + + + @test parse_stencil(Float64, TOML.parse(toml)["s1"]) == CenteredStencil(-1/12, 4/3, -5/2, 4/3, -1/12) + @test parse_stencil(Float64, TOML.parse(toml)["s2"]) == Stencil(2/1, -5/1, 4/1, -1/1, 0/1, 0/1; center=1) + @test parse_stencil(Float64, TOML.parse(toml)["s3"]) == Stencil(1/1, -2/1, 1/1, 0/1, 0/1, 0/1; center=2) + end + + @testset "parse_scalar" begin + toml = TOML.parse(""" + a1 = 1 + a2 = 1.5 + a3 = 1.0 + a4 = 10 + a5 = "1/2" + a6 = "1.5" + + e1 = [1,2,3] + e2 = "a string value" + """) + + @test parse_scalar(toml["a1"]) == 1//1 + @test parse_scalar(toml["a2"]) == 3//2 + @test parse_scalar(toml["a3"]) == 1//1 + @test parse_scalar(toml["a4"]) == 10//1 + @test parse_scalar(toml["a5"]) == 1//2 + @test parse_scalar(toml["a6"]) == 3//2 + + @test_throws ArgumentError parse_scalar(toml["e1"]) + @test_throws ArgumentError parse_scalar(toml["e2"]) + end + + @testset "parse_tuple" begin + toml = TOML.parse(""" + t1 = [1,3,4] + t2 = ["1/2","3/4","2/1"] + + e1 = "not a tuple" + e2.a="1" + e3 = 1 + e4 = ["1/2","3/4","not a number"] + """) + + @test parse_tuple(toml["t1"]) == (1//1,3//1,4//1) + @test parse_tuple(toml["t2"]) == (1//2,3//4,2//1) + + @test_throws ArgumentError parse_tuple(toml["e1"]) + @test_throws ArgumentError parse_tuple(toml["e2"]) + @test_throws ArgumentError parse_tuple(toml["e3"]) + @test_throws ArgumentError parse_tuple(toml["e4"]) + end +end + +@testset "parse_rational" begin + @test SbpOperators.parse_rational("1") isa Rational + @test SbpOperators.parse_rational("1") == 1//1 + @test SbpOperators.parse_rational("1/2") isa Rational + @test SbpOperators.parse_rational("1/2") == 1//2 + @test SbpOperators.parse_rational("37/13") isa Rational + @test SbpOperators.parse_rational("37/13") == 37//13 + + @test SbpOperators.parse_rational(0.5) isa Rational + @test SbpOperators.parse_rational(0.5) == 1//2 + + @test SbpOperators.parse_rational("0.5") isa Rational + @test SbpOperators.parse_rational("0.5") == 1//2 + + @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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/stencil_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,19 +1,27 @@ using Test -using Sbplib.SbpOperators -import Sbplib.SbpOperators.Stencil +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.)) + s = Stencil(-2:2, (1.,2.,2.,3.,4.)) @test s isa Stencil{Float64, 5} @test eltype(s) == Float64 - @test SbpOperators.scale(s, 2) == Stencil((-2,2), (2.,4.,4.,6.,8.)) + + @test length(s) == 5 + @test length(Stencil(-1:2, (1,2,3,4))) == 4 + + @test SbpOperators.scale(s, 2) == Stencil(-2:2, (2.,4.,4.,6.,8.)) - @test Stencil(1,2,3,4; center=1) == Stencil((0, 3),(1,2,3,4)) - @test Stencil(1,2,3,4; center=2) == Stencil((-1, 2),(1,2,3,4)) - @test Stencil(1,2,3,4; center=4) == Stencil((-3, 0),(1,2,3,4)) + @test Stencil(1,2,3,4; center=1) == Stencil(0:3,(1,2,3,4)) + @test Stencil(1,2,3,4; center=2) == Stencil(-1:2,(1,2,3,4)) + @test Stencil(1,2,3,4; center=4) == Stencil(-3:0,(1,2,3,4)) - @test CenteredStencil(1,2,3,4,5) == Stencil((-2, 2), (1,2,3,4,5)) + @test CenteredStencil(1,2,3,4,5) == Stencil(-2:2, (1,2,3,4,5)) @test_throws ArgumentError CenteredStencil(1,2,3,4) # Changing the type of the weights @@ -24,8 +32,228 @@ @testset "convert" begin @test convert(Stencil{Float64}, Stencil(1,2,3,4,5; center=2)) == Stencil(1.,2.,3.,4.,5.; center=2) - @test convert(Stencil{Float64}, CenteredStencil(1,2,3,4,5)) == CenteredStencil(1.,2.,3.,4.,5.) - @test convert(Stencil{Int}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1,2,3,4,5; center=2) - @test convert(Stencil{Rational}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1//1,2//1,3//1,4//1,5//1; center=2) + @test convert(Stencil{Float64,5}, CenteredStencil(1,2,3,4,5)) == CenteredStencil(1.,2.,3.,4.,5.) + @test convert(Stencil{Int,5}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1,2,3,4,5; center=2) + @test convert(Stencil{Rational,5}, Stencil(1.,2.,3.,4.,5.; center=2)) == Stencil(1//1,2//1,3//1,4//1,5//1; center=2) + end + + @testset "promotion of weights" begin + @test Stencil(1.,2; center = 1) isa Stencil{Float64, 2} + @test Stencil(1,2//2; center = 1) isa Stencil{Rational{Int64}, 2} + end + + @testset "promotion" begin + @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.) + v_int = rand(1:10,10); + v_float = rand(10); + + @inferred SbpOperators.apply_stencil(s_int, v_int, 2) + @inferred SbpOperators.apply_stencil(s_float, v_float, 2) + @inferred SbpOperators.apply_stencil(s_int, v_float, 2) + @inferred SbpOperators.apply_stencil(s_float, v_int, 2) + + @inferred SbpOperators.apply_stencil_backwards(s_int, v_int, 5) + @inferred SbpOperators.apply_stencil_backwards(s_float, v_float, 5) + @inferred SbpOperators.apply_stencil_backwards(s_int, v_float, 5) + @inferred SbpOperators.apply_stencil_backwards(s_float, v_int, 5) 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 + s1 = CenteredStencil(-1, 1, 0) + s2 = CenteredStencil(-1, 0, 1) + s3 = CenteredStencil( 0,-1, 1) + + ns = NestedStencil(CenteredStencil(s1,s2,s3)) + @test ns isa NestedStencil{Int,3} + + @test CenteredNestedStencil(s1,s2,s3) == ns + + @test NestedStencil(s1,s2,s3, center = 2) == ns + @test NestedStencil(s1,s2,s3, center = 1) == NestedStencil(Stencil(s1,s2,s3, center=1)) + + @test NestedStencil((-1,1,0),(-1,0,1),(0,-1,1), center=2) == ns + @test CenteredNestedStencil((-1,1,0),(-1,0,1),(0,-1,1)) == ns + @test NestedStencil((-1,1,0),(-1,0,1),(0,-1,1), center=1) == NestedStencil(Stencil( + Stencil(-1, 1, 0; center=1), + Stencil(-1, 0, 1; center=1), + Stencil( 0,-1, 1; center=1); + center=1 + )) + + @testset "Error handling" begin + end + end + + @testset "scale" begin + ns = NestedStencil((-1,1,0),(-1,0,1),(0,-1,1), center=2) + @test SbpOperators.scale(ns, 2) == NestedStencil((-2,2,0),(-2,0,2),(0,-2,2), center=2) + end + + @testset "conversion" begin + ns = NestedStencil((-1,1,0),(-1,0,1),(0,-1,1), center=2) + @test NestedStencil{Float64}(ns) == NestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-1.,1.), center=2) + @test NestedStencil{Rational}(ns) == NestedStencil((-1//1,1//1,0//1),(-1//1,0//1,1//1),(0//1,-1//1,1//1), center=2) + + @test convert(NestedStencil{Float64}, ns) == NestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-1.,1.), center=2) + @test convert(NestedStencil{Rational}, ns) == NestedStencil((-1//1,1//1,0//1),(-1//1,0//1,1//1),(0//1,-1//1,1//1), center=2) + end + + @testset "promotion of weights" begin + @test NestedStencil((-1,1,0),(-1.,0.,1.),(0,-1,1), center=2) isa NestedStencil{Float64,3,3} + @test NestedStencil((-1,1,0),(-1,0,1),(0//1,-1,1), center=2) isa NestedStencil{Rational{Int64},3,3} + end + + @testset "promotion" begin + promote( + CenteredNestedStencil((-1,1,0),(-1,0,1),(0,-1,1)), + CenteredNestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-1.,1.)) + ) == ( + CenteredNestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-1.,1.)), + CenteredNestedStencil((-1.,1.,0.),(-1.,0.,1.),(0.,-1.,1.)) + ) + end + + @testset "apply" begin + c = [ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55] + v = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + + # Centered + ns = NestedStencil((-1,1,0),(-1,0,1),(0,-2,2), center=2) + @test SbpOperators.apply_inner_stencils(ns, c, 4) == Stencil(4,9,10; center=2) + @test SbpOperators.apply_inner_stencils_backwards(ns, c, 4) == Stencil(-5,-9,-8; center=2) + + @test SbpOperators.apply_stencil(ns, c, v, 4) == 4*5 + 9*7 + 10*11 + @test SbpOperators.apply_stencil_backwards(ns, c, v, 4) == -8*5 - 9*7 - 5*11 + + # Non-centered + ns = NestedStencil((-1,1,0),(-1,0,1),(0,-1,1), center=1) + @test SbpOperators.apply_inner_stencils(ns, c, 4) == Stencil(5,11,6; center=1) + @test SbpOperators.apply_inner_stencils_backwards(ns, c, 4) == Stencil(-4,-7,-3; center=1) + + @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 + s_int = CenteredNestedStencil((1,2,3),(1,2,3),(1,2,3)) + s_float = CenteredNestedStencil((1.,2.,3.),(1.,2.,3.),(1.,2.,3.)) + + v_int = rand(1:10,10); + v_float = rand(10); + + c_int = rand(1:10,10); + c_float = rand(10); + + @inferred SbpOperators.apply_stencil(s_int, c_int, v_int, 2) + @inferred SbpOperators.apply_stencil(s_float, c_int, v_float, 2) + @inferred SbpOperators.apply_stencil(s_int, c_int, v_float, 2) + @inferred SbpOperators.apply_stencil(s_float, c_int, v_int, 2) + + @inferred SbpOperators.apply_stencil(s_int, c_float, v_int, 2) + @inferred SbpOperators.apply_stencil(s_float, c_float, v_float, 2) + @inferred SbpOperators.apply_stencil(s_int, c_float, v_float, 2) + @inferred SbpOperators.apply_stencil(s_float, c_float, v_int, 2) + + @inferred SbpOperators.apply_stencil_backwards(s_int, c_int, v_int, 2) + @inferred SbpOperators.apply_stencil_backwards(s_float, c_int, v_float, 2) + @inferred SbpOperators.apply_stencil_backwards(s_int, c_int, v_float, 2) + @inferred SbpOperators.apply_stencil_backwards(s_float, c_int, v_int, 2) + + @inferred SbpOperators.apply_stencil_backwards(s_int, c_float, v_int, 2) + @inferred SbpOperators.apply_stencil_backwards(s_float, c_float, v_float, 2) + @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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/constant_interior_scaling_operator_test.jl Sun Jan 12 21:18:44 2025 +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} @@ -24,13 +24,16 @@ @test a*v == [.1,.2,.5,.5,.5,.2,.1] @test a'*v == [.1,.2,.5,.5,.5,.2,.1] + @test (a*rand(ComplexF64, domain_size(a)... ))[1] isa ComplexF64 + @test (a'*rand(ComplexF64, domain_size(a')...))[1] isa ComplexF64 + @test range_size(a) == (7,) @test domain_size(a) == (7,) @test_throws DomainError ConstantInteriorScalingOperator(4,(2,3), 3) @testset "Grid constructor" begin - g = EquidistantGrid(11, 0., 2.) + 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 Sun Jan 12 21:18:44 2025 +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/SbpOperators/volumeops/derivatives/first_derivative_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,84 @@ +using Test + + +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors + +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₁ = equidistant_grid(0., 1., 11) + g₂ = equidistant_grid((0.,1.), (1.,3.), 11, 14) + + @test first_derivative(g₁, stencil_set) isa LazyTensor{Float64,1,1} + @test first_derivative(g₂, stencil_set, 2) isa LazyTensor{Float64,2,2} + + interior_stencil = CenteredStencil(-1,0,1) + closure_stencils = [Stencil(-1,1, center=1)] + + @test first_derivative(g₁, interior_stencil, closure_stencils) isa LazyTensor{Float64,1,1} + end + + @testset "Accuracy conditions" begin + N = 20 + 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) + + @testset "boundary x^$k" for k ∈ 0:order÷2 + v = eval_on(g, x->monomial(x,k)) + + @testset for i ∈ 1:closure_size(D₁) + x, = g[i] + @test (D₁*v)[i] == monomial(x,k-1) + end + + @testset for i ∈ (N-closure_size(D₁)+1):N + x, = g[i] + @test (D₁*v)[i] == monomial(x,k-1) + end + end + + @testset "interior x^$k" for k ∈ 0:order + v = eval_on(g, x->monomial(x,k)) + + x, = g[10] + @test (D₁*v)[10] == monomial(x,k-1) + end + end + end + + @testset "Accuracy on function" begin + @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 + end + end + + @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 + end + end + end +end +
--- a/test/SbpOperators/volumeops/derivatives/second_derivative_test.jl Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/derivatives/second_derivative_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,32 +1,32 @@ 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 - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) + 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"]) 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) - @test Dₓₓ == second_derivative(g_1D,inner_stencil,closure_stencils,1) - @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) - I = IdentityMapping{Float64}(size(g_2D)[2]) - @test Dₓₓ == D2⊗I - @test Dₓₓ isa TensorMapping{T,2,2} where T + Dₓₓ = second_derivative(g_2D,stencil_set,1) + @test Dₓₓ isa LazyTensor{Float64,2,2} end end @@ -39,18 +39,16 @@ 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. @testset "2nd order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils) + stencil_set = read_stencil_set(operator_path; order=2) + Dₓₓ = second_derivative(g_1D,stencil_set) @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10 @test Dₓₓ*monomials[2] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10 @test Dₓₓ*monomials[3] ≈ monomials[1] atol = 5e-10 @@ -60,10 +58,8 @@ # 4th order interior stencil, 2nd order boundary stencil, # implies that L*v should be exact for monomials up to order 3. @testset "4th order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - Dₓₓ = second_derivative(g_1D,inner_stencil,closure_stencils) + stencil_set = read_stencil_set(operator_path; order=4) + Dₓₓ = second_derivative(g_1D,stencil_set) # NOTE: high tolerances for checking the "exact" differentiation # due to accumulation of round-off errors/cancellation errors? @test Dₓₓ*monomials[1] ≈ zeros(Float64,size(g_1D)...) atol = 5e-10 @@ -75,42 +71,38 @@ 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. @testset "2nd order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - Dyy = second_derivative(g_2D,inner_stencil,closure_stencils,2) + stencil_set = read_stencil_set(operator_path; order=2) + 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 # 4th order interior stencil, 2nd order boundary stencil, # implies that L*v should be exact for binomials up to order 3. @testset "4th order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - Dyy = second_derivative(g_2D,inner_stencil,closure_stencils,2) + stencil_set = read_stencil_set(operator_path; order=4) + Dyy = second_derivative(g_2D,stencil_set,2) # NOTE: high tolerances for checking the "exact" differentiation # due to accumulation of round-off errors/cancellation errors? @test Dyy*binomials[1] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9 @test Dyy*binomials[2] ≈ zeros(Float64,size(g_2D)...) atol = 5e-9 - @test Dyy*binomials[3] ≈ evalOn(g_2D,(x,y)->1.) atol = 5e-9 - @test Dyy*binomials[4] ≈ evalOn(g_2D,(x,y)->y) atol = 5e-9 + @test Dyy*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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/inner_products/inner_product_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,52 +1,48 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors +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 == IdentityMapping{Float64}() - @test H isa TensorMapping{T,0,0} where T + 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, quadrature_interior, quadrature_closure) - @test H isa TensorMapping{T,1,1} where T + 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) + 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 TensorMapping{T,2,2} where T + @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 @@ -57,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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/inner_products/inverse_inner_product_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,49 +1,44 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors -import Sbplib.SbpOperators.Stencil +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 == IdentityMapping{Float64}() - @test Hi isa TensorMapping{T,0,0} where T + 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 isa TensorMapping{T,1,1} where T + 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) + 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 TensorMapping{T,2,2} where T + @test Hi isa LazyTensor{T,2,2} where T end 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 - 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 @@ -51,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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/laplace/laplace_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,71 +1,153 @@ using Test -using Sbplib.SbpOperators -using Sbplib.Grids -using Sbplib.LazyTensors +using Diffinitive.SbpOperators +using Diffinitive.Grids +using Diffinitive.LazyTensors @testset "Laplace" begin - g_1D = EquidistantGrid(101, 0.0, 1.) - g_3D = EquidistantGrid((51,101,52), (0.0, -1.0, 0.0), (1., 1., 1.)) + # 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 - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) @testset "1D" begin - L = laplace(g_1D, inner_stencil, closure_stencils) - @test L == second_derivative(g_1D, inner_stencil, closure_stencils) - @test L isa TensorMapping{T,1,1} where T + @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 - L = laplace(g_3D, inner_stencil, closure_stencils) - @test L isa TensorMapping{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) - @test L == Dxx + Dyy + Dzz + @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. @testset "2nd order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - L = laplace(g_3D, inner_stencil, closure_stencils) - @test L*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 - @test L*polynomials[2] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 - @test L*polynomials[3] ≈ polynomials[1] atol = 5e-9 - @test L*v ≈ Δv rtol = 5e-2 norm = l2 + stencil_set = read_stencil_set(operator_path; order=2) + Δ = Laplace(g_3D, stencil_set) + @test Δ*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 + @test Δ*polynomials[2] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 + @test Δ*polynomials[3] ≈ polynomials[1] atol = 5e-9 + @test Δ*v ≈ Δv rtol = 5e-2 norm = l2 end # 4th order interior stencil, 2nd order boundary stencil, # implies that L*v should be exact for binomials up to order 3. @testset "4th order" begin - stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4) - inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"]) - closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"]) - L = laplace(g_3D, inner_stencil, closure_stencils) + stencil_set = read_stencil_set(operator_path; order=4) + Δ = Laplace(g_3D, stencil_set) # NOTE: high tolerances for checking the "exact" differentiation # due to accumulation of round-off errors/cancellation errors? - @test L*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 - @test L*polynomials[2] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 - @test L*polynomials[3] ≈ polynomials[1] atol = 5e-9 - @test L*polynomials[4] ≈ polynomials[2] atol = 5e-9 - @test L*v ≈ Δv rtol = 5e-4 norm = l2 + @test Δ*polynomials[1] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 + @test Δ*polynomials[2] ≈ zeros(Float64, size(g_3D)...) atol = 5e-9 + @test Δ*polynomials[3] ≈ polynomials[1] atol = 5e-9 + @test Δ*polynomials[4] ≈ polynomials[2] atol = 5e-9 + @test Δ*v ≈ Δv rtol = 5e-4 norm = l2 end end 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, stencil_set) + @test Δ == second_derivative(g_1D, stencil_set) + @test Δ isa LazyTensor{Float64,1,1} + end + @testset "3D" begin + Δ = 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{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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/SbpOperators/volumeops/volume_operator_test.jl Sun Jan 12 21:18:44 2025 +0100 @@ -1,124 +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 TensorMapping{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 = IdentityMapping{Float64}((11,)) - Iy = IdentityMapping{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 TensorMapping{T,2,2} where T - @test op_y isa TensorMapping{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 = IdentityMapping{Float64}((11,)) - Iy = IdentityMapping{Float64}((12,)) - Iz = IdentityMapping{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 TensorMapping{T,3,3} where T - @test op_y isa TensorMapping{T,3,3} where T - @test op_z isa TensorMapping{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_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 Fri Jan 21 15:23:08 2022 +0100 +++ /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 Sun Jan 12 21:18:44 2025 +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 Sun Jan 12 21:18:44 2025 +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 Fri Jan 21 15:23:08 2022 +0100 +++ b/test/runtests.jl Sun Jan 12 21:18:44 2025 +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_defined_modules = true) + end + end + end + run_testfiles(ARGS) + println() end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/update_manifest.jl Sun Jan 12 21:18:44 2025 +0100 @@ -0,0 +1,12 @@ +using Pkg + +function update_directory(d) + Pkg.activate(d) + Pkg.update() + println() +end + +update_directory(".") +update_directory("benchmark") +update_directory("docs") +update_directory("test")