Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ExclusiveTopology with ArrayOfVectorViews backend #974

Merged
merged 62 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
f62b7b4
Add MaterializedTopology
KnutAM May 31, 2024
1253879
Fixes
KnutAM May 31, 2024
82abd0a
Initial test
KnutAM May 31, 2024
bcffdd4
Fixes
KnutAM May 31, 2024
f95adac
Add x_skeleton functions
KnutAM May 31, 2024
260fa81
Include existing topologies
KnutAM Jun 2, 2024
f115bed
Include in test suite
KnutAM Jun 2, 2024
867376d
Fix test on 1.6
KnutAM Jun 2, 2024
fce4180
Workaround for ndims on julia 1.6
KnutAM Jun 2, 2024
4931bb9
Start replacing ExclusiveTopology
KnutAM Jun 3, 2024
2820b4c
Use only ArrayOfVectorViews
KnutAM Jun 4, 2024
ad9e4d9
Fix tests
KnutAM Jun 5, 2024
10f4ee2
Improve error msg
KnutAM Jun 5, 2024
ef1ea3f
Delete unused files
KnutAM Jun 5, 2024
1a32352
Remove using of OrderedDict
KnutAM Jun 5, 2024
7cf573b
Fix topology example
KnutAM Jun 6, 2024
1d2d2c8
Fix topopt example 2
KnutAM Jun 6, 2024
ad82df6
Speed optimizations
KnutAM Jun 7, 2024
15c856d
Fix case for empty index array, e.g. 1d grid
KnutAM Jun 7, 2024
8d5929b
Reorganize order in topology to minimize diff
KnutAM Jun 7, 2024
bbbd7f2
More reorder
KnutAM Jun 7, 2024
c276cb2
Another day, another optimization
KnutAM Jun 8, 2024
cfaa23f
Correct comment
KnutAM Jun 8, 2024
55d0142
Move ArrayOfVectorViews into submodule CollectionsOfVectorViews
KnutAM Jun 12, 2024
cd99454
Apply suggestions from code review
KnutAM Jun 12, 2024
ae0d58a
Remove EntityNeighborhood and document ExclusiveTopology fields
KnutAM Jun 13, 2024
949e80e
Merge branch 'kam/exclusive_topology' of github.com:Ferrite-FEM/Ferri…
KnutAM Jun 13, 2024
f906d3b
Include self in data
KnutAM Jun 13, 2024
ab35571
Fix spelling error
KnutAM Jun 13, 2024
c3dae88
Fix skeleton logic
KnutAM Jun 13, 2024
a1f88e8
Work on making it work
KnutAM Jun 16, 2024
2b488ad
Revert including self in data
KnutAM Jun 17, 2024
7a6e6bd
Manually revert what git didn't do
KnutAM Jun 17, 2024
01f1c0e
Last revert fix
KnutAM Jun 17, 2024
5f2781e
Adjust sizehints
KnutAM Jun 17, 2024
725510f
Merge branch 'master' into kam/exclusive_topology
KnutAM Jun 19, 2024
0c99133
Fix merged code
KnutAM Jun 19, 2024
bd4604b
Test ArrayOfVectorViews
KnutAM Jun 20, 2024
40c94e4
Add devdocs for collection of views
KnutAM Jun 20, 2024
1a6b0bf
Add some tests with include_self=true
KnutAM Jun 20, 2024
3cdc98a
Fix test
KnutAM Jun 20, 2024
76999c6
Add tests for edgeindex in 2d
KnutAM Jun 20, 2024
35253ef
Add small test for grid with mixed celltypes
KnutAM Jun 20, 2024
7d64590
Update docstrings
KnutAM Jun 20, 2024
feb3675
Add CHANGELOG
KnutAM Jun 20, 2024
df6bc79
Fix test
KnutAM Jun 20, 2024
b6615cd
Fix test
KnutAM Jun 20, 2024
06ab140
Fix test
KnutAM Jun 20, 2024
ce4e83e
Add note about ArraysOfArrays.jl
KnutAM Jun 21, 2024
e07c563
Fix test
KnutAM Jun 21, 2024
68b19f4
Correct index style dispatch
KnutAM Jun 21, 2024
c428fbb
Clarify experimental status in docstring
KnutAM Jun 21, 2024
c032b3e
Fix type-instability due to lack of specialization
KnutAM Jun 21, 2024
b7e2add
Sizehint optimization
KnutAM Jun 21, 2024
e5b73c6
Remove a todo note
KnutAM Jun 21, 2024
eeeebe9
Merge master
KnutAM Jun 28, 2024
f66be4a
Apply suggestions from code review
KnutAM Jul 1, 2024
6f67692
Merge branch 'master' into kam/exclusive_topology
KnutAM Jul 1, 2024
603de9b
Remove space at end of lines
KnutAM Jul 1, 2024
886e8e2
Apply change from review
KnutAM Jul 9, 2024
b5a198f
Include information about changed constructor in changelog
KnutAM Jul 9, 2024
2961031
Merge branch 'master' into kam/exclusive_topology
KnutAM Jul 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -446,10 +446,18 @@ more discussion).
`facedof_indices`, `volumedof_indices` (and similar) according to these definitions.

