Skip to content

Commit 038ef3e

Browse files
authored
Merge pull request #7 from jucheval:prevent-numerical-edge-cases
Prevent numerical edge cases
2 parents 94081a1 + c87a936 commit 038ef3e

4 files changed

Lines changed: 43 additions & 12 deletions

File tree

src/estimation.jl

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function estimators(
1414
throw(ArgumentError("Δ is but must be non negative"))
1515
end
1616
if Δ == 0
17-
Δ = floor(Int, log(length(data)))
17+
Δ = max(1, floor(Int, log(length(data))))
1818
end
1919

2020
N, T = size(data)
@@ -25,11 +25,11 @@ function estimators(
2525
Z̄_T = mean(Z_T)
2626
= Z̄_T / T
2727
= (N) * (T + 1) * T^(-3) * (mean(Z_T .^ 2) - T / (T + 1) * (Z̄_T + Z̄_T^2))
28-
= 0.
28+
= 0.0
2929
for iter in 1:div(T, Δ)
3030
+= (N) / T * (sum(∑X[(1 + (iter - 1) * Δ):(iter * Δ)]) / (N) - Δ * m̂)^2
3131
end
32-
W2Δ = 0.
32+
W2Δ = 0.0
3333
for iter in 1:div(T, 2 * Δ)
3434
W2Δ +=
3535
(N) / T *
@@ -58,13 +58,13 @@ function estimators(
5858
= Float64[]
5959
for Δ in Δvec
6060
if Δ == 0
61-
Δ = floor(Int, log(length(data)))
61+
Δ = max(1, floor(Int, log(length(data))))
6262
end
63-
= 0.
63+
= 0.0
6464
for iter in 1:div(T, Δ)
6565
+= (N) / T * (sum(∑X[(1 + (iter - 1) * Δ):(iter * Δ)]) / (N) - Δ * m̂)^2
6666
end
67-
W2Δ = 0.
67+
W2Δ = 0.0
6868
for iter in 1:div(T, 2 * Δ)
6969
W2Δ +=
7070
(N) / T *
@@ -103,10 +103,13 @@ function Distributions.fit(
103103
end
104104

105105
## Auxiliary functions
106+
_safe_bernoulli_variance(m::Float64) = max(eps(Float64), m * (1 - m))
107+
106108
function ϕ(m::Float64, v::Float64, w_or_d::Float64, r₊::Float64)::Tuple{Float64,Float64}
107109
r₋ = 1 - r₊
110+
mvar = _safe_bernoulli_variance(m)
108111
if abs(r₊ - r₋) < 1e-3
109-
ϕ₁ = w_or_d / (m * (1 - m)) - 1
112+
ϕ₁ = w_or_d / mvar - 1
110113
else
111114
ϕ₁ = (1 - w_or_d)^2 / (r₊ - r₋)^2
112115
end
@@ -118,7 +121,9 @@ function ϕ(m::Float64, v::Float64, w_or_d::Float64, r₊::Float64)::Tuple{Float
118121
return ϕ₁, ϕ₂
119122
end
120123

121-
function Φ_aux(m::Float64, v::Float64, w_or_d::Float64, r₊::Float64)::Tuple{Float64,Float64,Float64}
124+
function Φ_aux(
125+
m::Float64, v::Float64, w_or_d::Float64, r₊::Float64
126+
)::Tuple{Float64,Float64,Float64}
122127
r₋ = 1 - r₊
123128
ϕ₁, ϕ₂ = ϕ(m, v, w_or_d, r₊)
124129
Φ₁ = m * (1 - (r₊ - r₋) * sqrt(ϕ₁)) - r₋ * sqrt(ϕ₁)
@@ -135,11 +140,12 @@ end
135140

136141
function Φ(m::Float64, v::Float64, w::Float64, r₊::Float64)::Tuple{Float64,Float64,Float64}
137142
r₋ = 1 - r₊
143+
mvar = _safe_bernoulli_variance(m)
138144
if abs(r₊ - r₋) < 1e-3
139145
return Φ_aux(m, v, w, r₊)
140146
end
141147

142-
κ = (r₊ - r₋)^2 * w / (m * (1 - m))
148+
κ = (r₊ - r₋)^2 * w / mvar
143149

144150
if abs- 4 * r₊ * r₋) < 1e-3
145151
d = (8 * r₊ * r₋)^(-1)
@@ -197,7 +203,9 @@ function distance2admissibleset(μ::Float64, λ::Float64, p::Float64)::Float64
197203
return d1 + d2 + d3
198204
end
199205

200-
function projection2admissibleset::Float64, λ::Float64, p::Float64)::Tuple{Float64,Float64,Float64}
206+
function projection2admissibleset(
207+
μ::Float64, λ::Float64, p::Float64
208+
)::Tuple{Float64,Float64,Float64}
201209
λ = min(1, max(0, λ))
202210
p = min(1, max(0, p))
203211
μ = min(λ, max(0, μ))

src/simulate.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ function backward_step(
128128
model = modelconnec.model
129129
N = size(modelconnec)
130130
λ = model.λ
131-
β = model.μ / λ
131+
β = iszero(λ) ? 0.0 : model.μ / λ
132132
θ = modelconnec.θ
133133
values = Dict{Int,Bool}()
134134
remaining_nodes = Int[]
@@ -159,7 +159,7 @@ function forward_simulation!(
159159
model = modelconnec.model
160160
N = size(modelconnec)
161161
λ = model.λ
162-
β = model.μ / λ
162+
β = iszero(λ) ? 0.0 : model.μ / λ
163163
θ = modelconnec.θ
164164
for i in 1:N
165165
if rand(Bernoulli(λ))

test/estimation.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,22 @@
4747
@test MF.fit(MarkovChainModel, data, r₊) ==
4848
MarkovChainModel(MF.Φ(estimators(data)..., r₊)...)
4949

50+
@test begin
51+
short_data = DiscreteTimeData(fill(true, 2, 1))
52+
m, v, w = estimators(short_data, 0)
53+
m == 1.0 && isfinite(v) && isfinite(w)
54+
end
55+
56+
@test begin
57+
μ, λ, p = MF.Φ(0.0, 0.0, 0.0, 0.5)
58+
isfinite(μ) && isfinite(λ) && isfinite(p) && 0 <= μ <= λ <= 1 && 0 <= p <= 1
59+
end
60+
61+
@test begin
62+
μ, λ, p = MF.Φ(1.0, 0.0, 0.0, 0.5)
63+
isfinite(μ) && isfinite(λ) && isfinite(p) && 0 <= μ <= λ <= 1 && 0 <= p <= 1
64+
end
65+
5066
@testset "inversion of Ψ" begin
5167
for (λ, p) in Iterators.product(0.1:0.1:0.9, 0.1:0.1:0.9)
5268
for μ in 0.1:0.1:λ

test/simulate.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
testvalue == [false, false, false, false]
2020
end
2121

22+
@test begin
23+
modelconnec =
24+
MF.MarkovChainConnectivity(MarkovChainModel(0.0, 0.0, 0.0), ones(Bool, 4, 4))
25+
values, child, parent = MF.backward_step(collect(1:4), modelconnec)
26+
all(i -> (haskey(values, i) || (i in child)), 1:4) && length(child) == length(parent)
27+
end
28+
2229
@test begin
2330
Random.seed!(1)
2431
Nsimu = 1e6

0 commit comments

Comments
 (0)