Skip to content

Commit 34d2b87

Browse files
pablosanjosefredrikekre
authored andcommitted
Allow more general eltypes in sparse array multiplication (#33205)
1 parent c5cc3f2 commit 34d2b87

File tree

6 files changed

+140
-87
lines changed

6 files changed

+140
-87
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Standard library changes
4848

4949
#### SparseArrays
5050

51+
* Products involving sparse arrays now allow more general sparse `eltype`s, such as `StaticArrays` ([#33205])
5152

5253
#### Dates
5354

stdlib/SparseArrays/src/linalg.jl

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,6 @@
33
import LinearAlgebra: checksquare
44
using Random: rand!
55

6-
## sparse matrix multiplication
7-
8-
*(A::AbstractSparseMatrixCSC{TvA,TiA}, B::AbstractSparseMatrixCSC{TvB,TiB}) where {TvA,TiA,TvB,TiB} =
9-
*(sppromote(A, B)...)
10-
*(A::AbstractSparseMatrixCSC{TvA,TiA}, transB::Transpose{<:Any,<:AbstractSparseMatrixCSC{TvB,TiB}}) where {TvA,TiA,TvB,TiB} =
11-
(B = transB.parent; (pA, pB) = sppromote(A, B); *(pA, transpose(pB)))
12-
*(A::AbstractSparseMatrixCSC{TvA,TiA}, adjB::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TvB,TiB}}) where {TvA,TiA,TvB,TiB} =
13-
(B = adjB.parent; (pA, pB) = sppromote(A, B); *(pA, adjoint(pB)))
14-
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}, B::AbstractSparseMatrixCSC{TvB,TiB}) where {TvA,TiA,TvB,TiB} =
15-
(A = transA.parent; (pA, pB) = sppromote(A, B); *(transpose(pA), pB))
16-
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}, B::AbstractSparseMatrixCSC{TvB,TiB}) where {TvA,TiA,TvB,TiB} =
17-
(A = adjA.parent; (pA, pB) = sppromote(A, B); *(adjoint(pA), pB))
18-
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}, transB::Transpose{<:Any,<:AbstractSparseMatrixCSC{TvB,TiB}}) where {TvA,TiA,TvB,TiB} =
19-
(A = transA.parent; B = transB.parent; (pA, pB) = sppromote(A, B); *(transpose(pA), transpose(pB)))
20-
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}, adjB::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TvB,TiB}}) where {TvA,TiA,TvB,TiB} =
21-
(A = adjA.parent; B = adjB.parent; (pA, pB) = sppromote(A, B); *(adjoint(pA), adjoint(pB)))
22-
23-
function sppromote(A::AbstractSparseMatrixCSC{TvA,TiA}, B::AbstractSparseMatrixCSC{TvB,TiB}) where {TvA,TiA,TvB,TiB}
24-
Tv = promote_type(TvA, TvB)
25-
Ti = promote_type(TiA, TiB)
26-
A = convert(SparseMatrixCSC{Tv,Ti}, A)
27-
B = convert(SparseMatrixCSC{Tv,Ti}, B)
28-
A, B
29-
end
30-
316
# In matrix-vector multiplication, the correct orientation of the vector is assumed.
327
const AdjOrTransStridedMatrix{T} = Union{StridedMatrix{T},Adjoint{<:Any,<:StridedMatrix{T}},Transpose{<:Any,<:StridedMatrix{T}}}
338