- `Ferrite.getdim` has been changed into `Ferrite.getrefdim` for getting the dimension of the reference shape
and `Ferrite.getspatialdim` to get the spatial dimension (of the grid). [#943][github-943]
and `Ferrite.getspatialdim` to get the spatial dimension (of the grid). ([#943][github-943])

- `Ferrite.getfielddim(::AbstractDofHandler, args...)` has been renamed to `Ferrite.n_components`.
[#943][github-943]
([#943][github-943])

- The constructor for `ExclusiveTopology` only accept an `AbstractGrid` as input,
removing the alternative of providing a `Vector{<:AbstractCell}`, as knowing the
spatial dimension is required for correct code paths.
Furthermore, it uses a new internal data structure, `ArrayOfVectorViews`, to store the neighborhood
information more efficiently The datatype for the neighborhood has thus changed to a view of a vector,
instead of the now removed `EntityNeighborhood` container. This also applies to `vertex_star_stencils`.
([#974][github-974]).

- `project(::L2Projector, data, qr_rhs)` now expects data to be indexed by the cellid, as opposed to
the index in the vector of cellids passed to the `L2Projector`. The data may be passed as an
Expand Down Expand Up @@ -1037,3 +1045,4 @@ poking into Ferrite internals:
[github-943]: https://github.com/Ferrite-FEM/Ferrite.jl/pull/943
[github-949]: https://github.com/Ferrite-FEM/Ferrite.jl/pull/949
[github-953]: https://github.com/Ferrite-FEM/Ferrite.jl/pull/953
[github-974]: https://github.com/Ferrite-FEM/Ferrite.jl/pull/974
2 changes: 1 addition & 1 deletion docs/src/devdocs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ developing the library.

```@contents
Depth = 1
Pages = ["reference_cells.md", "interpolations.md", "elements.md", "FEValues.md", "dofhandler.md", "performance.md"]
Pages = ["reference_cells.md", "interpolations.md", "elements.md", "FEValues.md", "dofhandler.md", "performance.md", "special_datastructures.md"]
```
16 changes: 16 additions & 0 deletions docs/src/devdocs/special_datastructures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Special data structures

## `ArrayOfVectorViews`
KnutAM marked this conversation as resolved.
Show resolved Hide resolved
`ArrayOfVectorViews` is a data structure representing an `Array` of
vector views (specifically `SubArray{T, 1} where T`). By arranging all
data (of type `T`) continuously in memory, this will significantly reduce
the garbage collection time compared to using an `Array{AbstractVector{T}}`. While the data in each view can be mutated, the length of each view is
fixed after construction.
This data structure provides two features not provided by `ArraysOfArrays.jl`: Support of matrices and higher order arrays for storing vectors
of different dimensions and efficient construction when the number of elements in each view is not known in advance.

```@docs
Ferrite.ArrayOfVectorViews
Ferrite.ConstructionBuffer
Ferrite.push_at_index!
```
6 changes: 3 additions & 3 deletions docs/src/literate-gallery/topology_optimization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,11 @@ function cache_neighborhood(dh, topology)
nbg = zeros(Int,_nfacets)
i = cellid(element)
for j in 1:_nfacets
nbg_cellid = getcells(getneighborhood(topology, dh.grid, FacetIndex(i,j)))
nbg_cellid = getneighborhood(topology, dh.grid, FacetIndex(i,j))
if(!isempty(nbg_cellid))
nbg[j] = first(nbg_cellid) # assuming only one face neighbor per cell
nbg[j] = first(nbg_cellid)[1] # assuming only one face neighbor per cell
else # boundary face
nbg[j] = first(getcells(getneighborhood(topology, dh.grid, FacetIndex(i,opp[j]))))
nbg[j] = first(getneighborhood(topology, dh.grid, FacetIndex(i,opp[j])))[1]
end
end

Expand Down
156 changes: 156 additions & 0 deletions src/CollectionsOfViews.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
module CollectionsOfViews

export ArrayOfVectorViews, push_at_index!, ConstructionBuffer

# `AdaptiveRange` and `ConstructionBuffer` are used to efficiently build up an `ArrayOfVectorViews`
# when the size of each view is unknown.
struct AdaptiveRange
start::Int
ncurrent::Int
nmax::Int
end

struct ConstructionBuffer{T, N}
indices::Array{AdaptiveRange, N}
data::Vector{T}
sizehint::Int
end

"""
ConstructionBuffer(data::Vector, dims::NTuple{N, Int}, sizehint)

Create a buffer for creating an [`ArrayOfVectorViews`](@ref), representing an array with `N` axes.
`sizehint` sets the number of elements in `data` allocated when a new index is added via `push_at_index!`,
or when the current storage for the index is full, how much many additional elements are reserved for that index.
Any content in `data` is overwritten, but performance is improved by pre-allocating it to a reasonable size or
by `sizehint!`ing it.
"""
function ConstructionBuffer(data::Vector, dims::NTuple{<:Any, Int}, sizehint::Int)
indices = fill(AdaptiveRange(0, 0, 0), dims)
return ConstructionBuffer(indices, empty!(data), sizehint)
end

"""
push_at_index!(b::ConstructionBuffer, val, indices::Int...)

`push!` the value `val` to the `Vector` view at the index given by `indices`, typically called
inside the [`ArrayOfVectorViews`](@ref) constructor do-block. But can also be used when manually
creating a `ConstructionBuffer`.
"""
function push_at_index!(b::ConstructionBuffer, val, indices::Vararg{Int, N}) where {N}
r = getindex(b.indices, indices...)
n = length(b.data)
if r.start == 0
# `indices...` not previously added, allocate new space for it at the end of `b.data`
resize!(b.data, n + b.sizehint)
b.data[n+1] = val
setindex!(b.indices, AdaptiveRange(n + 1, 1, b.sizehint), indices...)
elseif r.ncurrent == r.nmax
# We have used up our space, move data associated with `indices...` to the end of `b.data`
resize!(b.data, n + r.nmax + b.sizehint)
for i in 1:r.ncurrent
b.data[n + i] = b.data[r.start + i - 1]
end
b.data[n + r.ncurrent + 1] = val
setindex!(b.indices, AdaptiveRange(n + 1, r.ncurrent + 1, r.nmax + b.sizehint), indices...)
else # We have space in an already allocated section
b.data[r.start + r.ncurrent] = val
setindex!(b.indices, AdaptiveRange(r.start, r.ncurrent + 1, r.nmax), indices...)
end
return b
end

struct ArrayOfVectorViews{T, N} <: AbstractArray{SubArray{T, 1, Vector{T}, Tuple{UnitRange{Int64}}, true}, N}
indices::Vector{Int}
data::Vector{T}
lin_idx::LinearIndices{N, NTuple{N, Base.OneTo{Int}}}
function ArrayOfVectorViews{T, N}(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}) where {T, N}
return new{T, N}(indices, data, lin_idx)
end
end

# AbstractArray interface (https://docs.julialang.org/en/v1/manual/interfaces/#man-interface-array)
Base.size(cv::ArrayOfVectorViews) = size(cv.lin_idx)
@inline function Base.getindex(cv::ArrayOfVectorViews, linear_index::Int)
@boundscheck checkbounds(cv.lin_idx, linear_index)
return @inbounds view(cv.data, cv.indices[linear_index]:(cv.indices[linear_index+1]-1))
end
@inline function Base.getindex(cv::ArrayOfVectorViews, idx...)
linear_index = getindex(cv.lin_idx, idx...)
return @inbounds getindex(cv, linear_index)
end
Base.IndexStyle(::Type{<:ArrayOfVectorViews{<:Any, N}}) where N = Base.IndexStyle(Array{Int, N})

# Constructors
"""
ArrayOfVectorViews(f!::Function, data::Vector{T}, dims::NTuple{N, Int}; sizehint)

Create an `ArrayOfVectorViews` to store many vector views of potentially different sizes,
emulating an `Array{Vector{T}, N}` with size `dims`. However, it avoids allocating each vector individually
by storing all data in `data`, and instead of `Vector{T}`, the each element is a `typeof(view(data, 1:2))`.

When the length of each vector is unknown, the `ArrayOfVectorViews` can be created reasonably efficient
with the following do-block, which creates an intermediate `buffer::ConstructionBuffer` supporting the
[`push_at_index!`](@ref) function.
```
vector_views = ArrayOfVectorViews(data, dims; sizehint) do buffer
for (ind, val) in some_data
push_at_index!(buffer, val, ind)
end
end
```
`sizehint` tells how much space to allocate for the index `ind` if no `val` has been added to that index before,
or how much more space to allocate in case all previously allocated space for `ind` has been used up.
"""
function ArrayOfVectorViews(f!::F, data::Vector, dims::Tuple; sizehint = nothing) where {F <: Function}
sizehint === nothing && error("Providing sizehint is mandatory")
b = ConstructionBuffer(data, dims, sizehint)
f!(b)
return ArrayOfVectorViews(b)
end

"""
ArrayOfVectorViews(b::CollectionsOfViews.ConstructionBuffer)

Creates the `ArrayOfVectorViews` directly from the `ConstructionBuffer` that was manually created and filled.
"""
function ArrayOfVectorViews(b::ConstructionBuffer{T}) where T
indices = Vector{Int}(undef, length(b.indices) + 1)
lin_idx = LinearIndices(b.indices)
data_length = sum(ar.ncurrent for ar in b.indices; init=0)
data = Vector{T}(undef, data_length)
data_index = 1
for (idx, ar) in pairs(b.indices)
copyto!(data, data_index, b.data, ar.start, ar.ncurrent)
indices[lin_idx[idx]] = data_index
data_index += ar.ncurrent
end
indices[length(indices)] = data_index
# Since user-code in the constructor function has access to `b`, setting dimensions to
# zero here allows GC:ing the data in `b` even in cases when the compiler cannot
# guarantee that it is unreachable.
resize!(b.data, 0); sizehint!(b.data, 0)
isa(b.indices, Vector) && (resize!(b.indices, 0); sizehint!(b.indices, 0))
KnutAM marked this conversation as resolved.
Show resolved Hide resolved
return ArrayOfVectorViews(indices, data, lin_idx)
end

"""
ArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true)

Creates the `ArrayOfVectorViews` directly where the user is responsible for having the correct input data.
Checking of the argument dimensions can be elided by setting `checkargs = false`, but incorrect dimensions
may lead to illegal out of bounds access later.

`data` is indexed by `indices[i]:indices[i+1]`, where `i = lin_idx[idx...]` and `idx...` are the user-provided
indices to the `ArrayOfVectorViews`.
"""
function ArrayOfVectorViews(indices::Vector{Int}, data::Vector{T}, lin_idx::LinearIndices{N}; checkargs = true) where {T, N}
if checkargs
checkbounds(data, 1:(last(indices) - 1))
checkbounds(indices, last(lin_idx) + 1)
issorted(indices) || throw(ArgumentError("indices must be weakly increasing"))
end
return ArrayOfVectorViews{T, N}(indices, data, lin_idx)
end

end
4 changes: 4 additions & 0 deletions src/Ferrite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ using Tensors:
using ForwardDiff:
ForwardDiff

include("CollectionsOfViews.jl")
using .CollectionsOfViews:
CollectionsOfViews, ArrayOfVectorViews, push_at_index!, ConstructionBuffer

include("exports.jl")


"""
AbstractRefShape{refdim}

Expand Down
Loading
Loading