-
Notifications
You must be signed in to change notification settings - Fork 93
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
RFC: Introducing a MixedGrid
#445
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this PR; included a small suggestion. Would be a nice working example of using the grid interface. We should take care that functionality doesn't diverge too much. DofHandler
cannot use any other grid than Grid
. #379 tried to address it, but seems to be stalled. So with including something like this, it would be quite unnatural for new or not so experienced users to have a DofHandler
who can't take the MixedGrid
(which isn't probably that useful). Therefore, a long term strategy would be nice how this problem will be resolved.
nodes = grid.cells[i].nodes | ||
# shared implementation for all grids | ||
function cellcoords!(global_coords::Vector{Vec{dim,T}}, grid::AbstractGrid, cell::C) where {dim,C<:Ferrite.AbstractCell,T} | ||
nodes = cell.nodes | ||
N = length(nodes) | ||
@assert length(global_coords) == N | ||
for j in 1:N | ||
global_coords[j] = grid.nodes[nodes[j]].x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is an interface function which should be used, because the functionality of cellcoords!
can be recovered by the already defined interface functions. I think it would be worth considering to put this function in another file, e.g. in the grid.jl file, doesn't seem to be exclusively related the dofhandler
global_coords[j] = grid.nodes[nodes[j]].x | |
global_coords[j] = getnodes(grid,nodes[j]).x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using the interface function is definitely the right way to go.
I also agree that this should live in the grid file. I simply didn't move it yet so that it's easier to see the actual diff caused by this PR (and not the one by shuffling around functions).
edgesets::Dict{String,Set{EdgeIndex}} | ||
vertexsets::Dict{String,Set{VertexIndex}} | ||
# Boundary matrix (faces per cell × cell) | ||
boundary_matrix::SparseMatrixCSC{Bool,Int} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@termi-official is there a better way to encode the boundary information into the grid? Would be a good opportunity to include it in this struct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes and no. It really depends on your use case. You can get away without using the boundary_matrix
and I honestly think that it should not be in any grid struct, but in a separate (boundary information) data structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean we have the facesets data structure anyway to distinguish different portions of the boundary - and a set lookup should be O(1). If we want to check if we are on the interior, then the upcoming topology PR should handle this better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe @fredrikekre does know more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be open to removing this field - I don't use it myself. The reason that it's still there is to stay as close to existing code as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that removing the field is definitely a breaking change, so I do not think we can remove it immediately. (See
Line 90 in ea716bc
@inline onboundary(ci::CellIterator, face::Int) = ci.grid.boundary_matrix[face, ci.current_cellid[]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess the reason this exists is that it is faster than the set-lookup:
julia> grid = generate_grid(Triangle, (20, 20));
julia> ci = CellIterator(grid); reinit!(ci, 1);
julia> @btime onboundary($ci, $1);
4.300 ns (0 allocations: 0 bytes)
julia> faceindex = FaceIndex(1,1)
FaceIndex((1, 1))
julia> @btime in($faceindex, $(grid.facesets["bottom"]))
16.032 ns (0 allocations: 0 bytes)
So from that perspective, I'd suggest leaving it as it is for now and eventually tackle if it should be in the grid or not in another PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might make more sense to have it be a lookup for whether the cell has any face on a boundary. Then you can check once per cell, instead of once epr face. (This also works better with multi-celltype grids).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good PR to get a type-stable grid with mixed celltypes, and getting rid of the grid-representation in the MixedDofhandler. The MixedGrid is quite minimalistic, and fits into the grid api without changing other parts of the code too much.
Just one thought:
It seems like just type-annotating the normal Grid
(when it includes mixed celltypes) gives similar benchmarks when calling cellcords!(...)
, as compared to with your PR, e.g.:
function f1(coords, grid::Grid{2,Ferrite.AbstractCell,Float64}, cellset::Set{Int})
for i in cellset
cell::Quadrilateral = grid.cells[i]; #type annotate
Ferrite.cellcoords!(coords, grid, cell)
#...
end
end
I think it is typically the case that one can type-annotate their cell-loops (that is at least the case in my code), so It is worth considering if we want want to add a completely new Grid-type, if we dont gain that much?
Personally I would probably use the type-annotation approach above, but this PR supports that as well, so that is nice.
|
||
function MixedGrid(cells::C, | ||
nodes::Vector{Node{dim,T}}; | ||
cellsets::Dict{String,Set{Int}}=Dict{String,Set{Int}}(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess cellsets
should not be a vector of Ints in the MixedGrid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, working on that :)
end | ||
|
||
struct CellId{I} | ||
i::Int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the cell id as a type parameter is clever, but can give some trouble if one has a cellset with different celltypes: https://docs.julialang.org/en/v1/manual/performance-tips/#The-dangers-of-abusing-multiple-dispatch-(aka,-more-on-types-with-values-as-parameters)
I dont think that is necessarily that common though...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's actually inherently needed here, so that we can know the celltype in a type stable manner. Of course that means that collections of cells with different I
should be avoided. That is where this PR is coming from though, the purpose it to store the cells of the same type together.
It however does become a little tricky especially with facesets. I'll comment on that when I've pushed the changes on the different sets.
return dh, vertexdicts, edgedicts, facedicts | ||
|
||
end | ||
|
||
get_sorted_cellset(fh::FieldHandler{Int}) = sort!(collect(fh.cellset)) | ||
get_sorted_cellset(fh::FieldHandler{CellId{I}}) where I = sort!(collect(fh.cellset), by=c->c.i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define >
and <
for a CellId
instead?
@@ -41,7 +41,7 @@ struct CellIterator{dim,C,T,DH<:Union{AbstractDofHandler,Nothing}} | |||
dh::Union{DH,Nothing} | |||
celldofs::Vector{Int} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like the CellIterator will still have an Abstract celltype?
This PR doesn't seem necessary for fixing that but would enable lifting the type instability higher up (i.e. making the creation of CellIterator type-stable also) !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't done anything on the CellIterator
yet, I'd hope fixing the CellIterator
for mixed grids would come naturally once we agree on a grid format.
@@ -0,0 +1,51 @@ | |||
mutable struct MixedGrid{dim, C, T<:Real} <: Ferrite.AbstractGrid{dim} | |||
cells::C # Tuple of concretely typed cell vectors | |||
ncells_per_vector::Vector{Int} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ncells_per_vector
needed?
Did some quick benchmarks for getncells
and couldn't see that it would be faster (actually slower but so fast that it wouldn't matter anyway)
I see that this PR has quite some impact on the microbenchmarks, but does it really have a significant impact on overall assembly performance for non-trivial assembly loops? I mean first, we cut down the cost of calls which are usually not the most costly ones by several nanoseconds. Second, this PR feels a bit contrary to the goal of merging the DofHandler and MixedDofhandler, since we diversify the code base further. From a conceptual point, f we want to merge this code, wouldn't it be more beneficial if the MixedGrid replaces the current Grid? This also cuts maintainance cost. |
Superseded by the DofHandler merge. |
Following up on our discussion from last week, here comes a suggestion on how to handle mixed grids in a more data-oriented manner.
The main gripes about the current implementation are:
Vector{Union{CellType1, CelType2,...}}
or even worse asVector{AbstractCell}
. This makes extracting a cell based on its global id inherently type unstable.MixedDofHandler
stores its own grid representation so that it can efficiently access cell nodes and cell coordinates.DofHandler
&MixedDofHandler
. Mixed Celltypes in a grid are now reflected by need to useMixedDofHandler
. It would be nice to reflect this in theGrid
datatype instead and use a singleDofHandler
no matter what the grid type. (Currently theDofHandler
additionally does not support fields on subsets of the domain, while theMixedDofHandler
does.)MixedDofHandler
, it is crucial for performance to not iterate over all cells at once. Instead a function barrier should be set for eachFieldHandler
. This is not mentioned in our docs.This PR attempts to solve the first two points and lay ground for solving the 3rd in the future.
MixedGrid type
It introduces a
MixedGrid
that stores its cells asTuple{Vector{CellType1}, Vector{CellType2}, ...}
.The cells in the grid are indexed by
where
I
is the index within the tuple andi
is the index within theVector
. This can be seen as local cell id. Cells also have global cell ids (Int
s), which count through the cells tuple from beginning to end. Global ids can thus easily computed from local ids.This means that cells will likely be renumbered when importing a mixed grid from an external grid generator. I don't see this as a huge problem, but an alternative approach could be to store a mapping local id -> global id.
MixedDofHandler + MixedGrid
A grid with mixed celltypes should always be represented as
MixedGrid
and always be indexed byCellId
. That is, theFieldHandler
s then store a (concretely typed!)Set{CellId{I}}
and all cellsets + facesets in the mixed grid should reference cells byCellId
instead of their global id (the latter isn't implemented yet for this PR).The
MixedDofHandler
does not store cell nodes and cell coordinates anymore. Methods for extracting them fall back to the implementations for the underlying grid.The
MixedDofHandler
allows to use eitherGrid
orMixedGrid
with it. Therefore, thecelldofs
are still stored by global cell ids.DofHandler methods + Benchmarking
Here is a list of all functions that use the DofHandlers (to get an overview). Some methods are defined for
AbstractDofHandler
, but will only work for theDofHandler
, thus they're listed on the left.getfielddim(dh::DofHandler, field_idx::Int)
getfielddim(dh::DofHandler, name::Symbol)
getfielddim(dh::MixedDofHandler, name::Symbol)
ndim(dh::AbstractDofHandler, field_name::Symbol)
, same functionality as getfielddimnfields(dh::AbstractDofHandler)
, see #444nfields(dh::MixedDofHandler)
ndofs_per_cell(dh::AbstractDofHandler, cell::Int=1)
ndofs_per_cell(dh::MixedDofHandler, cell::Int=1)
getfieldnames(dh::AbstractDofHandler)
getfieldnames(dh::MixedDofHandler)
,getfieldnames(fh::FieldHandler)
find_field(dh::DofHandler, field_name::Symbol)
find_field(fh::FieldHandler, field_name::Symbol)
field_offset(dh::DofHandler, field_name::Symbol)
field_offset(fh::FieldHandler, field_name::Symbol)
dof_range(dh::DofHandler, field_name::Symbol)
dof_range(fh::FieldHandler, field_name::Symbol)
getfieldinterpolation(dh::DofHandler, field_idx::Int)
getfieldinterpolations(fh::FieldHandler)
cellcoords!(global_coords::Vector{<:Vec}, dh::DofHandler, i::Int)
cellcoords!(global_coords::Vector{Vec{dim,T}}, dh::MixedDofHandler, i::Int) where {dim,T}
celldofs!(global_dofs::Vector{Int}, dh::DofHandler, i::Int)
celldofs!(global_dofs::Vector{Int}, dh::MixedDofHandler, i::Int)
celldofs(dh::DofHandler, i::Int)
celldofs(dh::MixedDofHandler, i::Int)
cellnodes!(global_nodes::Vector{Int}, dh::MixedDofHandler, i::Int)
Most of these are used for dof distribution, I'd expect that only
celldofs!
andcellcoords!
are used in performance relevant code, so let's focus on these for some micro benchmarks. The benchmarks extract cellcoords and celldofs for aQuadrilateral
in a mixed grid withTriangle
s andQuadrilateral
s.DofHandler
MixedDofHandler#master
+ mixed grid
MixedDofHandler#thisPR
+ mixed grid
MixedDofHandler#thisPR
+ concrete grid
cellcoords!
celldofs!
For this case, the
MixedDofHandler
now performs equally well as theDofHandler
, no matter if it is used withGrid
or withMixedGrid
.Technically, it was possible to use the grid version of
cellcoords!
before this PR (and thus get rid of the grid representation in theMixedDofHandler
), however that can be (very) slow:Quadrilateral
(concretly typed)
Union{Triangle, Quadrilateral}
AbstractCell
cellcoords!
(xe, grid, i)
Usage
The usage pattern remains exactly the same as before.