Skip to content

Commit 1978d08

Browse files
authored
Merge pull request #527 from ReactiveBayes/approx-one-hot
use isapprox in the isonehot
2 parents 1262e56 + 9a2763d commit 1978d08

4 files changed

Lines changed: 30 additions & 15 deletions

File tree

src/helpers/algebra/common.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,19 +134,20 @@ end
134134
"""
135135
isonehot(vec::AbstractVector)
136136
137-
Checks if the given vector `vec` is a one-hot vector, i.e., a vector with exactly one entry equal to `one(eltype(vec))` and all other entries equal to `zero(eltype(vec))`.
137+
Checks if the given vector `vec` is a one-hot vector, i.e., a vector with exactly one entry approximately equal to `one(eltype(vec))` and all other entries approximately equal to `zero(eltype(vec))`.
138138
139139
Returns `true` if `vec` is one-hot, otherwise returns `false`.
140140
"""
141-
function isonehot(vec::AbstractVector)
141+
function isonehot(vec::AbstractVector{T}) where {T}
142142
number_of_ones::Int = 0
143+
atol = sqrt(eps(T))
143144
for e in vec
144-
if isequal(e, one(e))
145+
if isapprox(e, one(e); atol = atol)
145146
if number_of_ones > 1
146147
return false
147148
end
148149
number_of_ones += 1
149-
elseif !isequal(e, zero(e))
150+
elseif !isapprox(e, zero(e); atol = atol)
150151
return false
151152
end
152153
end

src/rules/categorical/p.jl

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ using SpecialFunctions: logfactorial
44
# https://github.com/JuliaStats/Distributions.jl/blob/master/src/univariate/discrete/categorical.jl#L27
55
@rule Categorical(:p, Marginalisation) (q_out::Categorical,) = begin
66
probs = probvec(q_out)
7-
if !isonehot(probs)
8-
throw(ArgumentError("q_out must be one-hot encoded. Got: $probs"))
9-
end
107
@logscale -logfactorial(length(probs))
118
return Dirichlet(probs .+ one(eltype(probs)))
129
end
1310

1411
# https://github.com/ReactiveBayes/BayesBase.jl/blob/main/src/densities/pointmass.jl
1512
@rule Categorical(:p, Marginalisation) (q_out::PointMass{V},) where {T <: Real, V <: AbstractVector{T}} = begin
16-
probs = probvec(q_out)
13+
probs = mean(q_out)
1714
if !isonehot(probs)
1815
throw(ArgumentError("q_out must be one-hot encoded. Got: $probs"))
1916
end
2017
@logscale -logfactorial(length(probs))
2118
return Dirichlet(probs .+ one(eltype(probs)))
2219
end
2320

24-
@rule Categorical(:p, Marginalisation) (q_out::Any,) = throw(ArgumentError("q_out is only defined for PointMass or Categorical over a one-hot vector. Got: $(typeof(q_out))"))
21+
@rule Categorical(:p, Marginalisation) (q_out::Any,) = throw(
22+
ArgumentError("This rule is only defined for PointMass over a one-hot vector or a Categorical distribution. Got: $(typeof(q_out))")
23+
)

test/helpers/algebra/common_tests.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,19 @@ end
111111
end
112112
end
113113
end
114+
115+
@testitem "isonehot approx" begin
116+
import ReactiveMP: isonehot
117+
118+
@test isonehot([2.9999999999849994e-12, 2.9999999999849994e-12, 0.999999999994]) == true
119+
@test isonehot([2.9999999999849994e-12, 0.999999999994, 2.9999999999849994e-12]) == true
120+
@test isonehot([0.999999999994, 2.9999999999849994e-12, 2.9999999999849994e-12]) == true
121+
122+
@test isonehot([0.03, 0.03, 0.94]) == false
123+
@test isonehot([0.03, 0.94, 0.03]) == false
124+
@test isonehot([0.94, 0.03, 0.03]) == false
125+
126+
@test isonehot([0.0003, 0.0003, 0.9994]) == false
127+
@test isonehot([0.0003, 0.9994, 0.0003]) == false
128+
@test isonehot([0.9994, 0.0003, 0.0003]) == false
129+
end

test/rules/categorical/p_tests.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
@testset "Variational Message Passing: (q_out::Categorical)" begin
1212
@test_rules [check_type_promotion = false] Categorical(:p, Marginalisation) [(input = (q_out = Categorical([0.0, 1.0]),), output = Dirichlet([1.0, 2.0]))]
13+
@test_rules [check_type_promotion = false] Categorical(:p, Marginalisation) [(input = (q_out = Categorical([0.5, 0.5]),), output = Dirichlet([1.5, 1.5]))]
1314
end
1415

1516
# Test if the input q_out for the outgoing message towards p edge is not a scalar
@@ -20,13 +21,11 @@
2021
end
2122

2223
# Test if the input q_out for the outgoing message towards p edge is a one-hot encoded vector
23-
@testset "q_out =?= one-hot encoded vector" begin
24+
@testset "q_out = PointMass one-hot encoded vector" begin
2425
# PointMass over a non-one-hot vector
25-
@test_throws ArgumentError @call_rule Categorical(:p, Marginalisation) (q_out = PointMass([0.5, 0.5]),)
26-
@test_throws ArgumentError @call_rule Categorical(:p, Marginalisation) (q_out = PointMass([1.0, 1.0]),)
27-
# Categorical with non-one-hot probability vector
28-
@test_throws ArgumentError @call_rule Categorical(:p, Marginalisation) (q_out = Categorical([0.3, 0.7]),)
26+
@test_throws "q_out must be one-hot encoded" @call_rule Categorical(:p, Marginalisation) (q_out = PointMass([0.5, 0.5]),)
27+
@test_throws "q_out must be one-hot encoded" @call_rule Categorical(:p, Marginalisation) (q_out = PointMass([1.0, 1.0]),)
2928
# Arbitrary non-distribution
30-
@test_throws ArgumentError @call_rule Categorical(:p, Marginalisation) (q_out = Dirichlet([1.0, 2.0]),)
29+
@test_throws "This rule is only defined for PointMass over a one-hot vector" @call_rule Categorical(:p, Marginalisation) (q_out = "Hello?",)
3130
end
3231
end

0 commit comments

Comments
 (0)