@@ -50,10 +25,10 @@ function mul!(C::StridedVecOrMat, A::AbstractSparseMatrixCSC, B::Union{StridedVe
5025
end
5126
C
5227
end
53-
*(A::AbstractSparseMatrixCSC{TA,S}, x::StridedVector{Tx}) where {TA,S,Tx} =
54-
(T = promote_op(matprod, TA, Tx); mul!(similar(x, T, size(A, 1)), A, x, one(T), zero(T)))
55-
*(A::AbstractSparseMatrixCSC{TA,S}, B::StridedMatrix{Tx}) where {TA,S,Tx} =
56-
(T = promote_op(matprod, TA, Tx); mul!(similar(B, T, (size(A, 1), size(B, 2))), A, B, one(T), zero(T)))
28+
*(A::AbstractSparseMatrixCSC{TA}, x::StridedVector{Tx}) where {TA,Tx} =
29+
(T = promote_op(matprod, TA, Tx); mul!(similar(x, T, size(A, 1)), A, x, true, false))
30+
*(A::AbstractSparseMatrixCSC{TA}, B::StridedMatrix{Tx}) where {TA,Tx} =
31+
(T = promote_op(matprod, TA, Tx); mul!(similar(B, T, (size(A, 1), size(B, 2))), A, B, true, false))
5732

5833
function mul!(C::StridedVecOrMat, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, B::Union{StridedVector,AdjOrTransStridedMatrix}, α::Number, β::Number)
5934
A = adjA.parent
@@ -76,10 +51,10 @@ function mul!(C::StridedVecOrMat, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}
7651
end
7752
C
7853
end
79-
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TA,S}}, x::StridedVector{Tx}) where {TA,S,Tx} =
80-
(T = promote_op(matprod, TA, Tx); mul!(similar(x, T, size(adjA, 1)), adjA, x, one(T), zero(T)))
81-
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TA,S}}, B::AdjOrTransStridedMatrix{Tx}) where {TA,S,Tx} =
82-
(T = promote_op(matprod, TA, Tx); mul!(similar(B, T, (size(adjA, 1), size(B, 2))), adjA, B, one(T), zero(T)))
54+
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, x::StridedVector{Tx}) where {Tx} =
55+
(T = promote_op(matprod, eltype(adjA), Tx); mul!(similar(x, T, size(adjA, 1)), adjA, x, true, false))
56+
*(adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, B::AdjOrTransStridedMatrix) =
57+
(T = promote_op(matprod, eltype(adjA), eltype(B)); mul!(similar(B, T, (size(adjA, 1), size(B, 2))), adjA, B, true, false))
8358

8459
function mul!(C::StridedVecOrMat, transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}, B::Union{StridedVector,AdjOrTransStridedMatrix}, α::Number, β::Number)
8560
A = transA.parent
@@ -102,19 +77,19 @@ function mul!(C::StridedVecOrMat, transA::Transpose{<:Any,<:AbstractSparseMatrix
10277
end
10378
C
10479
end
105-
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC{TA,S}}, x::StridedVector{Tx}) where {TA,S,Tx} =
106-
(T = promote_op(matprod, TA, Tx); mul!(similar(x, T, size(transA, 1)), transA, x, one(T), zero(T)))
107-
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC{TA,S}}, B::AdjOrTransStridedMatrix{Tx}) where {TA,S,Tx} =
108-
(T = promote_op(matprod, TA, Tx); mul!(similar(B, T, (size(transA, 1), size(B, 2))), transA, B, one(T), zero(T)))
80+
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}, x::StridedVector{Tx}) where {Tx} =
81+
(T = promote_op(matprod, eltype(transA), Tx); mul!(similar(x, T, size(transA, 1)), transA, x, true, false))
82+
*(transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}, B::AdjOrTransStridedMatrix) =
83+
(T = promote_op(matprod, eltype(transA), eltype(B)); mul!(similar(B, T, (size(transA, 1), size(B, 2))), transA, B, true, false))
10984

11085
# For compatibility with dense multiplication API. Should be deleted when dense multiplication
11186
# API is updated to follow BLAS API.
11287
mul!(C::StridedVecOrMat, A::AbstractSparseMatrixCSC, B::Union{StridedVector,AdjOrTransStridedMatrix}) =
113-
mul!(C, A, B, one(eltype(B)), zero(eltype(C)))
88+
mul!(C, A, B, true, false)
11489
mul!(C::StridedVecOrMat, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, B::Union{StridedVector,AdjOrTransStridedMatrix}) =
115-
mul!(C, adjA, B, one(eltype(B)), zero(eltype(C)))
90+
mul!(C, adjA, B, true, false)
11691
mul!(C::StridedVecOrMat, transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}, B::Union{StridedVector,AdjOrTransStridedMatrix}) =
117-
mul!(C, transA, B, one(eltype(B)), zero(eltype(C)))
92+
mul!(C, transA, B, true, false)
11893

