Repository: JuliaDynamics/ComplexityMeasures.jl (julia)

Issue:
`eachindex` for `Probabilities` is ambiguous
While working for a fix for #368, I encountered this (on ComplexityMeasures v3.0.2, Julia 1.9.4):

```julia
julia> using ComplexityMeasures

julia> s = StatisticalComplexity(def = ShannonExtropy(), o = Dispersion(), pest = Shrinkage())
StatisticalComplexity{Distances.JSDivergence, Shrinkage{Nothing, Nothing}, Dispersion{GaussianCDFEncoding}, ShannonExtropy{Int64}}(Distances.JSDivergence(), ShannonExtropy{Int64}(2), Shrinkage{Nothing, Nothing}(nothing, nothing), Dispersion{GaussianCDFEncoding}(GaussianCDFEncoding, 3, 2, 1, false), Base.RefValue{Float64}(0.0))

julia> entropy_complexity_curves(s)
ERROR: MethodError: eachindex(::Probabilities{Float64, 1, Symbol}) is ambiguous.

Candidates:
  eachindex(A::AbstractArray, B::AbstractArray...)
    @ Base abstractarray.jl:377
  eachindex(d::Probabilities{T, N}, args...) where {T, N}
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/core/probabilities.jl:97
  eachindex(A::AbstractVector)
    @ Base abstractarray.jl:314
  eachindex(A::AbstractArray)
    @ Base abstractarray.jl:371

Possible fix, define
  eachindex(::Probabilities{T, 1}) where T

Stacktrace:
  [1] first(a::Probabilities{Float64, 1, Symbol})
    @ Base ./abstractarray.jl:445
  [2] _mean(f::typeof(identity), A::Probabilities{Float64, 1, Symbol}, dims::Colon)
    @ Statistics /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:176
  [3] #mean#2
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:164 [inlined]
  [4] mean
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:164 [inlined]
  [5] _var(A::Probabilities{Float64, 1, Symbol}, corrected::Bool, mean::Nothing, #unused#::Colon)
    @ Statistics /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:379
  [6] #var#15
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:368 [inlined]
  [7] var
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:368 [inlined]
  [8] _std(A::Probabilities{Float64, 1, Symbol}, corrected::Bool, mean::Nothing, #unused#::Colon)
    @ Statistics /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:460
  [9] #std#17
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:449 [inlined]
 [10] std
    @ /Applications/Julia-1.9.app/Contents/Resources/julia/share/julia/stdlib/v1.9/Statistics/src/Statistics.jl:449 [inlined]
 [11] codify(est::Dispersion{GaussianCDFEncoding}, x::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/outcome_spaces/dispersion.jl:101
 [12] counts_and_outcomes(o::Dispersion{GaussianCDFEncoding}, x::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/outcome_spaces/dispersion.jl:75
 [13] probabilities_and_outcomes(est::RelativeAmount, outcomemodel::Dispersion{GaussianCDFEncoding}, x::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/probabilities_estimators/RelativeAmount.jl:53
 [14] probabilities_and_outcomes(est::Shrinkage{Nothing, Nothing}, outcomemodel::Dispersion{GaussianCDFEncoding}, x::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/probabilities_estimators/Shrinkage.jl:65
 [15] probabilities
    @ ~/Documents/Repos/ComplexityMeasures.jl/src/core/probabilities.jl:272 [inlined]
 [16] information(hest::PlugIn{ShannonExtropy{Int64}}, pest::Shrinkage{Nothing, Nothing}, o::Dispersion{GaussianCDFEncoding}, x::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/discrete_info_estimators/plugin.jl:38
 [17] information
    @ ~/Documents/Repos/ComplexityMeasures.jl/src/core/information_functions.jl:99 [inlined]
 [18] complexity(c::StatisticalComplexity{Distances.JSDivergence, Shrinkage{Nothing, Nothing}, Dispersion{GaussianCDFEncoding}, ShannonExtropy{Int64}}, p::Probabilities{Float64, 1, Symbol})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/complexity_measures/statistical_complexity.jl:114
 [19] entropy_complexity_curves(c::StatisticalComplexity{Distances.JSDivergence, Shrinkage{Nothing, Nothing}, Dispersion{GaussianCDFEncoding}, ShannonExtropy{Int64}}; num_max::Int64, num_min::Int64)
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/complexity_measures/statistical_complexity.jl:173
 [20] entropy_complexity_curves(c::StatisticalComplexity{Distances.JSDivergence, Shrinkage{Nothing, Nothing}, Dispersion{GaussianCDFEncoding}, ShannonExtropy{Int64}})
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/complexity_measures/statistical_complexity.jl:157
 [21] top-level scope
    @ REPL[22]:1

```

