# Mmap Symmetric Matricies

**URL:** <https://discourse.julialang.org/t/mmap-symmetric-matricies/63158>\
**Category:** General Usage\
**Tags:** question, mmap\
**Created:** [June 18, 2021, 8:32pm UTC](https://discourse.julialang.org/t/mmap-symmetric-matricies/63158 "2021-06-18T20:32:18Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![RobertGregg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/robertgregg/32/22105_2.png) [@RobertGregg](https://discourse.julialang.org/u/RobertGregg)\
**Post date:** [June 18, 2021, 8:32pm UTC](https://discourse.julialang.org/t/mmap-symmetric-matricies/63158/1 "2021-06-18T20:32:18Z")

</div>

I have a matrix that is too large to fit into memory, so I’ve decided to use `Mmap` to store it on disk which works perfectly. However, the matrix I’m storing is symmetric and it seems like a waste to use twice the amount of storage for the duplicated values.

Is there a way to use Mmap with an UpperTriangular or sparse matrix? I’ve tried:

```julia
using Mmap, LinearAlgebra

#Make up some data
n = 1000
X = rand(n,n)

io = open("myMatrix.bin", "w+");
Mmap.mmap(io, UpperTriangular{Float64, Matrix{Float64}}, (n,n))

```

but it leads to a method error.

Usually I wouldn’t mind the duplicated storage but the matricies I’m working with can be extremely large (e.g. 350,000 × 350,000 which takes up ~900 Gb of space).

Is there a way to reduce disk storage while still maintaining Mmap’s functionality?

---

<div class="post-metadata">

**Author:** ![blackeneth](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/blackeneth/32/10353_2.png) [@blackeneth](https://discourse.julialang.org/u/blackeneth)\
**Post date:** [June 18, 2021, 9:38pm UTC](https://discourse.julialang.org/t/mmap-symmetric-matricies/63158/2 "2021-06-18T21:38:24Z")

</div>

Can’t say I’ve done it … but look into storing your triangular matrix with HDF5, then use mmap.

---

<div class="post-metadata">

**Author:** ![RobertGregg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/robertgregg/32/22105_2.png) [@RobertGregg](https://discourse.julialang.org/u/RobertGregg)\
**Post date:** [June 18, 2021, 10:37pm UTC](https://discourse.julialang.org/t/mmap-symmetric-matricies/63158/3 "2021-06-18T22:37:10Z")

</div>

Oh would HDF5 take up less space, compared to a bin file?

Another thought I had was saving the triangular matrix to disk as a vector and reshaping it somehow after reading in the file.

I can also store everything as Float32 to use half the storage 🤣

---

<div class="post-metadata">

**Author:** ![RobertGregg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/robertgregg/32/22105_2.png) [@RobertGregg](https://discourse.julialang.org/u/RobertGregg)\
**Post date:** [June 19, 2021, 8:30pm UTC](https://discourse.julialang.org/t/mmap-symmetric-matricies/63158/4 "2021-06-19T20:30:48Z")

</div>

Just as a follow-up, I came up with this solution:

- Store the upper triangular matrix as a Float32 vector onto disk
- Create a subtype of `AbstractMatrix` to properly index the vector

```julia
#How many data points are being stored?
n = 5050

#Open an Mmap connection
io = open("X.bin", "w+");
X = Mmap.mmap(io, Vector{Float32}, n)

#Fill with some random data
X .= rand(Float32,n)

#Sync and close the connection
Mmap.sync!(X)
close(io)

```

Next I read the data back in (just to be thorough)

```julia
io = open("X.bin", "r+");
Xread = Mmap.mmap(io, Vector{Float32}, n);

```

Finally I create an array to properly index with (i.e. the hard part):

```julia
#A symmetric upper triangular matrix
mutable struct SymUpperTri{T} <: AbstractMatrix{T}
    data::AbstractVector{T}
    dim::Int

    function SymUpperTri(data::AbstractVector{T}) where T
        #Check if data is the right size to fit into upper triangular matrix
        n = length(data)
        sq = sqrt(8*n+1) #if this is a perfect square, then n is a triangular number
        floor(sq) == ceil(sq) || error("Data is not the correct length to fit into an upper triangular matrix")

        dim = (sq - 1) ÷ 2 # solving n = dim*(dim+1)/2 with quadratic formula
        return new{T}(data, dim)
    end
end

#Julia needs to know how to size and index
Base.size(A::SymUpperTri) = (A.dim, A.dim)

function Base.getindex(A::SymUpperTri{T}, i::Int, j::Int) where T
    return i > j ? A.data[i*(i-1)÷2 + j] : A.data[j*(j-1)÷2 + i]
end

# Found this fancy piece of code from the LinearAlgebra standard library
function Base.replace_in_print_matrix(A::SymUpperTri, i::Integer, j::Integer, s::AbstractString)
    return i ≤ j ? s : Base.replace_with_centered_mark(s)
end

#Check if it works (it does!)
A = SymUpperTri(Xread) # 100×100 SymUpperTri{Float32}:

```

The combination of using Float32 and storing as a vector means the data (350,000 × 350,000) takes up about 225 Gb instead of 900 Gb. Hopefully this helps someone in the future ~
