diff --git a/docs/src/reference/grid.md b/docs/src/reference/grid.md index f553167d0c..f8c0a51175 100644 --- a/docs/src/reference/grid.md +++ b/docs/src/reference/grid.md @@ -2,7 +2,9 @@ DocTestSetup = :(using Ferrite) ``` -# Grid +# Grid & AbstractGrid + +## Grid ```@docs Node @@ -14,7 +16,7 @@ FaceIndex Grid ``` -## Utility Functions +### Utility Functions ```@docs getcells @@ -23,18 +25,85 @@ getnodes getnnodes Ferrite.nnodes_per_cell getcellset +getcellsets getnodeset +getnodesets getfaceset +getfacesets +getedgeset +getedgesets +getvertexset +getvertexsets compute_vertex_values transform! getcoordinates getcoordinates! ``` -## Grid Sets Utility +### Grid Sets Utility ```@docs addcellset! addfaceset! addnodeset! ``` + +## AbstractGrid + +It can be very useful to use a grid type for a certain special case, e.g. mixed cell types, adaptivity, IGA, etc. +In order to define your own `<: AbstractGrid` you need to fulfill the `AbstractGrid` interface. +In case that certain structures are preserved from the `Ferrite.Grid` type, you don't need to dispatch on your own type, but rather rely on the fallback `AbstractGrid` dispatch. + +### Example + +As a starting point, we choose a minimal working example from the test suite: + +```julia +struct SmallGrid{dim,N,C<:Ferrite.AbstractCell} <: Ferrite.AbstractGrid{dim} + nodes_test::Vector{NTuple{dim,Float64}} + cells_test::NTuple{N,C} +end +``` + +Here, the names of the fields as well as their underlying datastructure changed compared to the `Grid` type. This would lead to the fact, that any usage +with the utility functions and DoF management will not work. So, we need to feed into the interface how to handle this subtyped datastructure. +We start with the utility functions that are associated with the cells of the grid: + +```julia +Ferrite.getcells(grid::SmallGrid) = grid.cells_test +Ferrite.getcells(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.cells_test[v] +Ferrite.getncells(grid::SmallGrid{dim,N}) where {dim,N} = N +Ferrite.getcelltype(grid::SmallGrid) = eltype(grid.cells_test) +Ferrite.getcelltype(grid::SmallGrid, i::Int) = typeof(grid.cells_test[i]) +``` + +Next, we define some helper functions that take care of the node handling. + +```julia +Ferrite.getnodes(grid::SmallGrid) = grid.nodes_test +Ferrite.getnodes(grid::SmallGrid, v::Union{Int, Vector{Int}}) = grid.nodes_test[v] +Ferrite.getnnodes(grid::SmallGrid) = length(grid.nodes_test) +Ferrite.nnodes_per_cell(grid::SmallGrid, i::Int=1) = Ferrite.nnodes(grid.cells_test[i]) +Ferrite.n_faces_per_cell(grid::SmallGrid) = nfaces(eltype(grid.cells_test)) +``` + +Finally, we define `getcoordinates`, which is an important function, if we want to assemble a problem. +The transformation from the reference space to the physical one requires information about the coordinates in order to construct the +Jacobian. The return of this part is later handled over to `reinit!`. + +```julia +function Ferrite.getcoordinates!(x::Vector{Vec{dim,T}}, grid::SmallGrid, cell::Int) where {dim,T} + for i in 1:length(x) + x[i] = Vec{dim,T}(grid.nodes_test[grid.cells_test[cell].nodes[i]]) + end +end + +function Ferrite.getcoordinates(grid::SmallGrid{dim}, cell::Int) where dim + nodeidx = grid.cells_test[cell].nodes + return [Vec{dim,Float64}(grid.nodes_test[i]) for i in nodeidx]::Vector{Vec{dim,Float64}} +end +``` + +Now, you would be able to assemble the heat equation example over the new custom `SmallGrid` type. +Note that this particular subtype isn't able to handle boundary entity sets and so, you can't describe boundaries with it. +In order to use boundaries, e.g. for Dirichlet constraints in the ConstraintHandler, you would need to dispatch the `AbstractGrid` sets utility functions on `SmallGrid`. diff --git a/src/Grid/grid.jl b/src/Grid/grid.jl index 04bc99293c..83b1f345fb 100644 --- a/src/Grid/grid.jl +++ b/src/Grid/grid.jl @@ -20,7 +20,7 @@ abstract type AbstractCell{dim,N,M} end """ Cell{dim,N,M} <: AbstractCell{dim,N,M} A `Cell` is a sub-domain defined by a collection of `Node`s as it's vertices. -However, a `cell` is not defined by the nodes but rather by the node ids +However, a `cell` is not defined by the nodes but rather by the global node ids # Fields - `nodes::Ntuple{N,Int}`: N-tuple that stores the node ids @@ -85,14 +85,16 @@ abstract type AbstractGrid{dim} end Grid{dim, C<:AbstractCell, T<:Real} <: AbstractGrid} A `Grid` is a collection of `Cells` and `Node`s which covers the computational domain, together with Sets of cells, nodes and faces. +There are multiple helper structures to apply boundary conditions or define subdomains. They are gathered in the `cellsets`, `nodesets`, +`facesets`, `edgesets` and `vertexsets`. # Fields - `cells::Vector{C}`: stores all cells of the grid - `nodes::Vector{Node{dim,T}}`: stores the `dim` dimensional nodes of the grid -- `cellsets::Dict{String,Set{CellIndex}}`: maps a `String` key to a `Set` of cell ids +- `cellsets::Dict{String,Set{Int}}`: maps a `String` key to a `Set` of cell ids - `nodesets::Dict{String,Set{Int}}`: maps a `String` key to a `Set` of global node ids -- `facesets::Dict{String,Set{FaceIndex}}`: maps a `String` to a `Set` of `Tuple{Int,Int} (global_cell_id, local_face_id)` -_ `edgesets::Dict{String,Set{EdgeIndex}}`: maps a `String` to a `Set` of `Set{EdgeIndex} (global_cell_id, local_edge_id` +- `facesets::Dict{String,Set{FaceIndex}}`: maps a `String` to a `Set` of `Set{FaceIndex} (global_cell_id, local_face_id)` +- `edgesets::Dict{String,Set{EdgeIndex}}`: maps a `String` to a `Set` of `Set{EdgeIndex} (global_cell_id, local_edge_id` - `vertexsets::Dict{String,Set{VertexIndex}}`: maps a `String` key to a `Set` of local vertex ids - `boundary_matrix::SparseMatrixCSC{Bool,Int}`: optional, only needed by `onboundary` to check if a cell is on the boundary, see, e.g. Helmholtz example """ @@ -127,55 +129,100 @@ end """ getcells(grid::AbstractGrid) getcells(grid::AbstractGrid, v::Union{Int,Vector{Int}} - getcells(grid::AbstractGrid, set::String) + getcells(grid::AbstractGrid, setname::String) -Returns either all `cells::Vector{C<:AbstractCell}` of a `grid` or a subset based on an `Int`, `Vector{Int}` or `String`. -Whereas the last option tries to call a `cellset` of the `grid`. +Returns either all `cells::Collection{C<:AbstractCell}` of a `<:AbstractGrid` or a subset based on an `Int`, `Vector{Int}` or `String`. +Whereas the last option tries to call a `cellset` of the `grid`. `Collection` can be any indexable type, for `Grid` it is `Vector{C<:AbstractCell}`. """ @inline getcells(grid::AbstractGrid) = grid.cells @inline getcells(grid::AbstractGrid, v::Union{Int, Vector{Int}}) = grid.cells[v] -@inline getcells(grid::AbstractGrid, set::String) = grid.cells[collect(grid.cellsets[set])] -"Returns and `Int` corresponding to how many cells are in the `grid`." +@inline getcells(grid::AbstractGrid, setname::String) = grid.cells[collect(getcellset(grid,setname))] +"Returns the number of cells in the `<:AbstractGrid`." @inline getncells(grid::AbstractGrid) = length(grid.cells) -"Returns the celltype of the `grid`." +"Returns the celltype of the `<:AbstractGrid`." @inline getcelltype(grid::AbstractGrid) = eltype(grid.cells) @inline getcelltype(grid::AbstractGrid, i::Int) = typeof(grid.cells[i]) """ getnodes(grid::AbstractGrid) getnodes(grid::AbstractGrid, v::Union{Int,Vector{Int}} - getnodes(grid::AbstractGrid, set::String) + getnodes(grid::AbstractGrid, setname::String) -Returns either all `nodes::Vector{Node{dim,T}}` of a `grid` or a subset based on an `Int`, `Vector{Int}` or `String`. -The last option tries to call a `nodeset` of the `grid`. +Returns either all `nodes::Collection{N}` of a `<:AbstractGrid` or a subset based on an `Int`, `Vector{Int}` or `String`. +The last option tries to call a `nodeset` of the `<:AbstractGrid`. `Collection{N}` refers to some indexable collection where each element corresponds +to a Node. """ @inline getnodes(grid::AbstractGrid) = grid.nodes @inline getnodes(grid::AbstractGrid, v::Union{Int, Vector{Int}}) = grid.nodes[v] -@inline getnodes(grid::AbstractGrid, set::String) = grid.nodes[collect(grid.nodesets[set])] -"returns an `Int` corresponding to how many nodes are in the `grid`" +@inline getnodes(grid::AbstractGrid, setname::String) = grid.nodes[collect(getnodeset(grid,setname))] +"Returns the number of nodes in the grid" @inline getnnodes(grid::AbstractGrid) = length(grid.nodes) -"returns an `Int` of how many nodes are in one `cell`" +"Returns the number of nodes of the `i`-th cell" @inline nnodes_per_cell(grid::AbstractGrid, i::Int=1) = nnodes(grid.cells[i]) -"Accesses the cellset which is mapped to the key `set::String`" -@inline getcellset(grid::AbstractGrid, set::String) = grid.cellsets[set] -"Returns all cellsets of the `grid`" +""" + getcellset(grid::AbstractGrid, setname::String) + +Returns all cells as cellid in a `Set` of a given `setname` +""" +@inline getcellset(grid::AbstractGrid, setname::String) = grid.cellsets[setname] +""" + getcellsets(grid::AbstractGrid) + +Returns all cellsets of the `grid` +""" @inline getcellsets(grid::AbstractGrid) = grid.cellsets -"Accesses the nodeset which is mapped to the key `set::String`" -@inline getnodeset(grid::AbstractGrid, set::String) = grid.nodesets[set] -"Returns all nodesets of the `grid`" +""" + getnodeset(grid::AbstractGrid, setname::String) + +Returns all nodes as nodeid in a `Set` of a given `setname` +""" +@inline getnodeset(grid::AbstractGrid, setname::String) = grid.nodesets[setname] +""" + getnodesets(grid::AbstractGrid) + +Returns all nodesets of the `grid` +""" @inline getnodesets(grid::AbstractGrid) = grid.nodesets -"Accesses the faceset which is mapped to the key `set::String`" -@inline getfaceset(grid::AbstractGrid, set::String) = grid.facesets[set] -"Returns all facesets of the `grid`" +""" + getfaceset(grid::AbstractGrid, setname::String) + +Returns all faces as `FaceIndex` in a `Set` of a given `setname` +""" +@inline getfaceset(grid::AbstractGrid, setname::String) = grid.facesets[setname] +""" + getfacesets(grid::AbstractGrid) + +Returns all facesets of the `grid` +""" @inline getfacesets(grid::AbstractGrid) = grid.facesets -@inline getedgeset(grid::AbstractGrid, set::String) = grid.edgesets[set] +""" + getedgeset(grid::AbstractGrid, setname::String) + +Returns all edges as `EdgeIndex` in a `Set` of a given `setname` +""" +@inline getedgeset(grid::AbstractGrid, setname::String) = grid.edgesets[setname] +""" + getedgesets(grid::AbstractGrid) + +Returns all edge sets of the grid +""" @inline getedgesets(grid::AbstractGrid) = grid.edgesets -@inline getvertexset(grid::AbstractGrid, set::String) = grid.vertexsets[set] +""" + getedgeset(grid::AbstractGrid, setname::String) + +Returns all vertices as `VertexIndex` in a `Set` of a given `setname` +""" +@inline getvertexset(grid::AbstractGrid, setname::String) = grid.vertexsets[setname] +""" + getvertexsets(grid::AbstractGrid) + +Returns all vertex sets of the grid +""" @inline getvertexsets(grid::AbstractGrid) = grid.vertexsets n_faces_per_cell(grid::Grid) = nfaces(eltype(grid.cells)) @@ -236,8 +283,9 @@ _warn_emptyset(set) = length(set) == 0 && @warn("no entities added to set") addcellset!(grid::AbstractGrid, name::String, cellid::Union{Set{Int}, Vector{Int}}) addcellset!(grid::AbstractGrid, name::String, f::function; all::Bool=true) -Adds a `cellset::Dict{String,Set{Int}}` to the `grid` with key `name`. -Cellsets can be used to specify a boundary for `Dirichlet`, which is needed by the `ConstraintHandler` +Adds a cellset to the grid with key `name`. +Cellsets are typically used to define subdomains of the problem, e.g. two materials in the computational domain. +The `MixedDofHandler` can construct different fields which live not on the whole domain, but rather on a cellset. ```julia addcellset!(grid, "left", Set((1,3))) #add cells with id 1 and 3 to cellset left @@ -273,8 +321,8 @@ end addfaceset!(grid::AbstractGrid, name::String, faceid::Union{Set{FaceIndex},Vector{FaceIndex}}) addfaceset!(grid::AbstractGrid, name::String, f::Function; all::Bool=true) -Adds a `faceset::Dict{String, Set{Tuple{Int,Int}}` to the `grid` with key `name`. -A `faceset` maps a `String` key to a `Set` of tuples corresponding to `(global_cell_id, local_face_id)`. +Adds a faceset to the grid with key `name`. +A faceset maps a `String` key to a `Set` of tuples corresponding to `(global_cell_id, local_face_id)`. Facesets are used to initialize `Dirichlet` structs, that are needed to specify the boundary for the `ConstraintHandler`. ```julia