Hi All!
I have a Spinor
struct, that Inherits from AbstractVector
, but it’s index go from 0 to 2^{\lfloor dim/2\rfloor}-1, with dim
a part of the Spinor
struct, for this I’m using CustomUnitRanges.jl. The Spinor
struct contains a SparseArray
to save the values.
Reading in the manual I found that I needed t specialize similar
in two different ways, but I was not getting arround about it, so I looked on the OffsetArray implementation and tried something. That worked, but when I was doing copy, It would allocate all the zeros of the SparseArray.
This is extremely problematic, because the plan is to have a dim
as large as possible (maybe 1024 o even more), and the benefit of the SparseArray
is to never have to allocate all the values. For the kind of problem I’m solving it’s extremely rare to have too many of the values as non-zeros.
This is a somewhat minimal example that is reproducible with my actual implementation. I added the show
routines so I was easier to see in the REPL.
module Spinors
# needs to install CustomUnitRanges
# using Pkg
# Pkg.add("CustomUnitRanges")
using CustomUnitRanges: filename_for_zerorange
include(filename_for_zerorange)
using SparseArrays
export Spinor, spinor
struct Spinor{Tv<:Number} <: AbstractVector{Tv}
ti::NamedTuple{(:dim,),Tuple{Int}}
v::SparseVector{Tv,Int} # Values
function Spinor{Tv}(
v::SparseVector{Tv,Ti},
ti::NamedTuple{(:dim,),Tuple{Int}},
) where {Tv<:Number,Ti<:Integer}
ti.dim >= 0 || throw(ArgumentError("The dimension of the spinor must be positive"))
d = ti.dim >> 1
r = d == 0 ? 0 : 1 << d
spv = sparsevec(SparseArrays.nonzeroinds(v), SparseArrays.nonzeros(v), r)
new{Tv}(ti, spv)
end
end
spinor(
coeff::AbstractVector{Tv},
idx::AbstractVector{Ti};
dim,
) where {Tv<:Number,Ti<:Integer} = Spinor{Tv}(sparsevec(idx .+ 1, coeff), (; dim))
Base.IndexStyle(::Spinor) = IndexLinear()
Base.size(spn::Spinor) = (length(spn),)
Base.axes(spn::Spinor) = (Base.Slice(map(ZeroRange, length(spn))),)
Base.length(spn::Spinor) = 1 << (spn.ti.dim >> 1)
const SpnAxis = Base.Slice{ZeroRange{Int}}
function Base.similar(
spn::Spinor,
::Type{T},
inds::Tuple{SpnAxis,Vararg{SpnAxis}},
) where {T}
B = similar(spn.v, T, map(length, inds))
return spinor(B, 0:(length(spn) - 1), dim = spn.ti.dim)
end
@inline function Base.getindex(spn::Spinor, i::Int)
@boundscheck checkbounds(spn, i)
spn.v[i + 1]
end
@inline function Base.getindex(spn::Spinor, I::AbstractUnitRange)
@boundscheck checkbounds(spn, I)
Spinor{eltype(spn)}(spn.v[I .+ 1], (; dim))
end
Base.getindex(spn::Spinor, ::Colon) = copy(spn)
@inline function Base.setindex!(spn::Spinor{Tv}, v::Tv, i::Int) where {Tv}
@boundscheck checkbounds(spn, i)
spn.v[i + 1] = v
end
SparseArrays.nonzeroinds(spn::Spinor) = SparseArrays.nonzeroinds(spn.v) .- 1
SparseArrays.nonzeros(spn::Spinor) = SparseArrays.nonzeros(spn.v)
SparseArrays.dropzeros!(spn::Spinor) = SparseArrays.dropzeros!(spn.v)
function Base.show(io::IO, ::MIME"text/plain", x::Spinor)
n = length(SparseArrays.nonzeros(x.v))
print(
io,
typeof(x),
"($(x.ti.dim))",
" with ",
n,
n == 1 ? " entry" : " entries",
" stored",
)
if count(!iszero, x.v) > 0
println(io, ":")
print(IOContext(io, :typeinfo => eltype(x)), x)
end
end
Base.show(io::IO, x::Spinor) = show(convert(IOContext, io), x)
function Base.show(io::IOContext, x::Spinor)
n = length(x)
nzind = SparseArrays.nonzeroinds(x)
nzval = nonzeros(x)
if isempty(nzind)
return show(io, MIME("text/plain"), x)
end
limit = get(io, :limit, false)::Bool
half_screen_rows = limit ? div(displaysize(io)[1] - 8, 2) : typemax(Int)
pad = ndigits(n)
if !haskey(io, :compact)
io = IOContext(io, :compact => true)
end
for k in eachindex(nzind)
if k < half_screen_rows || k > length(nzind) - half_screen_rows
print(io, " ", '[', rpad(nzind[k], pad), "] = ")
if isassigned(nzval, Int(k))
show(io, nzval[k])
else
print(io, Base.undef_ref_str)
end
k != length(nzind) && println(io)
elseif k == half_screen_rows
println(io, " ", " "^pad, " \u22ee")
end
end
end
end # module
What I would want to happen is
using Spinors
spn1 = spinor([1], [0], dim=16)
#Spinor{Int64}(16) with 1 entry stored:
spn1[:] # or copy(spn1)
#Spinor{Int64}(16) with 1 entry stored:
What hapen is
using Spinors
spn1 = spinor([1], [0], dim=16)
#Spinor{Int64}(16) with 1 entry stored:
spn1[:] # or copy(spn1)
#Spinor{Int64}(16) with 256 entries stored:
Of course I can do dropzeros!(spn1[:])
, but what I want is to never need to allocate all that memory.
I’m using the latest CustomUnitRanges, and my Julia version is
julia> versioninfo()
Julia Version 1.5.1
Commit 697e782ab8 (2020-08-25 20:08 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core(TM) i7-6500U CPU @ 2.50GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-9.0.1 (ORCJIT, skylake)
Environment:
JULIA_NUM_THREADS = 4
P.D. I could try to use OffsetArrays, but the index of the SparseArray will not be an Int
, but a large Int (maybe BigInt
, or something else with Integer
interface), where I will be continuously packing and unpacking the bits on other Int
’s for indexing. And I have no idea on how or if I can do that with OffsetArray.
Thanks in advance. ñ.ñ