# How can I specialize similar and copy on a offset custom array?

**URL:** https://discourse.julialang.org/t/how-can-i-specialize-similar-and-copy-on-a-offset-custom-array/46796
**Category:** Performance
**Tags:** question, performance, memory-allocation
**Created:** [September 17, 2020, 6:17pm UTC](https://discourse.julialang.org/t/how-can-i-specialize-similar-and-copy-on-a-offset-custom-array/46796 "2020-09-17T18:17:36Z")
**Posts on this page:** 1
**Page:** 1

<div class="post-metadata">

### Author: ![suavesito](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/suavesito/32/34386_2.png) [@suavesito](https://discourse.julialang.org/u/suavesito)
#### Post date: [September 17, 2020, 6:17pm UTC](https://discourse.julialang.org/t/how-can-i-specialize-similar-and-copy-on-a-offset-custom-array/46796/1 "2020-09-17T18:17:36Z")

</div>

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](https://github.com/JuliaArrays/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.

```julia
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

```julia
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

```julia
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
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](https://github.com/JuliaArrays/OffsetArrays.jl), 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. ñ.ñ 🙂
