Mercurial > repos > public > sbplib_julia
changeset 1222:5f677cd6f0b6 refactor/grids
Start refactoring
author | Jonatan Werpers <jonatan@werpers.com> |
---|---|
date | Sat, 18 Feb 2023 11:37:35 +0100 |
parents | b3b4d29b46c3 |
children | a8fa8c1137cc |
files | Notes.md grid_refactor.md src/Grids/equidistant_grid.jl src/Grids/grid.jl src/Grids/tensor_grid.jl |
diffstat | 5 files changed, 335 insertions(+), 178 deletions(-) [+] |
line wrap: on
line diff
--- a/Notes.md Fri Feb 10 08:36:56 2023 +0100 +++ b/Notes.md Sat Feb 18 11:37:35 2023 +0100 @@ -148,6 +148,7 @@ ## Identifiers for regions The identifiers (`Upper`, `Lower`, `Interior`) used for region indecies should probabily be included in the grid module. This allows new grid types to come with their own regions. +We implement this by refactoring RegionIndices to be agnostic to the region types and then moving the actual types to Grids. ## Regions and tensormappings - [ ] Use a trait to indicate if a LazyTensor uses indices with regions.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/grid_refactor.md Sat Feb 18 11:37:35 2023 +0100 @@ -0,0 +1,104 @@ +# Grids refactor + +## Mål + * Embedded grids + * Vektorvärda grid funktioner, speciellt koordinat-funktionen + * Olika griddar i olika riktningar? + * Tex periodiskt i en riktning och intervall i en. + * Tex ostrukturerat i ett par och strukturerat i en. + * Enkelt att evaluera på 0d-grid + +## Frågor + + +### Ska `Grid` vara en AbstractArray? +Efter som alla nät ska agera som en gridfunktion för koordinaterna måste man +svara på frågan hur vi hanterar generellla gridfunktioner samtidigt. + +Några saker att förhålla sig till: + - Multiblock nät? + - Unstructured? + - Triangular structured grids? + - Non-simply connected? + - CG/DG-nät + + +Om alla nät är någon slags AbstractArray så kan tillexempel ett multiblock nät vara en AbstractArray{<:Grid, 1} och motsvarande gridfunktioner AbstractArray{<:AbstractArray,1}. +Där man alltså först indexerar för vilket när man vill åt och sedan indexerar nätet. Tex `mg[2][32,12]`. + +Ett ostrukturerat nät med till exempel trianglar skulle vi kunna se på samma sätt som ett multiblocknät. Antagligen har de två typerna av nät olika underliggande datastruktur anpassade efter ändamålet. + +Hur fungerar tankarna ovan om man har tex tensorprodukten av ett ostrukturerat nät och en ekvidistant nät? +```julia +m = Mesh2DTriangle(10) +e = EqudistantGrid(range(1:10) + +e[4] # fourth point + +m[3][5] # Fifth node in third triangle +m[3,5] # Fifth node in third triangle # Funkar bara om alla nät är samma, (stämmer inte i mb-fallet) + +g = TensorGrid(m, e) + +g[3,4][5] # ?? +g[3,4] # ?? + +g[3,5,4] # ?? + + + +``` + +Alla grids kanske inte är AbstractArray? Måste de vara rektangulära? Det blir svårt för strukturerade trianglar och NSC-griddar. Men de ska i allafall vara indexerbara? + +Man skulle kunna utesluta MultiblockGrid i tensorgrids + +CG-nät och DG-nät blir olika. +På CG-nät kanske man både vill indexera noder och trianglar beroende på vad man håller på med? + +#### Försök till slutsater + * Multiblock-nät indexeras i två nivåer tex `g[3][3,4]` + * Vi struntar i att implementera multiblock-nät som en del av ett tensorgrid till att börja med. + * En grid kan inte alltid vara en AbstractArray eftersom till exempel ett NCS eller strukturerad triangel inte har rätt form. + * Om vi har nod-indexerade ostrukturerade nät borde de fungera med TensorGrid. + * + +### Kan vi introducera 1d griddar och tensorgriddar? + * Vanligt intervallgrid + * Periodiskt grid + * 0-dimensionellt grid + +Det skulle kunna lösa frågan med embedded-griddar +och frågan om mixade grids. + +En svårighet kan vara att implemetera indexeringen för tensorgridden. Är det +lätt att göra den transparent för kompilatorn? + +Läs i notes.md om: Grids embedded in higher dimensions + +Periodiska gridfunktioner? Eller ska vi bara skita i det och låta användaren +hantera det själv? Blir det krångligt i högre dimensioner? + + +### Hur ska vi hantera gridfunktioner med flera komponenter? +Tex koordinat-funktionen på nätet? + +Funkar det att ha StaticArray som element type? + Kan man köra rakt på med en operator då? + +Vi skulle också kunna använda ArraysOfArrays. Skillnaden blir att den elementtypen är Array{T,N}. Vilket betyder att den är by reference? + Frågan är om kompilatorn kommer att bry sig... Jag skulle luta mot StaticArrays for safety + +Läs i notes.md om: Vector valued grid functions + +## Notes from pluto notebook +- Är det dåligt att använda ndims om antalet index inte matchar? + - Tex ostrukturerat grid + - Baserat på hur Array fungerar och ndims docs borde den nog returnera + antalet index och inte griddens dimension + - Å andra sidan kanske man inte behöver veta viken dimension det är? Fast det känns konstigt... +- Should we keep the `points` function? + - Maybe it's better to support `collect` on the grid? +- How to handle boundary identifiers and boundary grids on `TensorGrid`? + +
--- a/src/Grids/equidistant_grid.jl Fri Feb 10 08:36:56 2023 +0100 +++ b/src/Grids/equidistant_grid.jl Sat Feb 18 11:37:35 2023 +0100 @@ -1,26 +1,77 @@ -""" - EquidistantGrid{Dim,T<:Real} <: Grid +struct EquidistantGrid{T,R<:AbstractRange{T}} <: Grid{T,1,1} + points::R +end -`Dim`-dimensional equidistant grid with coordinates of type `T`. +Base.eltype(g::EquidistantGrid{T}) where T = T +Base.getindex(g::EquidistantGrid, i) = g.points[i] +Base.size(g::EquidistantGrid) = size(g.points) +Base.length(g::EquidistantGrid) = length(g.points) +Base.eachindex(g::EquidistantGrid) = eachindex(g.points) + +# TODO: Make sure collect works! + + """ -struct EquidistantGrid{Dim,T<:Real} <: Grid - size::NTuple{Dim, Int} - limit_lower::NTuple{Dim, T} - limit_upper::NTuple{Dim, T} + spacing(grid::EquidistantGrid) + +The spacing between grid points. +""" +spacing(g::EquidistantGrid) = step(g.points) + - function EquidistantGrid{Dim,T}(size::NTuple{Dim, Int}, limit_lower::NTuple{Dim, T}, limit_upper::NTuple{Dim, T}) where {Dim,T} - if any(size .<= 0) - throw(DomainError("all components of size must be postive")) - end - if any(limit_upper.-limit_lower .<= 0) - throw(DomainError("all side lengths must be postive")) - end - return new{Dim,T}(size, limit_lower, limit_upper) - end +""" + inverse_spacing(grid::EquidistantGrid) + +The reciprocal of the spacing between grid points. +""" +inverse_spacing(g::EquidistantGrid) = 1/step(g.points) + + +boundary_identifiers(::EquidistantGrid) = (Lower(), Upper()) +boundary_grid(g::EquidistantGrid, id::Lower) = ZeroDimGrid(g[begin]) +boundary_grid(g::EquidistantGrid, id::Upper) = ZeroDimGrid(g[end]) + + +""" + refine(g::EquidistantGrid, r::Int) + +Refines `grid` by a factor `r`. The factor is applied to the number of +intervals which is 1 less than the size of the grid. + +See also: [`coarsen`](@ref) +""" +function refine(g::EquidistantGrid, r::Int) + new_sz = (length(g) - 1)*r + 1 + return EquidistantGrid(change_length(g.points, new_sz)) end """ - EquidistantGrid(size, limit_lower, limit_upper) + coarsen(grid::EquidistantGrid, r::Int) + +Coarsens `grid` by a factor `r`. The factor is applied to the number of +intervals which is 1 less than the size of the grid. If the number of +intervals are not divisible by `r` an error is raised. + +See also: [`refine`](@ref) +""" +function coarsen(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(size::Dims, limit_lower, limit_upper) Construct an equidistant grid with corners at the coordinates `limit_lower` and `limit_upper`. @@ -32,168 +83,33 @@ The number of equidistantly spaced points in each coordinate direction are given by the tuple `size`. """ -function EquidistantGrid(size, limit_lower, limit_upper) - return EquidistantGrid{length(size), eltype(limit_lower)}(size, limit_lower, limit_upper) -end - -""" - EquidistantGrid{T}() - -Constructs a 0-dimensional grid. -""" -EquidistantGrid{T}() where T = EquidistantGrid{0,T}((),(),()) # Convenience constructor for 0-dim grid - - -""" - EquidistantGrid(size::Int, limit_lower::T, limit_upper::T) - -Convenience constructor for 1D grids. -""" -function EquidistantGrid(size::Int, limit_lower::T, limit_upper::T) where T - return EquidistantGrid((size,),(limit_lower,),(limit_upper,)) -end - -Base.eltype(grid::EquidistantGrid{Dim,T}) where {Dim,T} = T - -Base.eachindex(grid::EquidistantGrid) = CartesianIndices(grid.size) - -Base.size(g::EquidistantGrid) = g.size - -Base.ndims(::EquidistantGrid{Dim}) where Dim = Dim - -function Base.getindex(g::EquidistantGrid, I::Vararg{Int}) - h = spacing(g) - return g.limit_lower .+ (I.-1).*h -end - -Base.getindex(g::EquidistantGrid, I::CartesianIndex) = g[Tuple(I)...] - -# Review: -# Is it not strange that evalOn(::Grid) is non-lazy while evalOn(::EquidistantGrid) is? -# Also: Change name to evalon or eval_on!!!!!! -function evalOn(grid::EquidistantGrid, f::Function) - F(I...) = f(grid[I...]...) +function equidistant_grid(size::Dims, limit_lower, limit_upper) + gs = map(size, limit_lower, limit_upper) do s,l,u + EquidistantGrid(range(l, u, length=s)) # TBD: Should it use LinRange instead? + end - return LazyFunctionArray(F, size(grid)) -end - -""" - spacing(grid::EquidistantGrid) - -The spacing between grid points. -""" -spacing(grid::EquidistantGrid) = (grid.limit_upper.-grid.limit_lower)./(grid.size.-1) - - -""" - inverse_spacing(grid::EquidistantGrid) - -The reciprocal of the spacing between grid points. -""" -inverse_spacing(grid::EquidistantGrid) = 1 ./ spacing(grid) - - -""" - points(grid::EquidistantGrid) - -The point of the grid as an array of tuples with the same dimension as the grid. -The points are stored as [(x1,y1), (x1,y2), … (x1,yn); - (x2,y1), (x2,y2), … (x2,yn); - ⋮ ⋮ ⋮ - (xm,y1), (xm,y2), … (xm,yn)] -""" -function points(grid::EquidistantGrid) - indices = Tuple.(CartesianIndices(grid.size)) - h = spacing(grid) - return broadcast(I -> grid.limit_lower .+ (I.-1).*h, indices) -end - -""" - restrict(::EquidistantGrid, dim) - -Pick out given dimensions from the grid and return a grid for them. -""" -function restrict(grid::EquidistantGrid, dim) - size = grid.size[dim] - limit_lower = grid.limit_lower[dim] - limit_upper = grid.limit_upper[dim] - - return EquidistantGrid(size, limit_lower, limit_upper) + return TensorGrid(gs...) end """ - orthogonal_dims(grid::EquidistantGrid,dim) + equidistant_grid(size::Int, limit_lower::T, limit_upper::T) -Returns the dimensions of grid orthogonal to that of dim. +Constructs a 1D equidistant grid. """ -function orthogonal_dims(grid::EquidistantGrid, dim) - orth_dims = filter(i -> i != dim, dims(grid)) - if orth_dims == dims(grid) - throw(DomainError(string("dimension ",string(dim)," not matching grid"))) - end - return orth_dims +function equidistant_grid(size::Int, limit_lower::T, limit_upper::T) where T + return equidistant_grid((size,),(limit_lower,),(limit_upper,)) end -""" - boundary_identifiers(::EquidistantGrid) - -Returns a tuple containing the boundary identifiers for the grid, stored as - (CartesianBoundary(1,Lower), - CartesianBoundary(1,Upper), - CartesianBoundary(2,Lower), - ...) -""" -boundary_identifiers(g::EquidistantGrid) = (((ntuple(i->(CartesianBoundary{i,Lower}(),CartesianBoundary{i,Upper}()),ndims(g)))...)...,) - """ - boundary_grid(grid::EquidistantGrid, id::CartesianBoundary) + change_length(::AbstractRange, n) -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) - orth_dims = orthogonal_dims(grid, dim(id)) - return restrict(grid, orth_dims) -end -boundary_grid(::EquidistantGrid{1,T},::CartesianBoundary{1}) where T = EquidistantGrid{T}() - - -""" - refine(grid::EquidistantGrid, r::Int) - -Refines `grid` by a factor `r`. The factor is applied to the number of -intervals which is 1 less than the size of the grid. - -See also: [`coarsen`](@ref) +Change the length of a range to `n`, keeping the same start and stop. """ -function refine(grid::EquidistantGrid, r::Int) - sz = size(grid) - new_sz = (sz .- 1).*r .+ 1 - return EquidistantGrid{ndims(grid), eltype(grid)}(new_sz, grid.limit_lower, grid.limit_upper) -end - - -""" - coarsen(grid::EquidistantGrid, r::Int) - -Coarsens `grid` by a factor `r`. The factor is applied to the number of -intervals which is 1 less than the size of the grid. If the number of -intervals are not divisible by `r` an error is raised. +function change_length(::AbstractRange, n) end -See also: [`refine`](@ref) -""" -function coarsen(grid::EquidistantGrid, r::Int) - sz = size(grid) - - if !all(n -> (n % r == 0), sz.-1) - throw(DomainError(r, "Size minus 1 must be divisible by the ratio.")) - end - - new_sz = (sz .- 1).÷r .+ 1 - - return EquidistantGrid{ndims(grid), eltype(grid)}(new_sz, grid.limit_lower, grid.limit_upper) -end +change_length(r::LinRange, n) = LinRange(r[begin], r[end], n) +change_length(r::StepRangeLen, n) = range(r[begin], r[end], n) +# TODO: Test the above
--- a/src/Grids/grid.jl Fri Feb 10 08:36:56 2023 +0100 +++ b/src/Grids/grid.jl Sat Feb 18 11:37:35 2023 +0100 @@ -1,27 +1,76 @@ """ - Grid + Grid{T,D,RD} <: AbstractArray{T,D} + +The top level type for grids. Should implement - Base.ndims(grid::Grid) - points(grid::Grid) +# TBD: +""" +#TBD: Does all the kinds of grids we want fit with this interface? +# Multigrid? +# Unstructured? +# Triangular structured grids? +# Non-simply connected? +# +# Maybe it shouldn't be an abstract array after all? +abstract type Grid{T,D,RD} <: AbstractArray{T,D} end + + +Base.ndims(::Grid{T,D,RD}) where {T,D,RD} = D # nidms borde nog vara antalet index som används för att indexera nätet. Snarare än vilken dimension nätet har (tänk ostrukturerat) +nrangedims(::Grid{T,D,RD}) where {T,D,RD} = RD +Base.eltype(::Grid{T,D,RD}) where {T,D,RD} = T # vad ska eltype vara? Inte T väl... en vektor? SVector{T,D}? + +function eval_on(::Grid) end # TODO: Should return a LazyArray and index the grid +function refine(::Grid) end +function coarsen(::Grid) end # Should this be here? What if it is not possible? + +abstract type BoundaryId end """ -abstract type Grid end -function points end +# TODO +""" +function boundary_identifiers(::Grid) end +""" +# TODO +""" +function boundary_grid(::Grid, ::BoundaryId) end + + +# TODO: Make sure that all grids implement all of the above. """ dims(grid::Grid) -A range containing the dimensions of `grid` +Enumerate the dimensions of the grid. """ dims(grid::Grid) = 1:ndims(grid) + + +# TBD: New file grid_functions.jl? + """ - evalOn(grid::Grid, f::Function) + getcomponent(gfun, I::Vararg{Int}) -Evaluate function `f` on `grid` +Return one of the components of gfun as a grid function. """ -function evalOn(grid::Grid, f::Function) - F(x) = f(x...) - return F.(points(grid)) +# Should it be lazy? Could it be a view? +function getcomponent(gfun, I::Vararg{Int}) end +# function getcomponent(gfun, s::Symbol) end ? + +# TBD: New file zero_dim_grid.jl? +struct ZeroDimGrid{T,S,RD} <: Grid{T,0,RD} + p::S + + function ZeroDimGrid(p) + T = eltype(p) + S = typeof(p) + RD = length(p) + return new{T,S,RD}(p) + end end + +Base.size(g::ZeroDimGrid) = () +Base.getindex(g::ZeroDimGrid) = g.p +Base.eachindex(g::ZeroDimGrid) = CartesianIndices(()) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/tensor_grid.jl Sat Feb 18 11:37:35 2023 +0100 @@ -0,0 +1,87 @@ +struct TensorGrid{T,D,RD,GT<:NTuple{N,Grid} where N} <: Grid{T,D,RD} + grids::GT + + function TensorGrid(gs...) + T = eltype(gs[1]) # All gs should have the same T + D = sum(ndims,gs) + RD = sum(nrangedims, gs) + + return new{T,D,RD,typeof(gs)}(gs) + end +end + +function Base.size(g::TensorGrid) + return concatenate_tuples(size.(g.grids)...) +end + +function Base.getindex(g::TensorGrid, I...) + szs = ndims.(g.grids) + + Is = split_tuple(I, szs) + ps = map((g,I)->SVector(g[I...]), g.grids, Is) + + return vcat(ps...) +end + +IndexStyle(::TensorGrid) = IndexCartesian() + +function Base.eachindex(g::TensorGrid) + szs = concatenate_tuples(size.(g.grids)...) + return CartesianIndices(szs) +end + + + +## Pre refactor code: +""" + orthogonal_dims(grid::EquidistantGrid,dim) + +Returns the dimensions of grid orthogonal to that of dim. +""" +function orthogonal_dims(grid::EquidistantGrid, dim) + orth_dims = filter(i -> i != dim, dims(grid)) + if orth_dims == dims(grid) + throw(DomainError(string("dimension ",string(dim)," not matching grid"))) + end + return orth_dims +end + +""" + restrict(::EquidistantGrid, dim) + +Pick out given dimensions from the grid and return a grid for them. +""" +function restrict(grid::EquidistantGrid, dim) + size = grid.size[dim] + limit_lower = grid.limit_lower[dim] + limit_upper = grid.limit_upper[dim] + + return EquidistantGrid(size, limit_lower, limit_upper) +end + + + +""" + boundary_identifiers(::EquidistantGrid) + +Returns a tuple containing the boundary identifiers for the grid, stored as + (CartesianBoundary(1,Lower), + CartesianBoundary(1,Upper), + CartesianBoundary(2,Lower), + ...) +""" +boundary_identifiers(g::EquidistantGrid) = (((ntuple(i->(CartesianBoundary{i,Lower}(),CartesianBoundary{i,Upper}()),ndims(g)))...)...,) + + +""" + boundary_grid(grid::EquidistantGrid, id::CartesianBoundary) + +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) + orth_dims = orthogonal_dims(grid, dim(id)) + return restrict(grid, orth_dims) +end +boundary_grid(::EquidistantGrid{1,T},::CartesianBoundary{1}) where T = EquidistantGrid{T}()