11994
function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, A::AbstractSparseMatrixCSC, α::Number, β::Number)
12095
mX, nX = size(X)
@@ -131,8 +106,8 @@ function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, A::AbstractSparseM
131106
end
132107
C
133108
end
134-
*(X::AdjOrTransStridedMatrix{TX}, A::AbstractSparseMatrixCSC{TvA,TiA}) where {TX,TvA,TiA} =
135-
(T = promote_op(matprod, TX, TvA); mul!(similar(X, T, (size(X, 1), size(A, 2))), X, A, one(T), zero(T)))
109+
*(X::AdjOrTransStridedMatrix, A::AbstractSparseMatrixCSC{TvA}) where {TvA} =
110+
(T = promote_op(matprod, eltype(X), TvA); mul!(similar(X, T, (size(X, 1), size(A, 2))), X, A, true, false))
136111

137112
function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, α::Number, β::Number)
138113
A = adjA.parent
@@ -150,8 +125,8 @@ function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, adjA::Adjoint{<:An
150125
end
151126
C
152127
end
153-
*(X::AdjOrTransStridedMatrix{TX}, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}) where {TX,TvA,TiA} =
154-
(T = promote_op(matprod, TX, TvA); mul!(similar(X, T, (size(X, 1), size(adjA, 2))), X, adjA, one(T), zero(T)))
128+
*(X::AdjOrTransStridedMatrix, adjA::Adjoint{<:Any,<:AbstractSparseMatrixCSC}) =
129+
(T = promote_op(matprod, eltype(X), eltype(adjA)); mul!(similar(X, T, (size(X, 1), size(adjA, 2))), X, adjA, true, false))
155130

