diff --git a/src/grid.jl b/src/grid.jl index afe3d1e..3cafa81 100644 --- a/src/grid.jl +++ b/src/grid.jl @@ -68,6 +68,18 @@ grid index => quantics function grididx_to_quantics(g::Grid{d}, grididx::NTuple{d,Int}) where {d} if g.unfoldingscheme === :fused return index_to_quantics_fused(grididx, numdigits = g.R, base = g.base) + elseif g.unfoldingscheme === :serial + return fused_to_serial( + index_to_quantics_fused(grididx, numdigits = g.R, base = g.base), + d, + base = g.base, + ) + elseif g.unfoldingscheme === :meander + return fused_to_meander( + index_to_quantics_fused(grididx, numdigits = g.R, base = g.base), + d, + base = g.base, + ) else return fused_to_interleaved( index_to_quantics_fused(grididx, numdigits = g.R, base = g.base), @@ -155,7 +167,7 @@ struct InherentDiscreteGrid{d} <: Grid{d} step::Union{NTuple{d,Int},Int} = 1, ) where {d} _rangecheck_R(R; base = base) - unfoldingscheme in (:fused, :interleaved) || + unfoldingscheme in (:fused, :interleaved, :serial, :meander) || error("Invalid unfolding scheme: $unfoldingscheme") origin_ = origin isa Int ? ntuple(i -> origin, d) : origin step_ = step isa Int ? ntuple(i -> step, d) : step @@ -305,7 +317,7 @@ struct DiscretizedGrid{d} <: Grid{d} includeendpoint::Bool = false, ) where {d} _rangecheck_R(R; base = base) - unfoldingscheme in (:fused, :interleaved) || + unfoldingscheme in (:fused, :interleaved, :serial, :meander) || error("Invalid unfolding scheme: $unfoldingscheme") return new( R, diff --git a/src/quantics.jl b/src/quantics.jl index fcafd1c..f6cb7a0 100644 --- a/src/quantics.jl +++ b/src/quantics.jl @@ -104,7 +104,118 @@ function deinterleave_dimensions!( end """ -Convert a fused quantics representation to an unfused quantics representation + serialize_dimensions(digitlists...) + +Sequences the indices of all digitlists into one long digitlist. Use this for +quantics representation of multidimensional objects without fusing indices. +Inverse of [`deserialize_dimensions`](@ref). +""" +function serialize_dimensions(digitlists...)::Vector{Int} + results = Vector{Int}(undef, length(digitlists[1]) * length(digitlists)) + return serialize_dimensions!(results, digitlists...) +end + + +function serialize_dimensions!( + serial_digitlist::AbstractArray{<:Integer}, + digitlists..., +) + l = length(digitlists[1]) + @inbounds for i in eachindex(digitlists) + copyto!(@view(serial_digitlist[(i-1)*l+1:i*l]), digitlists[i]) + end + return serial_digitlist +end + +""" + deserialize_dimensions(digitlist, d) + +Reverses the sequencing of bits, i.e. yields digitlists for each dimension from +a long serialized digitlist. Inverse of [`serialize_dimensions`](@ref). +""" +function deserialize_dimensions(digitlist, d) + l = length(digitlist) ÷ d + return [digitlist[(i-1)*l+1:i*l] for i = 1:d] +end + +function deserialize_dimensions!( + deserialized_digitlists::AbstractArray{<:AbstractArray{I}}, + digitlist, +) where {I<:Integer} + d, l = length(deserialized_digitlists), length(deserialized_digitlists[1]) + @inbounds for i in Base.OneTo(d) + @. deserialized_digitlists[i] = digitlist[(i-1)*l+1:i*l] + end + return deserialized_digitlists +end + +""" + meander_dimensions(digitlists...) + +Sequences the indices of all digitlists into one long digitlist. Use this for +quantics representation of multidimensional objects without fusing indices. +Inverse of [`demeander_dimensions`](@ref). +""" +function meander_dimensions(digitlists...)::Vector{Int} + results = Vector{Int}(undef, length(digitlists[1]) * length(digitlists)) + meander_dimensions!(results, digitlists...) + return results +end + + +function meander_dimensions!( + meander_digitlist::AbstractArray{<:Integer}, + digitlists..., +) + l = length(digitlists[1]) + for (i, digits) in enumerate(digitlists) + if isodd(i) + # odd dimension index (1-based): reverse + @inbounds for j = 1:l + meander_digitlist[(i-1)*l + j] = digits[l - j + 1] + end + else + # even dimension index: normal order + copyto!(@view(meander_digitlist[(i-1)*l+1:i*l]), digits) + end + end + return meander_digitlist +end + +""" + demeander_dimensions(digitlist, d) + +Reverses the sequencing of bits, i.e. yields digitlists for each dimension from +a long meandered digitlist. Inverse of [`meander_dimensions`](@ref). +""" +function demeander_dimensions(digitlist, d) + result = Vector{Vector{Int}}(undef, d) + l = length(digitlist) ÷ d + @inbounds for i in Base.OneTo(d) + segment = digitlist[(i-1)*l+1:i*l] + result[i] = isodd(i) ? reverse(segment) : segment + end + return result +end + +function demeander_dimensions!( + demeandered_digitlists::AbstractArray{<:AbstractArray{I}}, + digitlist, +) where {I<:Integer} + d, l = length(demeandered_digitlists), length(demeandered_digitlists[1]) + @inbounds for i in Base.OneTo(d) + segment = @view(digitlist[(i-1)*l+1:i*l]) + if isodd(i) + demeandered_digitlists[i] = reverse(segment) + else + demeandered_digitlists[i] = Vector(segment) + end + end + return demeandered_digitlists +end + +""" +Convert a fused quantics representation to an unfused quantics interleaved representation """ function fused_to_interleaved( digitlist::AbstractVector{T}, @@ -113,9 +224,29 @@ function fused_to_interleaved( )::Vector{T} where {T<:Integer} return interleave_dimensions(unfuse_dimensions(digitlist, d; base = base)...) end +""" +Convert a fused quantics representation to an unfused quantics serial representation +""" +function fused_to_serial( + digitlist::AbstractVector{T}, + d; + base = 2, +)::Vector{T} where {T<:Integer} + return serialize_dimensions(unfuse_dimensions(digitlist, d; base = base)...) +end +""" +Convert a fused quantics representation to an unfused quantics meander representation +""" +function fused_to_meander( + digitlist::AbstractVector{T}, + d; + base = 2, +)::Vector{T} where {T<:Integer} + return meander_dimensions(unfuse_dimensions(digitlist, d; base = base)...) +end """ -Convert an unfused quantics representation to an fused quantics representation +Convert an unfused quantics interleaved representation to an fused quantics representation """ function interleaved_to_fused( digitlist::AbstractVector{T}; @@ -126,6 +257,30 @@ function interleaved_to_fused( fuse_dimensions!(fused_digitlist, deinterleave_dimensions(digitlist, d)...) return fused_digitlist end +""" +Convert an unfused quantics serial representation to an fused quantics representation +""" +function serial_to_fused( + digitlist::AbstractVector{T}; + base::Integer = 2, + d::Int = 1, +)::Vector{T} where {T<:Integer} + fused_digitlist = Vector{T}(undef, length(digitlist) ÷ d) + fuse_dimensions!(fused_digitlist, deserialize_dimensions(digitlist, d)...) + return fused_digitlist +end +""" +Convert an unfused quantics meander representation to an fused quantics representation +""" +function meander_to_fused( + digitlist::AbstractVector{T}; + base::Integer = 2, + d::Int = 1, +)::Vector{T} where {T<:Integer} + fused_digitlist = Vector{T}(undef, length(digitlist) ÷ d) + fuse_dimensions!(fused_digitlist, demeander_dimensions(digitlist, d)...) + return fused_digitlist +end """ function quantics_to_index_fused( @@ -187,6 +342,18 @@ function quantics_to_index( )::NTuple{d,Int} where {d} if unfoldingscheme === :fused return quantics_to_index_fused(digitlist, base = base, dims = dims) + elseif unfoldingscheme === :serial + return quantics_to_index_fused( + serial_to_fused(digitlist; base = base, d = d), + base = base, + dims = dims, + ) + elseif unfoldingscheme === :meander + return quantics_to_index_fused( + meander_to_fused(digitlist; base = base, d = d), + base = base, + dims = dims, + ) else return quantics_to_index_fused( interleaved_to_fused(digitlist; base = base, d = d), diff --git a/test/grid_tests.jl b/test/grid_tests.jl index 8911d16..17063bc 100644 --- a/test/grid_tests.jl +++ b/test/grid_tests.jl @@ -79,7 +79,7 @@ end end - @testset "InherentDiscreteGrid" for unfoldingscheme in [:interleaved, :fused], + @testset "InherentDiscreteGrid" for unfoldingscheme in [:interleaved, :fused, :serial, :meander], step in [(1, 1, 1), (1, 1, 2)], origin in [(1, 1, 1), (1, 1, 2)] @@ -87,7 +87,7 @@ @test QuanticsGrids.grid_min(m) == origin @test QuanticsGrids.grid_step(m) == step - if unfoldingscheme === :interleaved + if unfoldingscheme === :interleaved || unfoldingscheme === :serial || unfoldingscheme === :meander @test QuanticsGrids.localdimensions(m) == fill(2, 3 * 5) else @test QuanticsGrids.localdimensions(m) == fill(2^3, 5) @@ -108,7 +108,7 @@ end end - @testset "DiscretizedGrid" for unfoldingscheme in [:interleaved, :fused] + @testset "DiscretizedGrid" for unfoldingscheme in [:interleaved, :fused, :serial, :meander] @testset "1D" begin unfoldingscheme = :interleaved @@ -134,7 +134,7 @@ @test QuanticsGrids.grid_step(g) == 0.059375 end - @testset "1D (includeendpoint)" for unfoldingscheme in [:interleaved, :fused] + @testset "1D (includeendpoint)" for unfoldingscheme in [:interleaved, :fused, :serial, :meander] R = 5 a = 0.0 b = 1.0 @@ -159,7 +159,7 @@ @test only(QuanticsGrids.quantics_to_origcoord(g, fill(2, R))) == b end - @testset "2D" for unfoldingscheme in [:interleaved, :fused] + @testset "2D" for unfoldingscheme in [:interleaved, :fused, :serial, :meander] R = 5 d = 2 a = (0.1, 0.1) @@ -167,7 +167,7 @@ dx = (b .- a) ./ 2^R g = QuanticsGrids.DiscretizedGrid{d}(R, a, b; unfoldingscheme) - if unfoldingscheme === :interleaved + if unfoldingscheme === :interleaved || unfoldingscheme === :serial || unfoldingscheme === :meander @test QuanticsGrids.localdimensions(g) == fill(2, d * R) else @test QuanticsGrids.localdimensions(g) == fill(2^d, R) @@ -197,7 +197,7 @@ @test_throws "Bound Error:" QuanticsGrids.origcoord_to_grididx(g, (3.0, 3.0)) end - @testset "2D (includeendpoint)" for unfoldingscheme in [:interleaved, :fused] + @testset "2D (includeendpoint)" for unfoldingscheme in [:interleaved, :fused, :serial, :meander] R = 5 d = 2 a = (0.1, 0.1) @@ -213,7 +213,7 @@ @test g.includeendpoint @test QuanticsGrids.grid_step(g) == dx - if unfoldingscheme === :interleaved + if unfoldingscheme === :interleaved || unfoldingscheme === :serial || unfoldingscheme === :meander @test QuanticsGrids.localdimensions(g) == fill(2, d * R) else @test QuanticsGrids.localdimensions(g) == fill(2^d, R) diff --git a/test/quantics_test.jl b/test/quantics_test.jl index f09f82f..0e32823 100644 --- a/test/quantics_test.jl +++ b/test/quantics_test.jl @@ -2,6 +2,8 @@ import QuanticsGrids: fuse_dimensions, unfuse_dimensions import QuanticsGrids: quantics_to_index_fused, index_to_quantics import QuanticsGrids: interleave_dimensions, deinterleave_dimensions + import QuanticsGrids: serialize_dimensions, deserialize_dimensions + import QuanticsGrids: meander_dimensions, demeander_dimensions @testset "quantics representation" begin @testset "fuse_dimensions" begin @@ -99,5 +101,43 @@ @test deinterleave_dimensions([1, 2, 11, 1, 3, 12, 1, 4, 13, 1, 5, 14], 3) == [[1, 1, 1, 1], [2, 3, 4, 5], [11, 12, 13, 14]] end + + @testset "serial dimensions" begin + @test [1, 1, 1, 1] == serialize_dimensions([1, 1, 1, 1]) + @test [1, 1, 1, 1, 1, 1, 1, 1] == + serialize_dimensions([1, 1, 1, 1], [1, 1, 1, 1]) + @test [1, 1, 1, 1, 2, 3, 4, 5] == + serialize_dimensions([1, 1, 1, 1], [2, 3, 4, 5]) + @test [1, 1, 1, 1, 2, 3, 4, 5, 11, 12, 13, 14] == + serialize_dimensions([1, 1, 1, 1], [2, 3, 4, 5], [11, 12, 13, 14]) + end + + @testset "deserial dimensions" begin + @test deserialize_dimensions([1, 1, 1, 1], 1) == [[1, 1, 1, 1]] + @test deserialize_dimensions([1, 1, 1, 1], 2) == [[1, 1], [1, 1]] + @test deserialize_dimensions([1, 2, 1, 3, 1, 4, 1, 5], 2) == + [[1, 2, 1, 3], [1, 4, 1, 5]] + @test deserialize_dimensions([1, 2, 11, 1, 3, 12, 1, 4, 13, 1, 5, 14], 3) == + [[1, 2, 11, 1], [3, 12, 1, 4], [13, 1, 5, 14]] + end + + @testset "meander dimensions" begin + @test [1, 1, 1, 1] == meander_dimensions([1, 1, 1, 1]) + @test [1, 1, 1, 1, 1, 1, 1, 1] == + meander_dimensions([1, 1, 1, 1], [1, 1, 1, 1]) + @test [1, 1, 1, 1, 2, 3, 4, 5] == + meander_dimensions([1, 1, 1, 1], [2, 3, 4, 5]) + @test [1, 1, 1, 1, 2, 3, 4, 5, 11, 12, 13, 14] == + meander_dimensions([1, 1, 1, 1], [2, 3, 4, 5], [14, 13, 12, 11]) + end + + @testset "demeander dimensions" begin + @test demeander_dimensions([1, 1, 1, 1], 1) == [[1, 1, 1, 1]] + @test demeander_dimensions([1, 1, 1, 1], 2) == [[1, 1], [1, 1]] + @test demeander_dimensions([1, 2, 1, 3, 1, 4, 1, 5], 2) == + [[3, 1, 2, 1], [1, 4, 1, 5]] + @test demeander_dimensions([1, 2, 11, 1, 3, 12, 1, 4, 13, 1, 5, 14], 3) == + [[1, 11, 2, 1], [3, 12, 1, 4], [14, 5, 1, 13]] + end end end