It turns out this has nothing to do with the statistical complexity: 

```julia
julia> p = probabilities(RelativeAmount(), Dispersion(), rand(50))
 Probabilities{Float64,1} over 9 outcomes
 [1, 1]  0.14285714285714285
 [1, 2]  0.08163265306122448
 [1, 3]  0.14285714285714285
 [2, 1]  0.14285714285714285
 [2, 2]  0.061224489795918366
 [2, 3]  0.061224489795918366
 [3, 1]  0.10204081632653061
 [3, 2]  0.10204081632653061
 [3, 3]  0.16326530612244897
 
julia> eachindex(p)
ERROR: MethodError: eachindex(::Probabilities{Float64, 1, Symbol}) is ambiguous.

Candidates:
  eachindex(A::AbstractArray, B::AbstractArray...)
    @ Base abstractarray.jl:377
  eachindex(d::Probabilities{T, N}, args...) where {T, N}
    @ ComplexityMeasures ~/Documents/Repos/ComplexityMeasures.jl/src/core/probabilities.jl:97
  eachindex(A::AbstractVector)
    @ Base abstractarray.jl:314
  eachindex(A::AbstractArray)
    @ Base abstractarray.jl:371

Possible fix, define
  eachindex(::Probabilities{T, 1}) where T

Stacktrace:
 [1] top-level scope
   @ REPL[19]:1
```

Pull request:
Closes #369.

Also fixes broken tests on `main` introduced by https://github.com/JuliaDynamics/ComplexityMeasures.jl/commit/1ebda7bf05aa2c7c1f4221a5ef59f38f500143b9, where tests where not updated in accordance with the new deprecation message.

Patch:
diff --git a/Project.toml b/Project.toml
index 50bfbf7bf..a57936da0 100644
--- a/Project.toml
+++ b/Project.toml
@@ -2,7 +2,7 @@ name = "ComplexityMeasures"
 uuid = "ab4b797d-85ee-42ba-b621-05d793b346a2"
 authors = "Kristian Agasøster Haaga <kahaaga@gmail.com>, George Datseries <datseris.george@gmail.com>"
 repo = "https://github.com/juliadynamics/ComplexityMeasures.jl.git"
-version = "3.0.2"
+version = "3.0.3"
 
 [deps]
 Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
diff --git a/src/core/counts.jl b/src/core/counts.jl
index bfac1c555..69a9f4db1 100644
--- a/src/core/counts.jl
+++ b/src/core/counts.jl
@@ -87,10 +87,13 @@ function generate_outcomes(x::AbstractArray{T, N}) where {T, N}
 end
 
 # extend base Array interface:
-for f in (:length, :size, :eachindex, :eltype, :parent,
+for f in (:length, :size, :eltype, :parent,
     :lastindex, :firstindex, :vec, :getindex, :iterate)
     @eval Base.$(f)(c::Counts, args...) = $(f)(c.cts, args...)
 end
+# One-argument definitions to avoid type ambiguities with Base:
+Base.eachindex(c::Counts) = eachindex(c.cts)
+
 Base.IteratorSize(::Counts) = Base.HasLength()
 
 # We strictly deal with single inputs here. For multi-inputs, see CausalityTools.jl
diff --git a/src/core/probabilities.jl b/src/core/probabilities.jl
index e71f02fff..249f9f0c6 100644
--- a/src/core/probabilities.jl
+++ b/src/core/probabilities.jl
@@ -92,11 +92,13 @@ function Probabilities(x::AbstractVector, outcomes::AbstractVector, label; norme
 end
 
 # extend base Array interface:
-for f in (:length, :size, :eachindex, :eltype, :parent,
+for f in (:length, :size, :eltype, :parent,
     :lastindex, :firstindex, :vec, :getindex, :iterate)
-    @eval Base.$(f)(d::Probabilities{T, N}, args...) where {T, N} = $(f)(d.p, args...)
+    @eval Base.$(f)(d::Probabilities, args...)= $(f)(d.p, args...)
 end
 
+# One-argument definitions to avoid type ambiguities with Base:
+Base.eachindex(p::Probabilities) = eachindex(p.p)
 # Other useful methods:
 Base.sort(p::Probabilities) = sort(p.p)
 


Test patch:
diff --git a/test/counts/core.jl b/test/counts/core.jl
index d0b05e913..19953f859 100644
--- a/test/counts/core.jl
+++ b/test/counts/core.jl
@@ -26,6 +26,22 @@ oz = (collect(1:2), collect(1:2), collect(1:3))
 @test Counts(y, (1:2 |> collect, ['a', 'b'])) isa Counts{T, 2} where {T}
 @test Counts(z, (1:2, ['a', 'b'], 7:9)) isa Counts{T, 3} where {T}
 
+# ----------------------------------------------------------------
+# Base extensions 
+# ----------------------------------------------------------------
+c = Counts(x, ox)
+# extend base Array interface:
+@test length(c) == length(c.cts)
+@test size(c) == size(c.cts)
+@test eachindex(c) == eachindex(c.cts)
+@test eltype(c) == eltype(c.cts)
+@test parent(c) == parent(c.cts)
+@test firstindex(c) == firstindex(c.cts)
+@test lastindex(c) == lastindex(c.cts)
+@test vec(c) == vec(c.cts)
+@test getindex(c, 2) == getindex(c.cts, 2)
+@test iterate(c) == iterate(c.cts)
+@test sort(c) == sort(c.cts)
 
 # ================================================================
 # Outcomes
diff --git a/test/deprecations.jl b/test/deprecations.jl
index 94d3c5da4..4557a0bcb 100644
--- a/test/deprecations.jl
+++ b/test/deprecations.jl
@@ -15,7 +15,7 @@ rng = Xoshiro(1234)
     msg = "`genentropy(probs::Probabilities; q, base)` deprecated.\nUse instead: `information(Renyi(q, base), probs)`.\n"
     @test_logs (:warn, msg) genentropy(Probabilities(rand(rng, 3)))
 
-    msg = "`genentropy(x::Array_or_SSSet, est::ProbabilitiesEstimator; q, base)` is deprecated.\nUse instead: `information(Renyi(q, base), est, x)`.\n"
+    msg = "`genentropy(x::Array_or_SSSet, o::OutcomeSpace; q, base)` is deprecated.\nUse instead: `information(Renyi(q, base), est, x)`.\n"
     @test_logs (:warn, msg) genentropy(x, ValueBinning(0.1))
 
     @test probabilities(x, 0.1) == probabilities(ValueBinning(0.1), x)
diff --git a/test/probabilities/probabilities.jl b/test/probabilities/probabilities.jl
index 415954546..4e6600fb9 100644
--- a/test/probabilities/probabilities.jl
+++ b/test/probabilities/probabilities.jl
@@ -11,6 +11,23 @@ outs = collect(1:10)
 @test Probabilities(rand(rng, 10), (outs,)) isa Probabilities
 @test Probabilities(rand(rng, 10), (outs,), (:x1, )) isa Probabilities
 
+# ----------------------------------------------------------------
+# Base extensions 
+# ----------------------------------------------------------------
+p =  Probabilities(rand(rng, 10), outs)
+# extend base Array interface:
+@test length(p) == length(p.p)
+@test size(p) == size(p.p)
+@test eachindex(p) == eachindex(p.p)
+@test eltype(p) == eltype(p.p)
+@test parent(p) == parent(p.p)
+@test firstindex(p) == firstindex(p.p)
+@test lastindex(p) == lastindex(p.p)
+@test vec(p) == vec(p.p)
+@test getindex(c, 2) == getindex(c.cts, 2)
+@test iterate(p) == iterate(p.p)
+@test sort(p) == sort(p.p)
+
 # The number of probabilities and outcomes must match.
 @test_throws ArgumentError Probabilities(rand(1:3, 10), (1:9,))