diff --git a/src/Dofs/DofHandler.jl b/src/Dofs/DofHandler.jl index 0fa8cad0bc..5536bb91cf 100644 --- a/src/Dofs/DofHandler.jl +++ b/src/Dofs/DofHandler.jl @@ -96,6 +96,7 @@ end mutable struct DofHandler{dim,G<:AbstractGrid{dim}} <: AbstractDofHandler const subdofhandlers::Vector{SubDofHandler{DofHandler{dim, G}}} const field_names::Vector{Symbol} + const global_dofs::OrderedDict{Symbol, UnitRange{Int}} # Dofs for cell i are stored in cell_dofs at the range: # cell_dofs_offset[i]:(cell_dofs_offset[i]+ndofs_per_cell(dh, i)-1) const cell_dofs::Vector{Int} @@ -131,7 +132,8 @@ close!(dh) function DofHandler(grid::G) where {dim, G <: AbstractGrid{dim}} ncells = getncells(grid) sdhs = SubDofHandler{DofHandler{dim, G}}[] - DofHandler{dim, G}(sdhs, Symbol[], Int[], zeros(Int, ncells), zeros(Int, ncells), false, grid, -1) + global_dofs = OrderedDict{Symbol, UnitRange{Int}}() + DofHandler{dim, G}(sdhs, Symbol[], global_dofs, Int[], zeros(Int, ncells), zeros(Int, ncells), false, grid, -1) end function Base.show(io::IO, mime::MIME"text/plain", dh::DofHandler) @@ -151,6 +153,10 @@ function Base.show(io::IO, mime::MIME"text/plain", dh::DofHandler) println(io, " ", repr(fieldname), ", ", field_type) end end + if length(dh.global_dofs) > 0 + gdof_str = join([string(key, " (", length(r), ")") for (key, r) in dh.global_dofs], ", ") + println(io, " Global dofs: ", gdof_str) + end if !isclosed(dh) print(io, " Not closed!") else @@ -322,6 +328,29 @@ function add!(dh::DofHandler, name::Symbol, ip::Interpolation) return dh end +""" + add_global_dofs!(dh::DofHandler, name::Symbol, n::Int) + +Add `n` global degrees of freedom to `dh`. The degree of freedom numbers can be obtained +after closing `dh` with [`global_dof_range`](@ref). +""" +function add_global_dofs!(dh::DofHandler, name::Symbol, n::Int) + @assert !isclosed(dh) + haskey(dh.global_dofs, name) && throw(ArgumentError(string(":", name, " is already a global dof"))) + dh.global_dofs[name] = (-n):(-1) # Range shifted in close +end + +""" + global_dof_range(dh::DofHandler, name::Symbol) + +Get a `UnitRange{Int}` describing the global dofs associated with `name`. +Such dofs can be added with [`add_global_dofs!`](@ref). +""" +function global_dof_range(dh::DofHandler, name::Symbol) + @assert isclosed(dh) + return dh.global_dofs[name] +end + """ close!(dh::AbstractDofHandler) @@ -388,7 +417,12 @@ function __close!(dh::DofHandler{dim}) where {dim} facedicts, ) end - dh.ndofs = maximum(dh.cell_dofs; init=0) + n_dofs = maximum(dh.cell_dofs; init=0) + for (key, dofs) in dh.global_dofs + dh.global_dofs[key] = n_dofs .+ (1:length(dofs)) + n_dofs += length(dofs) + end + dh.ndofs = n_dofs dh.closed = true return dh, vertexdicts, edgedicts, facedicts diff --git a/src/Ferrite.jl b/src/Ferrite.jl index ca5ef421e5..0f7d9a740a 100644 --- a/src/Ferrite.jl +++ b/src/Ferrite.jl @@ -12,7 +12,7 @@ using LinearAlgebra: using NearestNeighbors: NearestNeighbors, KDTree, knn using OrderedCollections: - OrderedSet + OrderedSet, OrderedDict using SparseArrays: SparseArrays, SparseMatrixCSC, nonzeros, nzrange, rowvals, sparse using StaticArrays: diff --git a/test/test_grid_dofhandler_vtk.jl b/test/test_grid_dofhandler_vtk.jl index 3ae5d15124..c35327734d 100644 --- a/test/test_grid_dofhandler_vtk.jl +++ b/test/test_grid_dofhandler_vtk.jl @@ -102,6 +102,63 @@ end # of testset close(csio) +@testset "DofHandler: global dofs" begin + grid = generate_grid(Quadrilateral, (5, 5)) + ip = Lagrange{RefQuadrilateral, 1}() + + # dh1: One standard field + dh1 = close!(add!(DofHandler(grid), :u, ip)) + + # dh1a: dh1 + one global dofs entry + dh1a = DofHandler(grid) + add!(dh1a, :u, ip) + Ferrite.add_global_dofs!(dh1a, :λ, 2) + @test_throws ArgumentError Ferrite.add_global_dofs!(dh1a, :λ, 3) # Field already existing + @test_throws AssertionError Ferrite.global_dof_range(dh1a, :λ) # Not closed + close!(dh1a) + @test_throws AssertionError Ferrite.add_global_dofs!(dh1a, :x, 2) # Closed + @test ndofs(dh1a) == ndofs(dh1) + 2 + @test Ferrite.global_dof_range(dh1a, :λ) == (ndofs(dh1)+1):(ndofs(dh1)+2) # global dofs added at the end + + showstring = sprint(show, MIME"text/plain"(), dh1a) + @test contains(showstring, "Global dofs: λ (2)") + + # dh1b: dh1 + two global dofs entries, added before and after standard field. + dh1b = DofHandler(grid) + Ferrite.add_global_dofs!(dh1b, :λ1, 2) + add!(dh1b, :u, ip) + Ferrite.add_global_dofs!(dh1b, :λ2, 1) + close!(dh1b) + @test ndofs(dh1b) == ndofs(dh1) + 3 + @test Ferrite.global_dof_range(dh1b, :λ1) == (ndofs(dh1)+1):(ndofs(dh1)+2) + @test Ferrite.global_dof_range(dh1b, :λ2) == (ndofs(dh1)+3):(ndofs(dh1)+3) + + showstring = sprint(show, MIME"text/plain"(), dh1b) + @test contains(showstring, "Global dofs: λ1 (2), λ2 (1)") + + # dh2: Use subdofhandlers, dh2a adds global fields in addition to the standard fields in dh2 + domain1 = Ferrite.create_cellset(grid, x -> norm(x) < 0.75) + domain2 = setdiff!(Set(1:getncells(grid)), domain1) + + (dh2, dh2a) = map((false, true)) do add_globals + dh = DofHandler(grid) + add_globals && Ferrite.add_global_dofs!(dh, :λ1, 1) + sdh1 = SubDofHandler(dh, domain1) + add!(sdh1, :s, ip) + add!(sdh1, :v, ip^2) + add_globals && Ferrite.add_global_dofs!(dh, :λ2, 2) + sdh2 = SubDofHandler(dh, domain2) + add!(sdh2, :v, ip^2) + add_globals && Ferrite.add_global_dofs!(dh, :λ3, 3) + close!(dh) + dh + end + @test ndofs(dh2a) == ndofs(dh2) + 6 + @test Ferrite.global_dof_range(dh2a, :λ1) == ndofs(dh2) .+ (1:1) + @test Ferrite.global_dof_range(dh2a, :λ2) == ndofs(dh2) .+ (2:3) + @test Ferrite.global_dof_range(dh2a, :λ3) == ndofs(dh2) .+ (4:6) +end + @testset "vtk tensor export" begin # open files checksums_file_tensors = joinpath(dirname(@__FILE__), "checksums2.sha1")