@@ -413,6 +413,12 @@ arguments `args` and keyword arguments `kwargs`.
413413 copy(K)`) instead.
414414"""
415415function allocate_matrix (:: Type{MatrixType} , dh:: DofHandler , args... ; kwargs... ) where {MatrixType}
416+ _get_Ti (:: Type{<:AbstractMatrix} ) = Int
417+ _get_Ti (:: Type{<:AbstractSparseMatrix{<:Any, Ti}} ) where {Ti} = Ti
418+ if _can_use_fastsp (MatrixType, args... ; kwargs... )
419+ fsp = FastSparsityPattern (_get_Ti (MatrixType), dh, args... ; kwargs... )
420+ return allocate_matrix (MatrixType, fsp)
421+ end
416422 sp = init_sparsity_pattern (dh)
417423 add_sparsity_entries! (sp, dh, args... ; kwargs... )
418424 return allocate_matrix (MatrixType, sp)
@@ -681,3 +687,198 @@ function _allocate_matrix(::Type{SparseMatrixCSC{Tv, Ti}}, sp::AbstractSparsityP
681687 S = SparseMatrixCSC (getnrows (sp), getncols (sp), colptr, rowval, nzval)
682688 return S
683689end
690+
691+ # # ================= ##
692+ # FastSparsityPattern #
693+ # # ================= ##
694+
695+ """
696+ FastSparsityPattern([Ti = Int64], dh::DofHandler)
697+
698+ This sparsity does not currently support the full `AbstractSparsityPattern` interface,
699+ but is used as an internal fast-path for `allocate_matrix(MatrixType, dh)` for some
700+ supported `MatrixType`s. It can be extended in the future or potentially be merged
701+ with `SparsityPattern`.
702+ See [#1302](https://github.com/Ferrite-FEM/Ferrite.jl/pull/1302) for details.
703+
704+ !!! warning "Internal"
705+ `FastSparsityPattern` is strictly internal and its interface and implementation
706+ may change at any time.
707+
708+ """
709+ mutable struct FastSparsityPattern{Ti} <: AbstractSparsityPattern
710+ const rowlen:: Vector{Ti} # Number of stored entries in each row
711+ const marker:: Vector{Ti} # Marker if column has been "visited" by certain row
712+ const rowptr:: Vector{Ti} # Index of stored entries at the start of each row
713+ const colidx:: Vector{Ti} # colidx[i] gives the column number of the ith stored entry
714+ is_colidx_sorted:: Bool # Is colidx sorted for each row
715+ end
716+ function FastSparsityPattern (:: Type{Ti} , ncols, nrows) where {Ti <: Integer }
717+ rowlen = zeros (Ti, nrows)
718+ marker = zeros (Ti, ncols)
719+ rowptr = Vector {Ti} (undef, nrows + 1 )
720+ colidx = Vector {Ti} (undef, 0 ) # To be resized later
721+ return FastSparsityPattern (rowlen, marker, rowptr, colidx, false )
722+ end
723+
724+ # _can_use_fastsp(MatrixType, args...; kwargs...) where args and kwargs are those passed to
725+ # `allocate_matrix`. See `add_sparsity_entries!` for a description of args/kwargs.
726+ function _can_use_fastsp (
727+ :: Type{MatrixType} ,
728+ ch = nothing ;
729+ topology = nothing ,
730+ keep_constrained = true ,
731+ coupling = nothing ,
732+ interface_coupling = nothing
733+ ) where {MatrixType}
734+ if ch === topology === coupling === interface_coupling === nothing
735+ if MatrixType <: AbstractSparseMatrix # Symmetric/Block matrices not supported
736+ return keep_constrained
737+ end
738+ end
739+ return false
740+ end
741+
742+ FastSparsityPattern (dh:: DofHandler ) = FastSparsityPattern (Int, dh)
743+ function FastSparsityPattern (:: Type{Ti} , dh:: DofHandler ) where {Ti}
744+ sp = FastSparsityPattern (Ti, ndofs (dh), ndofs (dh))
745+ # Step 1: Create cell_dof_views::ArrayOfVectorViews (would be nice in `DofHandler` directly)
746+ cell_dofs_views = create_celldofs (dh)
747+ # Step 2: Define mapping rownr to cells
748+ row_to_cells = create_row_to_cells (cell_dofs_views, sp)
749+ # Step 3: Count how many cols stored for each row
750+ count_row_sizes! (sp, row_to_cells, cell_dofs_views)
751+ # Step 4: Build the rowptr (indices for s)
752+ build_rowptr! (sp)
753+ fill_colidx! (sp, row_to_cells, cell_dofs_views)
754+ return sp
755+ end
756+
757+ getncols (sp:: FastSparsityPattern ) = length (sp. marker)
758+ getnrows (sp:: FastSparsityPattern ) = length (sp. rowlen)
759+
760+ function create_celldofs (dh:: DofHandler )
761+ isclosed (dh) || throw (ArgumentError (" DofHandler must be closed" ))
762+ ncells = getncells (dh. grid)
763+ indices = similar (dh. cell_dofs_offset, ncells + 1 )
764+ cell_dofs = similar (dh. cell_dofs)
765+ n = 1
766+ for cell_idx in 1 : ncells
767+ indices[cell_idx] = n
768+ num = ndofs_per_cell (dh, cell_idx)
769+ num == 0 && continue
770+ r = n: (n + num - 1 )
771+ # celldofs!(view(cell_dofs, r), dh, cell_idx), but faster without view:
772+ soffs = dh. cell_dofs_offset[cell_idx]
773+ copyto! (cell_dofs, n, dh. cell_dofs, soffs, num)
774+ n = last (r) + 1
775+ end
776+ indices[end ] = n
777+ return ArrayOfVectorViews (indices, cell_dofs, LinearIndices ((ncells,)))
778+ end
779+
780+ function create_row_to_cells (cell_dofs:: ArrayOfVectorViews , sp)
781+ nrows = getnrows (sp)
782+ num_cells = zeros (Int, nrows)
783+ # 1: Figure out how many cells are connected to each dof
784+ n_connected = 0
785+ @inbounds for rows in cell_dofs # dof = row
786+ for row in rows
787+ num_cells[row] += 1
788+ n_connected += 1
789+ end
790+ end
791+
792+ # 2: Create the correct datastructure
793+ data = Vector {Int} (undef, n_connected)
794+ indices = Vector {Int} (undef, nrows + 1 )
795+ indices[1 ] = 1
796+ @inbounds for row in 1 : nrows
797+ indices[row + 1 ] = indices[row] + num_cells[row]
798+ end
799+ fill! (num_cells, 0 ) # Now we use this to count how many have been added
800+ @inbounds for (cellnr, rows) in enumerate (cell_dofs)
801+ for row in rows
802+ data[indices[row] + num_cells[row]] = cellnr
803+ num_cells[row] += 1
804+ end
805+ end
806+ return ArrayOfVectorViews (indices, data, LinearIndices ((nrows,)))
807+ end
808+
809+ function count_row_sizes! (sp:: FastSparsityPattern , row_to_cells:: AbstractVector , cell_dofs:: ArrayOfVectorViews )
810+ @inbounds for row in 1 : getnrows (sp)
811+ for cnr in row_to_cells[row]
812+ for col in cell_dofs[cnr]
813+ if sp. marker[col] != row
814+ sp. marker[col] = row
815+ sp. rowlen[row] += 1
816+ end
817+ end
818+ end
819+ end
820+ return sp
821+ end
822+
823+ function build_rowptr! (sp)
824+ sp. rowptr[1 ] = 1
825+ @inbounds for row in 1 : getnrows (sp)
826+ sp. rowptr[row + 1 ] = sp. rowptr[row] + sp. rowlen[row]
827+ end
828+ return sp
829+ end
830+
831+ function fill_colidx! (sp:: FastSparsityPattern , row_to_cells:: AbstractVector , cell_dofs:: ArrayOfVectorViews )
832+ resize! (sp. colidx, sp. rowptr[end ] - 1 ) # nnz
833+ fill! (sp. marker, 0 )
834+ @inbounds for row in 1 : getnrows (sp)
835+ pos = sp. rowptr[row]
836+ for cnr in row_to_cells[row]
837+ for col in cell_dofs[cnr]
838+ if sp. marker[col] != row
839+ sp. marker[col] = row
840+ sp. colidx[pos] = col
841+ pos += 1
842+ end
843+ end
844+ end
845+ end
846+ return sp
847+ end
848+
849+ allocate_matrix (sp:: FastSparsityPattern ) = allocate_matrix (SparseMatrixCSC, sp)
850+ allocate_matrix (:: Type{SparseMatrixCSC} , sp:: FastSparsityPattern{Int} ) = allocate_matrix (SparseMatrixCSC{Float64, Int}, sp)
851+ function allocate_matrix (:: Type{<:SparseMatrixCSC{Tv, Ti}} , sp:: FastSparsityPattern{Ti} ) where {Ti, Tv}
852+ nnz = length (sp. colidx)
853+ ncols = getncols (sp)
854+ nrows = getnrows (sp)
855+
856+ # Number of stored entries per column
857+ collen = zeros (Ti, ncols)
858+ @inbounds for col in sp. colidx
859+ collen[col] += 1
860+ end
861+
862+ # Index of stored entries at the start of each column
863+ colptr = Vector {Ti} (undef, ncols + 1 )
864+ colptr[1 ] = 1
865+ @inbounds for col in 1 : ncols
866+ colptr[col + 1 ] = colptr[col] + collen[col]
867+ end
868+
869+ # Build rowidx[i] giving the row number of the ith stored entry
870+ rowidx = Vector {Ti} (undef, nnz)
871+ next = copy (colptr)
872+ @inbounds for row in 1 : nrows
873+ for p in sp. rowptr[row]: (sp. rowptr[row + 1 ] - 1 )
874+ col = sp. colidx[p]
875+ q = next[col]
876+ rowidx[q] = row
877+ next[col] = q + 1
878+ # For a given col, next[col] is increasing, and row is
879+ # increasing in the outer loop -> rowidx sorted for each col
880+ end
881+ end
882+ nzval = zeros (Tv, nnz)
883+ return SparseMatrixCSC (nrows, ncols, colptr, rowidx, nzval)
884+ end
0 commit comments