Skip to content

Commit ebf63c9

Browse files
Modularize and document assembly internals, add SparseMatrixCSR extension (#864)
Also introduces `CSRAssembler` for general CSR matrices.
1 parent c718c73 commit ebf63c9

File tree

10 files changed

+396
-56
lines changed

10 files changed

+396
-56
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Next] - xxxx-xx-xx
9+
10+
### Added
11+
- Support for directly assembling to `SparseMatrixCSR` (from `SparseMatricesCSR.jl`). ([#864])
12+
13+
### Documentation updates
14+
- Extended assembly docs with information on how to support direct assembly into new matrix types. ([#864])
15+
816
## [v1.1.0] - 2025-05-01
917

1018
### Added

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,20 @@ WriteVTK = "64499a7a-5c06-52f2-abe2-ccb03c286192"
1818
[weakdeps]
1919
BlockArrays = "8e7c35d0-a365-5155-bbbb-fb81a777f24e"
2020
Metis = "2679e427-3c69-5b7f-982b-ece356f1e94b"
21+
SparseMatricesCSR = "a0a7dd2c-ebf4-11e9-1f05-cf50bc540ca1"
2122

2223
[extensions]
2324
FerriteBlockArrays = "BlockArrays"
2425
FerriteMetis = "Metis"
26+
FerriteSparseMatrixCSR = "SparseMatricesCSR"
2527

2628
[compat]
2729
BlockArrays = "0.16, 1"
2830
EnumX = "1"
2931
HCubature = "1.7.0"
3032
ForwardDiff = "0.10, 1"
3133
Metis = "1.3"
34+
SparseMatricesCSR = "0.6"
3235
NearestNeighbors = "0.4"
3336
OrderedCollections = "1"
3437
Preferences = "1"
@@ -53,9 +56,10 @@ ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
5356
HCubature = "19dc6840-f33b-545b-b366-655c7e3ffd49"
5457
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
5558
SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce"
59+
SparseMatricesCSR = "a0a7dd2c-ebf4-11e9-1f05-cf50bc540ca1"
5660
TaskLocalValues = "ed4db957-447d-4319-bfb6-7fa9ae7ecf34"
5761
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5862
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
5963

6064
[targets]
61-
test = ["BlockArrays", "Downloads", "FerriteGmsh", "ForwardDiff", "Gmsh", "IterativeSolvers", "Metis", "NBInclude", "OhMyThreads", "ProgressMeter", "Random", "SHA", "TaskLocalValues", "Test", "TimerOutputs", "Logging", "HCubature"]
65+
test = ["BlockArrays", "Downloads", "FerriteGmsh", "ForwardDiff", "Gmsh", "IterativeSolvers", "Metis", "NBInclude", "OhMyThreads", "ProgressMeter", "Random", "SHA", "SparseMatricesCSR", "TaskLocalValues", "Test", "TimerOutputs", "Logging", "HCubature"]

docs/src/devdocs/assembly.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,48 @@
11
# [Assembly](@id devdocs-assembly)
22

3+
The assembler handles the insertion of the element matrices and element vectors into the system matrix and right hand side.
4+
5+
## Custom matrix formats
6+
While the CSC and CSR formats are the most common sparse matrix formats in practice, users might want to have optimized custom matrix formats for their specific use-case. The default assemblers [`Ferrite.CSCAssembler`](@ref) and [`Ferrite.CSRAssembler`](@ref) should be able to handle most cases in practice. To support a custom format users have to dispatch the following functions on their matrix type. There is the public interface
7+
8+
```@docs; canonical=false
9+
Ferrite.allocate_matrix
10+
```
11+
12+
the internal interface
13+
```@docs
14+
Ferrite.zero_out_rows!
15+
Ferrite.zero_out_columns!
16+
Ferrite._condense!
17+
```
18+
19+
and the `AbstractSparseMatrix` interface for their custom matrix type. Optional dispatches to speed up operations might be
20+
21+
```@docs
22+
Ferrite.add_inhomogeneities!
23+
```
24+
25+
## Custom Assembler
26+
27+
In case the default assembler is insufficient, users can implement a custom assemblers. For this, they can create a custom type and dispatch the following functions.
28+
29+
```@docs; canonical=false
30+
start_assemble
31+
assemble!
32+
```
33+
34+
For local elimination support the following functions might also need custom dispatches
35+
36+
```@docs
37+
Ferrite._condense_local!
38+
```
39+
340
## Type definitions
441

542
```@docs
643
Ferrite.COOAssembler
744
Ferrite.CSCAssembler
45+
Ferrite.CSRAssembler
846
Ferrite.SymmetricCSCAssembler
947
```
1048

ext/FerriteSparseMatrixCSR.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
module FerriteSparseMatrixCSR
2+
3+
using Ferrite, SparseArrays, SparseMatricesCSR
4+
import Ferrite: AbstractSparsityPattern, CSRAssembler
5+
import Base: @propagate_inbounds
6+
7+
# Could be generalized if https://github.com/JuliaSparse/SparseArrays.jl/pull/546 is merged
8+
function Ferrite.start_assemble(K::SparseMatrixCSR{<:Any, T}, f::Vector = T[]; fillzero::Bool = true, maxcelldofs_hint::Int = 0) where {T}
9+
fillzero && (Ferrite.fillzero!(K); Ferrite.fillzero!(f))
10+
return CSRAssembler(K, f, zeros(Int, maxcelldofs_hint), zeros(Int, maxcelldofs_hint))
11+
end
12+
13+
@propagate_inbounds function Ferrite._assemble_inner!(K::SparseMatrixCSR, Ke::AbstractMatrix, dofs::AbstractVector, sorteddofs::AbstractVector, permutation::AbstractVector, sym::Bool)
14+
current_row = 1
15+
ld = length(dofs)
16+
return @inbounds for Krow in sorteddofs
17+
maxlookups = sym ? current_row : ld
18+
Kerow = permutation[current_row]
19+
ci = 1 # col index pointer for the local matrix
20+
Ci = 1 # col index pointer for the global matrix
21+
nzr = nzrange(K, Krow)
22+
while Ci <= length(nzr) && ci <= maxlookups
23+
C = nzr[Ci]
24+
Kcol = K.colval[C]
25+
Kecol = permutation[ci]
26+
val = Ke[Kerow, Kecol]
27+
if Kcol == dofs[Kecol]
28+
# Match: add the value (if non-zero) and advance the pointers
29+
if !iszero(val)
30+
K.nzval[C] += val
31+
end
32+
ci += 1
33+
Ci += 1
34+
elseif Kcol < dofs[Kecol]
35+
# No match yet: advance the global matrix row pointer
36+
Ci += 1
37+
else # Kcol > dofs[Kecol]
38+
# No match: no entry exist in the global matrix for this row. This is
39+
# allowed as long as the value which would have been inserted is zero.
40+
iszero(val) || Ferrite._missing_sparsity_pattern_error(Krow, Kcol)
41+
# Advance the local matrix row pointer
42+
ci += 1
43+
end
44+
end
45+
# Make sure that remaining entries in this column of the local matrix are all zero
46+
for i in ci:maxlookups
47+
if !iszero(Ke[Kerow, permutation[i]])
48+
Ferrite._missing_sparsity_pattern_error(Krow, sorteddofs[i])
49+
end
50+
end
51+
current_row += 1
52+
end
53+
end
54+
55+
function Ferrite.zero_out_rows!(K::SparseMatrixCSR, ch::ConstraintHandler)
56+
@debug @assert issorted(ch.prescribed_dofs)
57+
for row in ch.prescribed_dofs
58+
r = nzrange(K, row)
59+
K.nzval[r] .= 0.0
60+
end
61+
return
62+
end
63+
64+
function Ferrite.zero_out_columns!(K::SparseMatrixCSR, ch::ConstraintHandler)
65+
colval = K.colval
66+
nzval = K.nzval
67+
return @inbounds for i in eachindex(colval, nzval)
68+
if haskey(ch.dofmapping, colval[i])
69+
nzval[i] = 0
70+
end
71+
end
72+
end
73+
74+
function Ferrite.allocate_matrix(::Type{SparseMatrixCSR}, sp::AbstractSparsityPattern)
75+
return Ferrite.allocate_matrix(SparseMatrixCSR{1, Float64, Int64}, sp)
76+
end
77+
78+
function Ferrite.allocate_matrix(::Type{SparseMatrixCSR{1, Tv, Ti}}, sp::AbstractSparsityPattern) where {Tv, Ti}
79+
return _allocate_matrix(SparseMatrixCSR{1, Tv, Ti}, sp, false)
80+
end
81+
82+
function _allocate_matrix(::Type{SparseMatrixCSR{1, Tv, Ti}}, sp::AbstractSparsityPattern, sym::Bool) where {Tv, Ti}
83+
# 1. Setup rowptr
84+
rowptr = zeros(Ti, Ferrite.getnrows(sp) + 1)
85+
rowptr[1] = 1
86+
for (row, colidxs) in enumerate(Ferrite.eachrow(sp))
87+
for col in colidxs
88+
sym && row > col && continue
89+
rowptr[row + 1] += 1
90+
end
91+
end
92+
cumsum!(rowptr, rowptr)
93+
nnz = rowptr[end] - 1
94+
# 2. Allocate colval and nzval now that nnz is known
95+
colval = Vector{Ti}(undef, nnz)
96+
nzval = zeros(Tv, nnz)
97+
# 3. Populate colval.
98+
k = 1
99+
for (row, colidxs) in zip(1:Ferrite.getnrows(sp), Ferrite.eachrow(sp)) # pairs(eachrow(sp))
100+
for col in colidxs
101+
sym && row > col && continue
102+
colval[k] = col
103+
k += 1
104+
end
105+
end
106+
S = SparseMatrixCSR{1}(Ferrite.getnrows(sp), Ferrite.getncols(sp), rowptr, colval, nzval)
107+
return S
108+
end
109+
110+
end

0 commit comments

Comments
 (0)