156131
function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}, α::Number, β::Number)
157132
A = transA.parent
@@ -169,8 +144,8 @@ function mul!(C::StridedVecOrMat, X::AdjOrTransStridedMatrix, transA::Transpose{
169144
end
170145
C
171146
end
172-
*(X::AdjOrTransStridedMatrix{TX}, transA::Transpose{<:Any,<:AbstractSparseMatrixCSC{TvA,TiA}}) where {TX,TvA,TiA} =
173-
(T = promote_op(matprod, TX, TvA); mul!(similar(X, T, (size(X, 1), size(transA, 2))), X, transA, one(T), zero(T)))
147+
*(X::AdjOrTransStridedMatrix, transA::Transpose{<:Any,<:AbstractSparseMatrixCSC}) =
148+
(T = promote_op(matprod, eltype(X), eltype(transA)); mul!(similar(X, T, (size(X, 1), size(transA, 2))), X, transA, true, false))
174149

175150
function (*)(D::Diagonal, A::AbstractSparseMatrixCSC)
176151
T = Base.promote_op(*, eltype(D), eltype(A))
@@ -184,13 +159,13 @@ end
184159
# Sparse matrix multiplication as described in [Gustavson, 1978]:
185160
# http://dl.acm.org/citation.cfm?id=355796
186161

187-
*(A::AbstractSparseMatrixCSC{Tv,Ti}, B::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = spmatmul(A,B)
188-
*(A::AbstractSparseMatrixCSC{Tv,Ti}, B::Adjoint{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}) where {Tv,Ti} = spmatmul(A, copy(B))
189-
*(A::AbstractSparseMatrixCSC{Tv,Ti}, B::Transpose{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}) where {Tv,Ti} = spmatmul(A, copy(B))
190-
*(A::Transpose{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}, B::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = spmatmul(copy(A), B)
191-
*(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}, B::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = spmatmul(copy(A), B)
192-
*(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}, B::Adjoint{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}) where {Tv,Ti} = spmatmul(copy(A), copy(B))
193-
*(A::Transpose{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}, B::Transpose{<:Any,<:AbstractSparseMatrixCSC{Tv,Ti}}) where {Tv,Ti} = spmatmul(copy(A), copy(B))
162+
*(A::AbstractSparseMatrixCSC, B::AbstractSparseMatrixCSC) = spmatmul(A,B)
163+
*(A::AbstractSparseMatrixCSC, B::Adjoint{<:Any,<:AbstractSparseMatrixCSC}) = spmatmul(A, copy(B))
164+
*(A::AbstractSparseMatrixCSC, B::Transpose{<:Any,<:AbstractSparseMatrixCSC}) = spmatmul(A, copy(B))
165+
*(A::Transpose{<:Any,<:AbstractSparseMatrixCSC}, B::AbstractSparseMatrixCSC) = spmatmul(copy(A), B)
166+
*(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, B::AbstractSparseMatrixCSC) = spmatmul(copy(A), B)
167+
*(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC}, B::Adjoint{<:Any,<:AbstractSparseMatrixCSC}) = spmatmul(copy(A), copy(B))
168+
*(A::Transpose{<:Any,<:AbstractSparseMatrixCSC}, B::Transpose{<:Any,<:AbstractSparseMatrixCSC}) = spmatmul(copy(A), copy(B))
194169

195170
# Gustavsen's matrix multiplication algorithm revisited.
196171
# The result rowval vector is already sorted by construction.
@@ -200,7 +175,9 @@ end
200175
# done by a quicksort of the row indices or by a full scan of the dense result vector.
201176
# The last is faster, if more than ≈ 1/32 of the result column is nonzero.
202177
# TODO: extend to SparseMatrixCSCUnion to allow for SubArrays (view(X, :, r)).
203-
function spmatmul(A::AbstractSparseMatrixCSC{Tv,Ti}, B::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
178+
function spmatmul(A::AbstractSparseMatrixCSC{TvA,TiA}, B::AbstractSparseMatrixCSC{TvB,TiB}) where {TvA,TiA,TvB,TiB}
179+
Tv = promote_op(matprod, TvA, TvB)
180+
Ti = promote_type(TiA, TiB)
204181
mA, nA = size(A)
205182
nB = size(B, 2)
206183
nA == size(B, 1) || throw(DimensionMismatch())
@@ -371,8 +348,8 @@ end
371348

372349
## triangular sparse handling
373350

374-
possible_adjoint(adj::Bool, a::Real ) = a
375-
possible_adjoint(adj::Bool, a ) = adj ? adjoint(a) : a
351+
possible_adjoint(adj::Bool, a::Real) = a
352+
possible_adjoint(adj::Bool, a) = adj ? adjoint(a) : a
376353

377354
const UnitDiagonalTriangular = Union{UnitUpperTriangular,UnitLowerTriangular}
378355

@@ -776,7 +753,6 @@ mul!(y::StridedVecOrMat, A::SparseMatrixCSCSymmHerm, x::StridedVecOrMat) = mul!(
776753
# C .= α * A * B + β * C
777754
function mul!(C::StridedVecOrMat{T}, sA::SparseMatrixCSCSymmHerm, B::StridedVecOrMat,
778755
α::Number, β::Number) where T
779-
780756
fuplo = sA.uplo == 'U' ? nzrangeup : nzrangelo
781757
_mul!(fuplo, C, sA, B, T(α), T(β))
782758
end

stdlib/SparseArrays/src/sparsematrix.jl

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -856,16 +856,17 @@ sparse(I,J,v::Number,m,n,combine::Function) = sparse(I, J, fill(v,length(I)), In
856856
## Transposition and permutation methods
857857

858858
"""
859-
halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{Tv,Ti},
860-
q::AbstractVector{<:Integer}, f::Function = identity) where {Tv,Ti}
859+
halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{TvA,Ti},
860+
q::AbstractVector{<:Integer}, f::Function = identity) where {Tv,TvA,Ti}
861861
862862
Column-permute and transpose `A`, simultaneously applying `f` to each entry of `A`, storing
863863
the result `(f(A)Q)^T` (`map(f, transpose(A[:,q]))`) in `X`.
864864
865-
`X`'s dimensions must match those of `transpose(A)` (`size(X, 1) == size(A, 2)` and `size(X, 2) == size(A, 1)`), and `X`
866-
must have enough storage to accommodate all allocated entries in `A` (`length(rowvals(X)) >= nnz(A)`
867-
and `length(nonzeros(X)) >= nnz(A)`). Column-permutation `q`'s length must match `A`'s column
868-
count (`length(q) == size(A, 2)`).
865+
Element type `Tv` of `X` must match `f(::TvA)`, where `TvA` is the element type of `A`.
866+
`X`'s dimensions must match those of `transpose(A)` (`size(X, 1) == size(A, 2)` and
867+
`size(X, 2) == size(A, 1)`), and `X` must have enough storage to accommodate all allocated
868+
entries in `A` (`length(rowvals(X)) >= nnz(A)` and `length(nonzeros(X)) >= nnz(A)`).
869+
Column-permutation `q`'s length must match `A`'s column count (`length(q) == size(A, 2)`).
869870
870871
This method is the parent of several methods performing transposition and permutation
871872
operations on [`SparseMatrixCSC`](@ref)s. As this method performs no argument checking,
@@ -876,8 +877,8 @@ algorithms for sparse matrices: multiplication and permuted transposition," ACM
876877
250-269 (1978). The algorithm runs in `O(size(A, 1), size(A, 2), nnz(A))` time and requires no space
877878
beyond that passed in.
878879
"""
879-
function halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{Tv,Ti},
880-
q::AbstractVector{<:Integer}, f::Function = identity) where {Tv,Ti}
880+
function halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{TvA,Ti},
881+
q::AbstractVector{<:Integer}, f::Function = identity) where {Tv,TvA,Ti}
881882
_computecolptrs_halfperm!(X, A)
882883
_distributevals_halfperm!(X, A, q, f)
883884
return X
@@ -886,7 +887,7 @@ end
886887
Helper method for `halfperm!`. Computes `transpose(A[:,q])`'s column pointers, storing them
887888
shifted one position forward in `getcolptr(X)`; `_distributevals_halfperm!` fixes this shift.
888889
"""
889-
function _computecolptrs_halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti}
890+
function _computecolptrs_halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{TvA,Ti}) where {Tv,TvA,Ti}
890891
# Compute `transpose(A[:,q])`'s column counts. Store shifted forward one position in getcolptr(X).
891892
fill!(getcolptr(X), 0)
892893
@inbounds for k in 1:nnz(A)
@@ -908,7 +909,7 @@ distributing `rowvals(A)` and `f`-transformed `nonzeros(A)` into `rowvals(X)` an
908909
respectively. Simultaneously fixes the one-position-forward shift in `getcolptr(X)`.
909910
"""
910911
@noinline function _distributevals_halfperm!(X::AbstractSparseMatrixCSC{Tv,Ti},
911-
A::AbstractSparseMatrixCSC{Tv,Ti}, q::AbstractVector{<:Integer}, f::Function) where {Tv,Ti}
912+
A::AbstractSparseMatrixCSC{TvA,Ti}, q::AbstractVector{<:Integer}, f::Function) where {Tv,TvA,Ti}
912913
@inbounds for Xi in 1:size(A, 2)
913914
Aj = q[Xi]
914915
for Ak in nzrange(A, Aj)
@@ -944,7 +945,8 @@ end
944945
transpose!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, identity)
945946
adjoint!(X::AbstractSparseMatrixCSC{Tv,Ti}, A::AbstractSparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, conj)
946947

947-
function ftranspose(A::AbstractSparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti}
948+
# manually specifying eltype allows to avoid calling return_type of f on TvA
949+
function ftranspose(A::AbstractSparseMatrixCSC{TvA,Ti}, f::Function, eltype::Type{Tv} = TvA) where {Tv,TvA,Ti}
948950
X = SparseMatrixCSC(size(A, 2), size(A, 1),
949951
ones(Ti, size(A, 1)+1),
950952
Vector{Ti}(undef, nnz(A)),
@@ -953,8 +955,10 @@ function ftranspose(A::AbstractSparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti
953955
end
954956
adjoint(A::AbstractSparseMatrixCSC) = Adjoint(A)
955957
transpose(A::AbstractSparseMatrixCSC) = Transpose(A)
956-
Base.copy(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC}) = ftranspose(A.parent, x -> copy(adjoint(x)))
957-
Base.copy(A::Transpose{<:Any,<:AbstractSparseMatrixCSC}) = ftranspose(A.parent, x -> copy(transpose(x)))
958+
Base.copy(A::Adjoint{<:Any,<:AbstractSparseMatrixCSC}) =
959+
ftranspose(A.parent, x -> adjoint(copy(x)), eltype(A))
960+
Base.copy(A::Transpose{<:Any,<:AbstractSparseMatrixCSC}) =
961+
ftranspose(A.parent, x -> transpose(copy(x)), eltype(A))
958962
function Base.permutedims(A::AbstractSparseMatrixCSC, (a,b))
959963
(a, b) == (2, 1) && return ftranspose(A, identity)
960964
(a, b) == (1, 2) && return copy(A)

0 commit comments

Comments
 (0)