-
Notifications
You must be signed in to change notification settings - Fork 99
Modularize assembly internals and add SparseMatrixCSR extension #864
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
Changes from 20 commits
7edd47a
7c8e7b1
a29dbb0
d8589ee
26c0f58
056f5c3
f021d01
46f6428
4a47a3f
d227ef7
0c3efc8
a2160e0
8102774
cfabf75
0328f24
d20389a
ed365f1
29888f6
60bf45f
0b5a861
d653475
896fee1
3976701
43b755f
debdf9c
d7f2cc5
512eaac
cdc828e
6f80dfd
0f42594
4b45329
e2ded19
a18fdff
6a472b4
f1bb29f
a503c00
3ed968f
832f586
47fc786
17c5068
4793c2f
d95b5c6
5d2e82c
af13a8f
9f4ab68
0cce299
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# [Assembler](@id devdocs-assembler) | ||
|
||
The assembler handles the insertion of the element matrices and element vectors into the system matrix and right hand side. 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 assembler [`AssemblerSparsityPattern`](@ref) should be able to handle most cases in practice. To support a custom format users have to dispatch the following functions: | ||
|
||
```@docs | ||
Ferrite._assemble_inner! | ||
Ferrite.zero_out_rows! | ||
Ferrite.zero_out_columns! | ||
``` | ||
|
||
and the `AbstractSparseMatrix` interface for their custom matrix type. Optional dispatches to speed up operations might be | ||
|
||
```@docs | ||
Ferrite.add_inhomogeneities! | ||
``` | ||
|
||
## Custom Assembler | ||
|
||
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. | ||
|
||
```@docs | ||
Ferrite.matrix_handle | ||
Ferrite.vector_handle | ||
start_assemble! | ||
finish_assemble! | ||
assemble! | ||
``` | ||
|
||
For local elimination support the following functions might also need custom dispatches | ||
|
||
```@docs | ||
Ferrite._condense_local! | ||
``` | ||
|
||
## Custom Matrix Type Sparsity Pattern | ||
|
||
TODO `create_sparsity_pattern` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
module FerriteSparseMatrixCSR | ||
|
||
using Ferrite, SparseArrays, SparseMatricesCSR | ||
import Ferrite: AbstractSparsityPattern | ||
import Base: @propagate_inbounds | ||
|
||
@propagate_inbounds function Ferrite._assemble_inner!(K::SparseMatrixCSR, Ke::AbstractMatrix, dofs::AbstractVector, sorteddofs::AbstractVector, permutation::AbstractVector, sym::Bool) | ||
current_row = 1 | ||
ld = length(dofs) | ||
@inbounds for Krow in sorteddofs | ||
maxlookups = sym ? current_row : ld | ||
Kerow = permutation[current_row] | ||
ci = 1 # col index pointer for the local matrix | ||
Ci = 1 # col index pointer for the global matrix | ||
nzr = nzrange(K, Krow) | ||
while Ci <= length(nzr) && ci <= maxlookups | ||
C = nzr[Ci] | ||
Kcol = K.colval[C] | ||
Kecol = permutation[ci] | ||
val = Ke[Kerow, Kecol] | ||
if Kcol == dofs[Kecol] | ||
# Match: add the value (if non-zero) and advance the pointers | ||
if !iszero(val) | ||
K.nzval[C] += val | ||
end | ||
ci += 1 | ||
Ci += 1 | ||
elseif Kcol < dofs[Kecol] | ||
# No match yet: advance the global matrix row pointer | ||
Ci += 1 | ||
else # Kcol > dofs[Kecol] | ||
# No match: no entry exist in the global matrix for this row. This is | ||
# allowed as long as the value which would have been inserted is zero. | ||
iszero(val) || Ferrite._missing_sparsity_pattern_error(Krow, Kcol) | ||
# Advance the local matrix row pointer | ||
ci += 1 | ||
end | ||
end | ||
# Make sure that remaining entries in this column of the local matrix are all zero | ||
for i in ci:maxlookups | ||
if !iszero(Ke[Kerow, permutation[i]]) | ||
Ferrite._missing_sparsity_pattern_error(Krow, sorteddofs[i]) | ||
end | ||
end | ||
current_row += 1 | ||
end | ||
end | ||
|
||
function Ferrite.zero_out_rows!(K::SparseMatrixCSR, ch::ConstraintHandler) # can be removed in 0.7 with #24711 merged | ||
@debug @assert issorted(ch.prescribed_dofs) | ||
for row in ch.prescribed_dofs | ||
r = nzrange(K, row) | ||
K.nzval[r] .= 0.0 | ||
end | ||
end | ||
|
||
function Ferrite.zero_out_columns!(K::SparseMatrixCSR, ch::ConstraintHandler) | ||
colval = K.colval | ||
nzval = K.nzval | ||
@inbounds for i in eachindex(colval, nzval) | ||
if haskey(ch.dofmapping, colval[i]) | ||
nzval[i] = 0 | ||
end | ||
end | ||
end | ||
|
||
|
||
function Ferrite.allocate_matrix(::Type{SparseMatrixCSR}, sp::AbstractSparsityPattern) | ||
return allocate_matrix(SparseMatrixCSR{1,Float64,Int}, sp) | ||
end | ||
|
||
function Ferrite.allocate_matrix(::Type{MatrixType}, sp::AbstractSparsityPattern) where {Bi, Tv, Ti, MatrixType <: SparseMatrixCSR{Bi, Tv, Ti}} | ||
# Allocate CSC first ... | ||
K = allocate_matrix(SparseMatrixCSC{Tv, Ti}, sp) | ||
# ... and transform to SparseMatrixCSR | ||
return SparseMatrixCSR{Bi}(transpose(sparse(transpose(K)))) | ||
termi-official marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -495,7 +495,7 @@ end | |||||
|
||||||
""" | ||||||
|
||||||
apply!(K::SparseMatrixCSC, rhs::AbstractVector, ch::ConstraintHandler) | ||||||
apply!(K::AbstractSparseMatrix, rhs::AbstractVector, ch::ConstraintHandler) | ||||||
|
||||||
Adjust the matrix `K` and right hand side `rhs` to account for the Dirichlet boundary | ||||||
conditions specified in `ch` such that `K \\ rhs` gives the expected solution. | ||||||
|
@@ -587,11 +587,11 @@ function _apply_v(v::AbstractVector, ch::ConstraintHandler, apply_zero::Bool) | |||||
return v | ||||||
end | ||||||
|
||||||
function apply!(K::Union{SparseMatrixCSC,Symmetric}, ch::ConstraintHandler) | ||||||
function apply!(K::Union{AbstractSparseMatrix,Symmetric}, ch::ConstraintHandler) | ||||||
apply!(K, eltype(K)[], ch, true) | ||||||
end | ||||||
|
||||||
function apply_zero!(K::Union{SparseMatrixCSC,Symmetric}, f::AbstractVector, ch::ConstraintHandler) | ||||||
function apply_zero!(K::Union{AbstractSparseMatrix,Symmetric}, f::AbstractVector, ch::ConstraintHandler) | ||||||
apply!(K, f, ch, true) | ||||||
end | ||||||
|
||||||
|
@@ -600,7 +600,7 @@ end | |||||
const APPLY_TRANSPOSE = ApplyStrategy.Transpose | ||||||
const APPLY_INPLACE = ApplyStrategy.Inplace | ||||||
|
||||||
function apply!(KK::Union{SparseMatrixCSC,Symmetric}, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false; | ||||||
function apply!(KK::Union{AbstractSparseMatrix,Symmetric{<:Any,<:AbstractSparseMatrix}}, f::AbstractVector, ch::ConstraintHandler, applyzero::Bool=false; | ||||||
strategy::ApplyStrategy.T=ApplyStrategy.Transpose) | ||||||
@assert isclosed(ch) | ||||||
sym = isa(KK, Symmetric) | ||||||
|
@@ -612,43 +612,14 @@ function apply!(KK::Union{SparseMatrixCSC,Symmetric}, f::AbstractVector, ch::Con | |||||
m = meandiag(K) # Use the mean of the diagonal here to not ruin things for iterative solver | ||||||
|
||||||
# Add inhomogeneities to f: (f - K * ch.inhomogeneities) | ||||||
if !applyzero | ||||||
@inbounds for i in 1:length(ch.inhomogeneities) | ||||||
d = ch.prescribed_dofs[i] | ||||||
v = ch.inhomogeneities[i] | ||||||
if v != 0 | ||||||
for j in nzrange(K, d) | ||||||
r = K.rowval[j] | ||||||
sym && r > d && break # don't look below diagonal | ||||||
f[r] -= v * K.nzval[j] | ||||||
end | ||||||
end | ||||||
end | ||||||
if sym | ||||||
# In the symmetric case, for a constrained dof `d`, we handle the contribution | ||||||
# from `K[1:d, d]` in the loop above, but we are still missing the contribution | ||||||
# from `K[(d+1):size(K,1), d]`. These values are not stored, but since the | ||||||
# matrix is symmetric we can instead use `K[d, (d+1):size(K,1)]`. Looping over | ||||||
# rows is slow, so loop over all columns again, and check if the row is a | ||||||
# constrained row. | ||||||
@inbounds for col in 1:size(K, 2) | ||||||
for ri in nzrange(K, col) | ||||||
row = K.rowval[ri] | ||||||
row >= col && break | ||||||
if (i = get(ch.dofmapping, row, 0); i != 0) | ||||||
f[col] -= ch.inhomogeneities[i] * K.nzval[ri] | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
!applyzero && add_inhomogeneities!(KK, f, ch.inhomogeneities, ch.prescribed_dofs, ch.dofmapping) | ||||||
|
||||||
# Condense K (C' * K * C) and f (C' * f) | ||||||
# Condense K := (C' * K * C) and f := (C' * f) | ||||||
_condense!(K, f, ch.dofcoefficients, ch.dofmapping, sym) | ||||||
|
||||||
# Remove constrained dofs from the matrix | ||||||
zero_out_columns!(K, ch.prescribed_dofs) | ||||||
zero_out_rows!(K, ch.dofmapping) | ||||||
zero_out_columns!(K, ch) | ||||||
zero_out_rows!(K, ch) | ||||||
|
||||||
# Add meandiag to constraint dofs | ||||||
@inbounds for i in 1:length(ch.inhomogeneities) | ||||||
|
@@ -661,6 +632,50 @@ function apply!(KK::Union{SparseMatrixCSC,Symmetric}, f::AbstractVector, ch::Con | |||||
end | ||||||
end | ||||||
|
||||||
""" | ||||||
add_inhomogeneities!(K, f::AbstractVector, inhomogeneities::AbstractVector, prescribed_dofs::AbstractVector{<:Integer}, dofmapping::Dict{Int,Int}) | ||||||
|
||||||
Compute "f -= K*inhomogeneities". | ||||||
By default this is a generic version via SpMSpV kernel. | ||||||
""" | ||||||
function add_inhomogeneities!(K, f::AbstractVector, inhomogeneities::AbstractVector, prescribed_dofs::AbstractVector{<:Integer}, dofmapping) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this only modifies
Suggested change
I would prefer the interface There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. That makes sense from an interface point of view. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Out of curiosity, any reason not passing the constraint handler instead of inhomogeneities, prescribed_dofs, and dofmapping? (Esp. since dofmapping isn't always used). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No specific reason. The internals are not consistent here. I changed it to take the ch for now to simplify forward compatibility. |
||||||
f .-= K*sparsevec(prescribed_dofs, inhomogeneities, size(K,2)) | ||||||
end | ||||||
|
||||||
# Optimized version for SparseMatrixCSC | ||||||
add_inhomogeneities!(K::SparseMatrixCSC, f::AbstractVector, inhomogeneities::AbstractVector, prescribed_dofs::AbstractVector{<:Integer}, dofmapping) = add_inhomogeneities_csc!(K, f, inhomogeneities, prescribed_dofs, dofmapping, false) | ||||||
add_inhomogeneities!(K::Symmetric{<:Any,<:SparseMatrixCSC}, f::AbstractVector, inhomogeneities::AbstractVector, prescribed_dofs::AbstractVector{<:Integer}, dofmapping) = add_inhomogeneities_csc!(K.data, f, inhomogeneities, prescribed_dofs, dofmapping, true) | ||||||
function add_inhomogeneities_csc!(K::SparseMatrixCSC, f::AbstractVector, inhomogeneities::AbstractVector, prescribed_dofs::AbstractVector{<:Integer}, dofmapping, sym::Bool) | ||||||
@inbounds for i in 1:length(inhomogeneities) | ||||||
d = prescribed_dofs[i] | ||||||
v = inhomogeneities[i] | ||||||
if v != 0 | ||||||
for j in nzrange(K, d) | ||||||
r = K.rowval[j] | ||||||
sym && r > d && break # don't look below diagonal | ||||||
f[r] -= v * K.nzval[j] | ||||||
end | ||||||
end | ||||||
end | ||||||
if sym | ||||||
# In the symmetric case, for a constrained dof `d`, we handle the contribution | ||||||
# from `K[1:d, d]` in the loop above, but we are still missing the contribution | ||||||
# from `K[(d+1):size(K,1), d]`. These values are not stored, but since the | ||||||
# matrix is symmetric we can instead use `K[d, (d+1):size(K,1)]`. Looping over | ||||||
# rows is slow, so loop over all columns again, and check if the row is a | ||||||
# constrained row. | ||||||
@inbounds for col in 1:size(K, 2) | ||||||
for ri in nzrange(K, col) | ||||||
row = K.rowval[ri] | ||||||
row >= col && break | ||||||
if (i = get(dofmapping, row, 0); i != 0) | ||||||
f[col] -= inhomogeneities[i] * K.nzval[ri] | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
end | ||||||
|
||||||
# Fetch dof coefficients for a dof prescribed by an affine constraint. Return nothing if the | ||||||
# dof is not prescribed, or prescribed by DBC. | ||||||
@inline function coefficients_for_dof(dofmapping, dofcoeffs, dof) | ||||||
|
@@ -669,6 +684,13 @@ end | |||||
return dofcoeffs[idx] | ||||||
end | ||||||
|
||||||
|
||||||
function _condense!(K::AbstractSparseMatrix, f::AbstractVector, dofcoefficients::Vector{Union{Nothing, DofCoefficients{T}}}, dofmapping::Dict{Int,Int}, sym::Bool=false) where T | ||||||
# Return early if there are no non-trivial affine constraints | ||||||
any(i -> !(i === nothing || isempty(i)), dofcoefficients) || return | ||||||
error("condensation of ::$(typeof(K)) matrix not supported") | ||||||
end | ||||||
|
||||||
# Condenses K and f: C'*K*C, C'*f, in-place assuming the sparsity pattern is correct | ||||||
function _condense!(K::SparseMatrixCSC, f::AbstractVector, dofcoefficients::Vector{Union{Nothing, DofCoefficients{T}}}, dofmapping::Dict{Int,Int}, sym::Bool=false) where T | ||||||
|
||||||
|
@@ -779,19 +801,19 @@ function create_constraint_matrix(ch::ConstraintHandler{dh,T}) where {dh,T} | |||||
end | ||||||
|
||||||
# columns need to be stored entries, this is not checked | ||||||
function zero_out_columns!(K, dofs::Vector{Int}) # can be removed in 0.7 with #24711 merged | ||||||
@debug @assert issorted(dofs) | ||||||
for col in dofs | ||||||
function zero_out_columns!(K::SparseArrays.AbstractSparseMatrixCSC, ch::ConstraintHandler) # can be removed in 0.7 with #24711 merged | ||||||
KnutAM marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
@debug @assert issorted(ch.prescribed_dofs) | ||||||
for col in ch.prescribed_dofs | ||||||
r = nzrange(K, col) | ||||||
K.nzval[r] .= 0.0 | ||||||
end | ||||||
end | ||||||
|
||||||
function zero_out_rows!(K, dofmapping) | ||||||
function zero_out_rows!(K::SparseArrays.AbstractSparseMatrixCSC, ch::ConstraintHandler) | ||||||
KnutAM marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
rowval = K.rowval | ||||||
nzval = K.nzval | ||||||
@inbounds for i in eachindex(rowval, nzval) | ||||||
if haskey(dofmapping, rowval[i]) | ||||||
if haskey(ch.dofmapping, rowval[i]) | ||||||
nzval[i] = 0 | ||||||
end | ||||||
end | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.