SparseArrays
Julia は疎ベクトルと疎行列のサポートを標準ライブラリの SparseArrays
モジュールに持ちます。疎配列とはゼロ要素を多く持つために密配列ではない特別なデータ構造を使うことで空間効率と実行速度を改善できるような配列です。
疎行列の格納形式
Julia は疎行列を圧縮列格納形式 (compressed sparse column format, CSC) で格納します。Julia の疎行列は SparseMatrixCSC{Tv,Ti}
という型を持ち、ここで Tv
は格納される値の型、Ti
は列ポインタと行インデックスに使われる整数型を表します。SparseMatrixCSC
の内部表現は次の通りです:
struct SparseMatrixCSC{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti}
m::Int # 行数
n::Int # 列数
colptr::Vector{Ti} # 第 j 列は colptr[j]:(colptr[j+1]-1) にある
rowval::Vector{Ti} # 格納された値の行インデックス
nzval::Vector{Tv} # 格納される値 (通常は非ゼロ)
end
CSC 形式により疎行列の要素への列ごとのアクセスは高速になりますが、行ごとのアクセスは非常に低速になります。また CSC 形式を使う疎行列の格納されていない部分に要素を一つずつ挿入するといった操作は遅くなる傾向があります。これは挿入される要素より後ろの要素を全て一つずつ後ろに動かす必要があるためです。
疎行列に対する全ての操作は CSC を活用して性能を向上させ、コストの大きい操作を避けるよう注意深く実装されています。
異なるアプリケーションやライブラリからの CSC 形式のデータを Julia にインポートするときは、1 始まりの添え字が使われていることを確認してください。また各列の行インデックスはソートされている必要があります。SparseMatrixCSC
オブジェクトがソートされていない行インデックスを持つときは、二回転置を取ると簡単にソートできます。
一部のアプリケーションでは SparseMatrixCSC
に一部のゼロ要素を明示的に保存した方がよい場合もあります。Base
の関数はそういったオブジェクトに対しても正しく動作します (が、改変を行う操作で格納されたゼロ要素が保存されるとは限りません)。多くのルーチンは明示的に格納されたゼロ要素を "構造的" 非ゼロ要素とみなします。例えば nnz
は疎データ構造に格納された要素数を返しますが、この値には構造的非ゼロ要素の個数が含まれます。"数値的" 非ゼロ要素を数えるには、count(!iszero, x)
を使って疎行列に格納された各要素を調べてください。dropzeros
およびインプレースの dropzeros!
を使えば疎行列に格納されたゼロ要素を削除できます。
julia> A = sparse([1, 1, 2, 3], [1, 3, 2, 3], [0, 1, 2, 0])
3×3 SparseMatrixCSC{Int64,Int64} with 4 stored entries:
[1, 1] = 0
[2, 2] = 2
[1, 3] = 1
[3, 3] = 0
julia> dropzeros(A)
3×3 SparseMatrixCSC{Int64,Int64} with 2 stored entries:
[2, 2] = 2
[1, 3] = 1
疎ベクトルの格納形式
疎ベクトルは疎行列に対する CSC 形式によく似た形式で格納されます。Julia の疎ベクトルは SparseVector(Tv,Ti)
という型を持ち、ここで Tv
は格納される値の型、Ti
は添え字で使われる整数型です。内部表現は次の通りです:
struct SparseVector{Tv,Ti<:Integer} <: AbstractSparseVector{Tv,Ti}
n::Int # 疎ベクトルの長さ
nzind::Vector{Ti} # 格納された値の添え字
nzval::Vector{Tv} # 格納された値 (通常は非ゼロ)
end
SparseMatrixCSC
と同様に、SparseVector
型には明示的なゼロ要素を格納できます (参照: 疎行列の格納形式)。
疎ベクトルと疎行列のコンストラクタ
疎配列を作る最も単純な方法は、密行列の zeros
に対応する関数を使うものです。疎配列を作るときは sp
が最初に付いた spzeros
関数を使います:
julia> spzeros(3)
3-element SparseVector{Float64,Int64} with 0 stored entries
sparse
関数を使っても疎配列を簡単に構築できます。例えば疎行列を構築するには、行の添え字からなるベクトル I
, 列の添え字からなるベクトル J
, 格納される値からなるベクトル V
を sparse
に渡します (この形式は COO 形式と呼ばれます)。このとき sparse(I,J,V)
が構築する疎行列 S
では S[I[k], J[k]] = V[k]
が全ての添え字 k
に対して成り立ちます。同様の疎ベクトルコンストラクタは sparsevec
であり、(行の) 添え字からなるベクトル I
と格納する値からなるベクトル V
を渡すと、全ての添え字 k
に対して R[I[k]] = V[k]
が成り立つような疎ベクトル R
が構築されます。
julia> I = [1, 4, 3, 5]; J = [4, 7, 18, 9]; V = [1, 2, -5, 3];
julia> S = sparse(I,J,V)
5×18 SparseMatrixCSC{Int64,Int64} with 4 stored entries:
[1, 4] = 1
[4, 7] = 2
[5, 9] = 3
[3, 18] = -5
julia> R = sparsevec(I,V)
5-element SparseVector{Int64,Int64} with 4 stored entries:
[1] = 1
[3] = -5
[4] = 2
[5] = 3
sparse
, sparsevec
と逆の処理を行うのが findnz
であり、この関数は入力と同じ疎配列を作成する sparse
/sparsevec
への入力を返します。findall(!iszero, x)
とすると x
内の非ゼロ要素の格子添え字を取得できます。格納されたゼロ ("構造的" ゼロ) の添え字は返り値に含まれません。
julia> findnz(S)
([1, 4, 5, 3], [4, 7, 9, 18], [1, 2, 3, -5])
julia> findall(!iszero, S)
4-element Array{CartesianIndex{2},1}:
CartesianIndex(1, 4)
CartesianIndex(4, 7)
CartesianIndex(5, 9)
CartesianIndex(3, 18)
julia> findnz(R)
([1, 3, 4, 5], [1, -5, 2, 3])
julia> findall(!iszero, R)
4-element Array{Int64,1}:
1
3
4
5
sparse
関数を使って密配列から変換することでも疎配列は作成できます:
julia> sparse(Matrix(1.0I, 5, 5))
5×5 SparseMatrixCSC{Float64,Int64} with 5 stored entries:
[1, 1] = 1.0
[2, 2] = 1.0
[3, 3] = 1.0
[4, 4] = 1.0
[5, 5] = 1.0
julia> sparse([1.0, 0.0, 1.0])
3-element SparseVector{Float64,Int64} with 2 stored entries:
[1] = 1.0
[3] = 1.0
密行列を作成する逆方向の変換は Array
コンストラクタで行えます。行列が疎かどうかの判定には issparse
を使います。
julia> issparse(spzeros(5))
true
疎行列に対する操作
疎行列に対する算術演算は密行列と同様に行えます。添え字アクセス・代入・連結の動作も密行列と同じです。ただし添え字を使う操作、特に代入操作は、一つずつ行うとコストが高くなります。そのため多くの場合、最初に findnz
を使って疎行列を (I,J,V)
の形式に変換し、それを使って所望の値もしくは構造を密ベクトル (I',J',V')
として作成し、そこから疎行列を再構築する方法を取るのがよいでしょう。
密行列と疎行列のメソッドの対応
疎行列用の組み込みメソッドとそれに対応する密行列用メソッドの関係を次の表に示します。一般に疎行列を生成するメソッドと対応する密行列に対するメソッドの違いは、結果の行列が与えられた疎行列と同じ場所にだけ要素を持つこと、あるいは結果の疎行列が d
の密度を持つ (行列の各要素が確率 d
で非ゼロとなる) ことです。
詳細は標準ライブラリのリファレンスにあります。
疎行列用 | 密行列用 | 説明 |
---|---|---|
spzeros(m,n)
|
zeros(m,n)
|
要素が全てゼロの m×n 行列を作成する。spzeros(m,n) は空となる。
|
sparse(I, n, n)
|
Matrix(I,n,n)
|
n×m の恒等行列を作成する。 |
Array(S)
|
sparse(A)
|
形式を疎と密の間で変換する。 |
sprand(m,n,d)
|
rand(m,n)
|
密度 d の m×n ランダム行列を作成する。各非ゼロ要素は独立に [0,1) の一様分布に従う。 |
sprandn(m,n,d)
|
randn(m,n)
|
密度 d の m×n ランダム行列を作成する。各非ゼロ要素は独立に標準正規 (ガウス) 分布に従う。 |
sprandn(rng,m,n,d)
|
randn(rng,m,n)
|
密度 d の m×n ランダム行列を作成する。各非ゼロ要素は独立に乱数生成器 rng から生成される。 |
API リファレンス
SparseArrays.AbstractSparseArray
── 型
AbstractSparseArray{Tv,Ti,N}
要素型 Tv
と添え字型 Ti
を持つ N
次元疎配列型 (および疎配列風の型) を表す上位型です。部分型に SparseMatrixCSC
, SparseVector
, SuiteSparse.CHOLMOD.Sparse
を持ちます。
SparseArrays.AbstractSparseVector
── 型
AbstractSparseVector{Tv,Ti}
要素型 Tv
と添え字型 Ti
を持つ一次元疎配列型 (および疎配列風の型) を表す上位型です。この型は AbstractSparseArray{Tv,Ti,1}
の別名です。
SparseArrays.AbstractSparseMatrix
── 型
AbstractSparseMatrix{Tv,Ti}
要素型 Tv
と添え字型 Ti
を持つ二次元疎配列型 (および疎配列風の型) を表す上位型です。この型は AbstractSparseArray{Tv,Ti,2}
の別名です。
SparseArrays.SparseVector
── 型
SparseArrays.SparseMatrixCSC
── 型
SparseArrays.sparse
── 関数
sparse(A::AbstractMatrix)
AbstractMatrix
型の A
を疎行列に変換します。
例
julia> A = Matrix(1.0I, 3, 3)
3×3 Array{Float64,2}:
1.0 0.0 0.0
0.0 1.0 0.0
0.0 0.0 1.0
julia> sparse(A)
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
[1, 1] = 1.0
[2, 2] = 1.0
[3, 3] = 1.0
sparse(I, J, V,[ m, n, combine])
全ての添え字 k
に対して S[I[k], J[k]] = V[k]
が成り立つような次元 m×n
の疎行列 S
を作成します。重複する要素を混ぜるときは関数 combine
が使われます。m
と n
が指定されないと、それぞれ maximum(I)
と maximum(J)
になります。combine
が与えられないときのデフォルトは V
の要素が真偽値でなければ +
で、真偽値なら |
です。I
の全ての要素は 1 <= I[k] <= m
を満たす必要があり、J
の全ての要素は 1 <= J[k] <= n
を満たす必要があります。I
, J
, V
に含まれる "数値的" ゼロは "構造的" 非ゼロ要素として残されます。数値的ゼロを落とすには dropzeros!
を使ってください。
さらに詳しいドキュメントと実際の処理を行うドライバについては (エクスポートされていない) SparseArrays.sparse!
を参照してください。
例
julia> Is = [1; 2; 3];
julia> Js = [1; 2; 3];
julia> Vs = [1; 2; 3];
julia> sparse(Is, Js, Vs)
3×3 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
[1, 1] = 1
[2, 2] = 2
[3, 3] = 3
SparseArrays.sparsevec
── 関数
sparsevec(I, V, [m, combine])
全ての添え字 k
に対して S[I[k]] = V[k]
が成り立つような長さ m
の疎ベクトル S
を作成します。重複する要素は combine
関数を使って組み合わされます。combine
が指定されないときのデフォルトは V
の要素が真偽値でないなら +
で、真偽値なら |
です。
例
julia> II = [1, 3, 3, 5]; V = [0.1, 0.2, 0.3, 0.2];
julia> sparsevec(II, V)
5-element SparseVector{Float64,Int64} with 3 stored entries:
[1] = 0.1
[3] = 0.5
[5] = 0.2
julia> sparsevec(II, V, 8, -)
8-element SparseVector{Float64,Int64} with 3 stored entries:
[1] = 0.1
[3] = -0.1
[5] = 0.2
julia> sparsevec([1, 3, 1, 2, 2], [true, true, false, false, false])
3-element SparseVector{Bool,Int64} with 3 stored entries:
[1] = 1
[2] = 0
[3] = 1
sparsevec(d::Dict, [m])
非ゼロ要素の添え字が d
のキーで、値が d
のバリューであるような長さ m
の疎ベクトルを作成します。
例
julia> sparsevec(Dict(1 => 3, 2 => 2))
2-element SparseVector{Int64,Int64} with 2 stored entries:
[1] = 3
[2] = 2
sparsevec(A)
ベクトル A
を疎ベクトルに変換します。
例
julia> sparsevec([1.0, 2.0, 0.0, 0.0, 3.0, 0.0])
6-element SparseVector{Float64,Int64} with 3 stored entries:
[1] = 1.0
[2] = 2.0
[5] = 3.0
SparseArrays.issparse
── 関数
issparse(S)
S
が疎なら true
を、そうでなければ false
を返します。
例
julia> sv = sparsevec([1, 4], [2.3, 2.2], 10)
10-element SparseVector{Float64,Int64} with 2 stored entries:
[1 ] = 2.3
[4 ] = 2.2
julia> issparse(sv)
true
julia> issparse(Array(sv))
false
SparseArrays.nnz
── 関数
nnz(A)
疎配列に格納されている (埋まっている) 要素の個数を返します。
例
julia> A = sparse(2I, 3, 3)
3×3 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
[1, 1] = 2
[2, 2] = 2
[3, 3] = 2
julia> nnz(A)
3
SparseArrays.findnz
── 関数
findnz(A)
疎行列 A
に格納された要素 ("構造的" 非ゼロ要素) の行および列の添え字からなるベクトル I
, J
と、値からなるベクトル V
のタプル (I, J, V)
を返します。
例
julia> A = sparse([1 2 0; 0 0 3; 0 4 0])
3×3 SparseMatrixCSC{Int64,Int64} with 4 stored entries:
[1, 1] = 1
[1, 2] = 2
[3, 2] = 4
[2, 3] = 3
julia> findnz(A)
([1, 1, 3, 2], [1, 2, 2, 3], [1, 2, 4, 3])
SparseArrays.spzeros
── 関数
spzeros([type,]m[,n])
長さ m
の疎ベクトルまたはサイズ m×n
の疎行列を作成します。作成される疎配列は非ゼロ要素を持たず、非ゼロ要素に対する格納領域は作成時に一切アロケートされません。要素型 type
を指定しなければ Float64
がデフォルトで使われます。
例
julia> spzeros(3, 3)
3×3 SparseMatrixCSC{Float64,Int64} with 0 stored entries
julia> spzeros(Float32, 4)
4-element SparseVector{Float32,Int64} with 0 stored entries
SparseArrays.spdiagm
── 関数
spdiagm(kv::Pair{<:Integer,<:AbstractVector}...)
spdiagm(m::Integer, n::Ingeger, kv::Pair{<:Integer,<:AbstractVector}...)
整数と対角要素の組をいくつか受け取り、そこから疎な帯行列を構築します。ベクトル kv.second
が第 kv.first
優対角要素に配置されます。デフォルトでは size=nothing
のとき行列は正方とみなされ、サイズは kv
から計算されます。ただし引数の最初に m,n
を渡せば正方でないサイズ m×n
を指定することもできます (必要ならゼロ埋めされます)。
例
julia> spdiagm(-1 => [1,2,3,4], 1 => [4,3,2,1])
5×5 SparseMatrixCSC{Int64,Int64} with 8 stored entries:
[2, 1] = 1
[1, 2] = 4
[3, 2] = 2
[2, 3] = 3
[4, 3] = 3
[3, 4] = 2
[5, 4] = 4
[4, 5] = 1
SparseArrays.blockdiag
── 関数
blockdiag(A...)
行列をブロックごとに対角に並べて連結します。現在疎行列に対してだけ実装されます。
例
julia> blockdiag(sparse(2I, 3, 3), sparse(4I, 2, 2))
5×5 SparseMatrixCSC{Int64,Int64} with 5 stored entries:
[1, 1] = 2
[2, 2] = 2
[3, 3] = 2
[4, 4] = 4
[5, 5] = 4
SparseArrays.sprand
── 関数
sprand([rng],[type],m,[n],p::AbstractFloat,[rfn])
各要素が独立な確率 p
で非ゼロとなる長さ m
の疎ベクトルまたはサイズ m×n
の疎行列を作成します(このため非ゼロ要素の平均密度はちょうど p
です)。非ゼロ要素の値は rfn
が指定する分布からサンプルされ、type
型を持ちます。rfn
が指定されなければ一様分布が使われます。省略可能引数 rng
には乱数生成器を指定します。詳しくは Random
モジュールのリファレンスを参照してください。
例
julia> sprand(Bool, 2, 2, 0.5)
2×2 SparseMatrixCSC{Bool,Int64} with 1 stored entry:
[2, 2] = 1
julia> sprand(Float64, 3, 0.75)
3-element SparseVector{Float64,Int64} with 1 stored entry:
[3] = 0.298614
SparseArrays.sprandn
── 関数
sprandn([rng][,Type],m[,n],p::AbstractFloat)
各要素が独立な確率 p
で非ゼロとなる長さ m
の疎ベクトルまたはサイズ m×n
の疎行列を作成します。非ゼロ要素の値は正規分布からサンプルされます。省略可能引数 rng
には乱数生成器を指定します。詳しくは Random
モジュールのリファレンスを参照してください。
出力の要素型を指定する機能は Julia 1.1 以降でサポートされます。
例
julia> sprandn(2, 2, 0.75)
2×2 SparseMatrixCSC{Float64,Int64} with 2 stored entries:
[1, 2] = 0.586617
[2, 2] = 0.297336
SparseArrays.nonzeros
── 関数
nonzeros(A)
疎配列 A
に含まれる "構造的" 非ゼロ要素からなるベクトルを返します。A
に明示的に格納されたゼロは返り値に含まれます。返り値のベクトルの要素は A
の非ゼロ要素の格納領域を直接指すので、返り値のベクトルを変更すると A
も変更されます。rowvals
と nzrange
も参照してください。
例
julia> A = sparse(2I, 3, 3)
3×3 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
[1, 1] = 2
[2, 2] = 2
[3, 3] = 2
julia> nonzeros(A)
3-element Array{Int64,1}:
2
2
2
SparseArrays.rowvals
── 関数
rowvals(A::AbstractSparseMatrixCSC)
A
の行インデックスのベクトルを返します。返り値のベクトルに対する任意の変更は A
にも伝わります。行インデックスの内部表現へのアクセスは構造的非ゼロ要素の反復と共に使用すると有用な場合があります。nonzeros
と nzrange
も参照してください。
例
julia> A = sparse(2I, 3, 3)
3×3 SparseMatrixCSC{Int64,Int64} with 3 stored entries:
[1, 1] = 2
[2, 2] = 2
[3, 3] = 2
julia> rowvals(A)
3-element Array{Int64,1}:
1
2
3
SparseArrays.nzrange
── 関数
SparseArrays.droptol!
── 関数
SparseArrays.dropzeros!
── 関数
dropzeros!(A::AbstractSparseMatrixCSC;)
A
から数値的ゼロを取り除きます。
インプレースでないバージョンは dropzeros
です。アルゴリズムに関しては (エクスポートされていない) fkeep!
を参照してください。
SparseArrays.dropzeros
── 関数
dropzeros(A::AbstractSparseMatrixCSC;)
A
のコピーを作成し、そこから数値的ゼロを取り除いて返します。
インプレースなバージョンおよびアルゴリズムについては dropzeros!
を参照してください。
例
julia> A = sparse([1, 2, 3], [1, 2, 3], [1.0, 0.0, 1.0])
3×3 SparseMatrixCSC{Float64,Int64} with 3 stored entries:
[1, 1] = 1.0
[2, 2] = 0.0
[3, 3] = 1.0
julia> dropzeros(A)
3×3 SparseMatrixCSC{Float64,Int64} with 2 stored entries:
[1, 1] = 1.0
[3, 3] = 1.0
dropzeros(x::SparseVector)
x
のコピーを作成し、そこから数値的ゼロを取り除いて返します。
インプレースなバージョンおよびアルゴリズムについては dropzeros!
を参照してください。
例
julia> A = sparsevec([1, 2, 3], [1.0, 0.0, 1.0])
3-element SparseVector{Float64,Int64} with 3 stored entries:
[1] = 1.0
[2] = 0.0
[3] = 1.0
julia> dropzeros(A)
3-element SparseVector{Float64,Int64} with 2 stored entries:
[1] = 1.0
[3] = 1.0
SparseArrays.permute
── 関数
permute(A::AbstractSparseMatrixCSC{Tv,Ti},
p::AbstractVector{<:Integer},
q::AbstractVector{<:Integer}) where {Tv,Ti}
A
を両側から置換した PAQ
(A[p,q]
) を返します。列の置換 q
の長さは A
の列数と一致する (length(q) == size(A, 2)
) 必要があり、行の置換 p
の長さは A
の行数と一致する (length(p) == size(A, 1)
) 必要があります。
さらに詳しいドキュメントと実際の処理を行うドライバについては permute!
を参照してください。
例
julia> A = spdiagm(0 => [1, 2, 3, 4], 1 => [5, 6, 7])
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
[1, 1] = 1
[1, 2] = 5
[2, 2] = 2
[2, 3] = 6
[3, 3] = 3
[3, 4] = 7
[4, 4] = 4
julia> permute(A, [4, 3, 2, 1], [1, 2, 3, 4])
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
[4, 1] = 1
[3, 2] = 2
[4, 2] = 5
[2, 3] = 3
[3, 3] = 6
[1, 4] = 4
[2, 4] = 7
julia> permute(A, [1, 2, 3, 4], [4, 3, 2, 1])
4×4 SparseMatrixCSC{Int64,Int64} with 7 stored entries:
[3, 1] = 7
[4, 1] = 4
[2, 2] = 6
[3, 2] = 3
[1, 3] = 5
[2, 3] = 2
[1, 4] = 1
Base.permute!
── メソッド
permute!(X::AbstractSparseMatrixCSC{Tv,Ti},
A::AbstractSparseMatrixCSC{Tv,Ti},
p::AbstractVector{<:Integer},
q::AbstractVector{<:Integer},
[C::AbstractSparseMatrixCSC{Tv,Ti}]) where {Tv,Ti}
A
を両側から置換した PAQ
(A[p,q]
) を X
に格納します。省略可能な引数 C
が与えられると、中間結果 (AQ)^T
(transpose(A[:,q])
) が C
に格納されます。X
, A
, (与えられるなら) C
が互いの別名でないことが必要です。PAQ
を A
に書き戻したい場合は X
を受け取らない次のメソッドを使ってください:
permute!(A::AbstractSparseMatrixCSC{Tv,Ti},
p::AbstractVector{<:Integer},
q::AbstractVector{<:Integer},
[C::AbstractSparseMatrixCSC{Tv,Ti},
[workcolptr::Vector{Ti}]]) where {Tv,Ti}
X
の次元は A
と一致する (size(X, 1) == size(A, 1)
と size(X, 2) == size(A, 2)
が成り立つ) 必要があり、また X
には A
でアロケートされている要素を全て格納できるだけの領域 (length(rowvals(X)) >= nnz(A)
かつ length(nonzeros(X)) >= nnz(A)
) が必要です。さらに列の置換 q
の長さは A
の列数と一致する (length(q) == size(A, 2)
) 必要があり、行の置換 p
の長さは A
の行数と一致する (length(p) == size(A, 1)
) 必要があります。
C
の次元は transpose(A)
と一致する (size(C, 1) == size(A, 2)
と size(C, 2) == size(A, 1)
が成り立つ) 必要があり、C
には A
でアロケートされている要素を全て格納できるだけの領域 (length(rowvals(C)) >= nnz(A)
かつ length(nonzeros(C)) >= nnz(A)
) が必要です。
さらに詳しい (アルゴリズムに関する) 情報および引数検査を行わないバージョンのメソッドについては (エクスポートされていない) 親メソッド unchecked_noalias_permute!
と unchecked_aliasing_permute!
を参照してください。
permute
も参照してください。