Unclear behaviour of Symmetric() on sparse matrices

Viewed 85

I am trying to have a better understanding on how the Symmetric() function acts on sparseCSC matrices.

I am storing the lower triangular portion of a symmetric matrix A. I then created sA = Symmetric(A) to allow Julia libraries functions as "\" and "eigs" to treat the matrix as symmetric.

If I want to efficiently modify the elements of sA, am I forced to operate on A? I mean, from A I can access the structure attributes as nzval and change its values, yet from sA it seems that I can only access the data using standard slicing operations for dense matrices.

For example, let us assume I want to add at position [7,3] a certain value X. Using sparse matrices representation I would just use a binary search on the rows associated to column 3 of A and then add the value at the proper entry of A.nzval. on the other hand, on sA it looks that I can only call sA[7,3] = X.

Is there a way to access the structure attributes directly from sA? Keeping both A and sA as references to the same object does not look like a clean idea, yet I am unsure on how to avoid it.

1 Answers

While this may or may not be part of the public API, it looks like "keeping A around" is indeed exactly what Symmetric(A) does internally anyways:

julia> A = sprand(10,10,0.03)
10×10 SparseMatrixCSC{Float64, Int64} with 2 stored entries:
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅   0.437621   ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅        0.913864   ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅

julia> sA = Symmetric(A)
10×10 Symmetric{Float64, SparseMatrixCSC{Float64, Int64}}:
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.437621  0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.437621  0.0  0.0       0.913864  0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.913864  0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0
 0.0  0.0  0.0       0.0  0.0       0.0       0.0  0.0  0.0  0.0

julia> sA.<TAB>
data uplo

julia> sA.data
10×10 SparseMatrixCSC{Float64, Int64} with 2 stored entries:
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅   0.437621   ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅        0.913864   ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅
  ⋅    ⋅    ⋅    ⋅    ⋅         ⋅         ⋅    ⋅    ⋅    ⋅

julia> sA.data === A
true

julia> sA.data.<TAB>
colptr m       n       nzval   rowval

Given this, I suppose you could forget about A and operate on sA.data -- but by the same token, keeping around your own reference to A is, given this implementation, nothing worse than having two names for the same variable, which isn't generally a major problem anyways.

Related