diff --git a/Project.toml b/Project.toml index d4c2b70..cfc1b8d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "SphericalHarmonicModes" uuid = "0e9554e2-b38b-11e9-16d7-9d9abfec665a" -version = "0.4.5" +version = "0.4.6" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/src/SphericalHarmonicModes.jl b/src/SphericalHarmonicModes.jl index b5d60d1..029ac49 100644 --- a/src/SphericalHarmonicModes.jl +++ b/src/SphericalHarmonicModes.jl @@ -25,7 +25,7 @@ The types [`LM`](@ref) and [`ML`](@ref) are subtypes of this. """ abstract type SHModeRange{LT,MT} <: ModeRange end -Base.eltype(::SHModeRange) = Tuple{Int,Int} +Base.eltype(::SHModeRange{<:AbstractRange{L},<:AbstractRange{M}}) where {L,M} = Tuple{L,M} """ LM(l_range::AbstractUnitRange{<:Integer}, m_range::AbstractUnitRange{<:Integer}) @@ -136,18 +136,18 @@ struct ML{LT,MT} <: SHModeRange{LT,MT} end for DT in [:LM, :ML] - @eval function $DT(l_range::AbstractUnitRange{<:Integer}, m_range::AbstractUnitRange{<:Integer}) + @eval function $DT(l_range::AbstractUnitRange{LI}, m_range::AbstractUnitRange{<:Integer}) where {LI<:Integer} l_min, l_max = firstlast(l_range) m_min, m_max = firstlast(m_range) check_if_lm_range_is_valid(l_min,l_max,m_min,m_max) if max(m_min,-m_max) > l_min - l_min = max(m_min,-m_max) + l_min = oftype(l_min, max(m_min,-m_max)) l_range = l_min:l_max end - $DT{typeof(l_range),typeof(m_range)}(l_range, m_range) + $DT{UnitRange{LI},typeof(m_range)}(UnitRange{LI}(l_range), m_range) end end @@ -269,9 +269,12 @@ function ensure_nonnegative(l::Integer) end ensure_nonnegative(l::Unsigned) = true +function check_if_lm_range_is_valid(l_range::AbstractRange, m_range::AbstractRange) + check_if_lm_range_is_valid(first(l_range), last(l_range), first(m_range), last(m_range)) +end function check_if_lm_range_is_valid(l_min, l_max, m_min, m_max) map(ensure_nonnegative, l_min) - + if abs(m_max) > l_max throw_mboundserror(l_max, m_max) end @@ -364,10 +367,20 @@ Base.intersect(a::T, b::T) where {T<:ZeroClampedRange} = T(min(a.l, b.l)) Base.intersect(a::ZeroClampedRange, b::ZeroClampedRange) = ZeroTo(0) for DT in [:LM, :ML] - @eval function $DT(l_range, ::Type{MT}) where {MT<:PartiallySpecifiedRange} + @eval function $DT(l_range::LT, ::Type{MT}) where {MT<:PartiallySpecifiedRange, LT<:AbstractUnitRange{<:Integer}} ensure_nonempty(l_range) + ensure_nonnegative(first(l_range)) m_range = MT(last(l_range)) - $DT(l_range, m_range) + $DT{LT,MT}(l_range, m_range) + end + @eval function $DT(l_range::SingleValuedRange, m_range::MT) where {MT<:AbstractUnitRange{<:Integer}} + ensure_nonempty(m_range) + check_if_lm_range_is_valid(l_range, m_range) + $DT{SingleValuedRange, MT}(l_range, m_range) + end + @eval function $DT(l_range::SingleValuedRange, m_range::SingleValuedRange) + check_if_lm_range_is_valid(l_range, m_range) + $DT{SingleValuedRange, SingleValuedRange}(l_range, m_range) end @eval $DT(l_range) = $DT(l_range, FullRange) @@ -864,25 +877,25 @@ end Base.length(mr::ModeRange) = @inbounds modeindex(mr, last(mr)) -# Optimized definition -function Base.length(mr::SHModeRange{<:AbstractUnitRange, FullRange}) - # 2l + 1 m's for each l - l_min, l_max = firstlast(l_range(mr)) - (l_max + 1)^2 - l_min^2 -end -function Base.length(mr::SHModeRange{<:AbstractUnitRange, <:ZeroClampedRange}) - # l + 1 m's for each l - l_min, l_max = firstlast(l_range(mr)) - div((1 + l_max - l_min)*(2 + l_max + l_min),2) -end - -# Special methods for single-valued ranges -for DT in [:(<:AbstractUnitRange), :(<:ZeroClampedRange), :FullRange] - @eval Base.length(mr::SHModeRange{$DT, SingleValuedRange}) = length(l_range(mr)) - @eval Base.length(mr::SHModeRange{SingleValuedRange, $DT}) = length(m_range(mr)) -end - -Base.length(mr::SHModeRange{SingleValuedRange, SingleValuedRange}) = 1 +# # Optimized definition +# function Base.length(mr::SHModeRange{<:AbstractUnitRange, FullRange}) +# # 2l + 1 m's for each l +# l_min, l_max = firstlast(l_range(mr)) +# (l_max + 1)^2 - l_min^2 +# end +# function Base.length(mr::SHModeRange{<:AbstractUnitRange, <:ZeroClampedRange}) +# # l + 1 m's for each l +# l_min, l_max = firstlast(l_range(mr)) +# div((1 + l_max - l_min)*(2 + l_max + l_min),2) +# end + +# # Special methods for single-valued ranges +# for DT in [:(<:AbstractUnitRange), :(<:ZeroClampedRange), :FullRange] +# @eval Base.length(mr::SHModeRange{$DT, SingleValuedRange}) = length(l_range(mr)) +# @eval Base.length(mr::SHModeRange{SingleValuedRange, $DT}) = length(m_range(mr)) +# end + +# Base.length(mr::SHModeRange{SingleValuedRange, SingleValuedRange}) = 1 Base.firstindex(mr::ModeRange) = 1 Base.lastindex(mr::ModeRange) = length(mr) diff --git a/test/runtests.jl b/test/runtests.jl index a7e61fb..f35546f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -51,6 +51,8 @@ import SphericalHarmonicModes: flip @testset "LM" begin @test eltype(LM(1:2)) == Tuple{Int,Int} + @test eltype(LM(big(1):big(2))) == Tuple{BigInt,Int} + @test LM{UnitRange{Int},UnitRange{Int}}(2:3,2:2) == LM(2:3,2:2) @test LM(1:3,2:2) == LM(2:3,2:2) @test LM(1:3) == LM(1:3, -3:3) @@ -63,16 +65,32 @@ import SphericalHarmonicModes: flip @test LM(ZeroTo(3), ZeroTo) == LM(0:3, 0:3) @test LM(ZeroTo(3), ToZero) == LM(0:3, -3:0) + @test LM(SingleValuedRange(3), 1:2) == LM(3:3, 1:2) + @test_throws ArgumentError LM(1:0) @test_throws ArgumentError LM(1:1, 1:0) @test_throws ArgumentError LM(-1:1) @test_throws ArgumentError LM(0:1, 1:3) @test_throws ArgumentError LM(0:1, 3:3) @test_throws ArgumentError LM(0:1, -3:0) + @test_throws ArgumentError LM(SingleValuedRange(1), 3:4) + @test_throws ArgumentError LM(SingleValuedRange(1), ZeroTo(3)) + @test_throws ArgumentError LM(SingleValuedRange(1), ToZero(3)) + @test_throws ArgumentError LM(SingleValuedRange(1), FullRange(3)) + + @testset "type-stability" begin + for LT in (:ZeroTo, :SingleValuedRange), + MT in (:ZeroTo, :ToZero, :FullRange) + @eval @test LM($LT(3), $MT) isa LM{$LT, $MT} + end + @test LM(ZeroTo(3), SingleValuedRange(2)) isa LM{UnitRange{Int}, SingleValuedRange} + @test LM(SingleValuedRange(3), SingleValuedRange(2)) isa LM{SingleValuedRange, SingleValuedRange} + end end @testset "ML" begin @test eltype(ML(1:2)) == Tuple{Int,Int} + @test eltype(ML(big(1):big(2))) == Tuple{BigInt,Int} @test ML{UnitRange{Int},UnitRange{Int}}(2:3,2:2) == ML(2:3,2:2) @test ML(1:3,2:2) == ML(2:3,2:2) @test ML(1:3) == ML(1:3, -3:3) @@ -85,12 +103,27 @@ import SphericalHarmonicModes: flip @test ML(ZeroTo(3), ZeroTo) == ML(0:3, 0:3) @test ML(ZeroTo(3), ToZero) == ML(0:3, -3:0) + @test ML(SingleValuedRange(3), 1:2) == ML(3:3, 1:2) + @test_throws ArgumentError ML(1:0) @test_throws ArgumentError ML(1:1, 1:0) @test_throws ArgumentError ML(-1:1) @test_throws ArgumentError ML(0:1, 1:3) @test_throws ArgumentError ML(0:1, 3:3) @test_throws ArgumentError ML(0:1, -3:0) + @test_throws ArgumentError ML(SingleValuedRange(1), 3:4) + @test_throws ArgumentError ML(SingleValuedRange(1), ZeroTo(3)) + @test_throws ArgumentError ML(SingleValuedRange(1), ToZero(3)) + @test_throws ArgumentError ML(SingleValuedRange(1), FullRange(3)) + + @testset "type-stability" begin + for LT in (:ZeroTo, :SingleValuedRange), + MT in (:ZeroTo, :ToZero, :FullRange) + @eval @test ML($LT(3), $MT) isa ML{$LT, $MT} + end + @test ML(ZeroTo(3), SingleValuedRange(2)) isa ML{UnitRange{Int}, SingleValuedRange} + @test ML(SingleValuedRange(3), SingleValuedRange(2)) isa ML{SingleValuedRange, SingleValuedRange} + end end @testset "L2L1Triangle " begin @@ -159,7 +192,7 @@ end @test begin res = length(mr) == iterated_length(mr) == length(collect(mr)) if !res - @show mr + @show mr, typeof(mr) end res end @@ -259,7 +292,7 @@ end @test length(m1) == length(m2) m1 = LM(ZeroTo(l_min), SingleValuedRange(l_min)) - m1 = ML(ZeroTo(l_min), SingleValuedRange(l_min)) + m2 = ML(ZeroTo(l_min), SingleValuedRange(l_min)) @test length(m1) == length(m2) m1 = LM(SingleValuedRange(l_min), SingleValuedRange(l_min))