Skip to content

Commit

Permalink
refactor: Clean up constructor and fix the sampling via stochastic re…
Browse files Browse the repository at this point in the history
…presentation for FGMCopula
  • Loading branch information
lrnv committed Sep 21, 2024
1 parent 18d8a56 commit 254018c
Showing 1 changed file with 31 additions and 54 deletions.
85 changes: 31 additions & 54 deletions src/MiscellaneousCopulas/FGMCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,19 @@ C(\\boldsymbol{u})=\\prod_{i=1}^{d}u_i \\left[1+ \\sum_{k=2}^{d}\\sum_{1 \\leq j
where `` \\bar{u}=1-u``.
More details about Farlie-Gumbel-Morgenstern (FGM) copula are found in :
Nelsen, Roger B. An introduction to copulas. Springer, 2006. Exercise 3.38.
We use the stochastic representation of the copula to obtain random samples.
Blier-Wong, C., Cossette, H., & Marceau, E. (2022). Stochastic representation of FGM copulas using multivariate Bernoulli random variables. Computational Statistics & Data Analysis, 173, 107506.
It has a few special cases:
- When d=2 and θ = 0, it is the IndependentCopula.
More details about Farlie-Gumbel-Morgenstern (FGM) copula are found in [nelsen2006](@cite).
We use the stochastic representation from [blier2022stochastic](@cite) to obtain random samples.
References:
* [nelsen2006](@cite) Nelsen, Roger B. An introduction to copulas. Springer, 2006.
* [blier2022stochastic](@cite) Blier-Wong, C., Cossette, H., & Marceau, E. (2022). Stochastic representation of FGM copulas using multivariate Bernoulli random variables. Computational Statistics & Data Analysis, 173, 107506.
"""
struct FGMCopula{d, Tθ} <: Copula{d}
struct FGMCopula{d, Tθ, Tf} <: Copula{d}
θ::Tθ
fᵢ::Tf
function FGMCopula(d, θ)
= typeof(θ)<:Vector ? θ : [θ]
if all.== 0)
Expand All @@ -40,70 +37,50 @@ struct FGMCopula{d, Tθ} <: Copula{d}
# Check first restrictions on parameters
any(abs.(vθ) .> 1) && throw(ArgumentError("Each component of the parameter vector must satisfy that |θᵢ| ≤ 1"))
length(vθ) != 2^d - d - 1 && throw(ArgumentError("Number of parameters (θ) must match the dimension ($d): 2ᵈ-d-1"))

# Last check:
rez = new{d, typeof(vθ)}(vθ)
for epsilon in Base.product(fill([-1, 1], d)...)
if 1 + _reduce_over_combinations(rez,epsilon,prod) < 0
if 1 + _fgm_red(vθ, epsilon) < 0
throw(ArgumentError("Invalid parameters. The parameters do not meet the condition to be an FGM copula"))
end
end
return rez

# Now construct the stochastic representation:
wᵢ = [_fgm_red(vθ, 1 .- 2*Base.reverse(digits(i, base=2, pad=d))) for i in 0:(2^d-1)]
fᵢ = Distributions.DiscreteNonParametric(0:(2^d-1), (1 .+ wᵢ)/2^d)
return new{d, typeof(vθ), typeof(fᵢ)}(vθ, fᵢ)
end
end
Base.eltype(C::FGMCopula) = eltype(C.θ)
function _reduce_over_combinations(C::FGMCopula{d,Tθ}, vector_to_combine, reducer_function) where {d,Tθ}
# This version of the reductor is non-allocative, which is much better in terms of performance.
# Moreover, since $d$ is a type parameter the loop will fold out at compile time :)
rez = zero(eltype(vector_to_combine))
# Iterate over all possible combinations of k elements, for k = 2, 3, ..., d
i = 1
function _fgm_red(θ, v)
# This function implements the reduction over combinations of the fgm copula.
# It is non-alocative thus performant :)
rez, d, i = zero(eltype(v)), length(v), 1
for k in 2:d
for indices in Combinatorics.combinations(1:d, k)
rez += C.θ[i] * reducer_function(vector_to_combine[indices])
i = i+1
end
for indices in Combinatorics.combinations(1:d, k)
rez += θ[i] * prod(v[indices])
i = i+1
end
end
return rez
end
function _cdf(fgm::FGMCopula, u::Vector{T}) where {T}
return prod(u) * (1 + _reduce_over_combinations(fgm, 1 .-u, prod))
end
function Distributions._logpdf(fgm::FGMCopula, u)
return log1p(_reduce_over_combinations(fgm, 1 .-2u, prod))
end
function Distributions._rand!(rng::Distributions.AbstractRNG, fgm::FGMCopula{d,Tθ}, x::AbstractVector{T}) where {d,Tθ, T <: Real}
if d == 2
u = rand(rng, T)
t = rand(rng, T)
a = 1.0 .+ fgm.θ .* (1.0-2.0*u)
b = sqrt.(a.^2 .-4.0 .*(a .-1.0).*t)
v = (2.0 .*t) ./(b .+ a)
x[1] = u
x[2] = v[1]
return x
elseif d > 2
I = zeros(T,d)
for i in 1:d
term = _reduce_over_combinations(fgm, I, x -> (-1)^sum(x))
I[i] = rand(rng) < (1 / 2^d) * (1 + term)
end
V0 = rand(rng, d)
V1 = rand(rng, d)
for j in 1:d
U_j = 1-sqrt(1-V0[j])*(1-V1[j])^(I[j])
x[j] = U_j
end
return x
end
_cdf(fgm::FGMCopula, u::Vector{T}) where {T} = prod(u) * (1 + _fgm_red(fgm.θ, 1 .-u))
Distributions._logpdf(fgm::FGMCopula, u) = log1p(_fgm_red(fgm.θ, 1 .-2u))
function Distributions._rand!(rng::Distributions.AbstractRNG, fgm::FGMCopula{d, Tθ, Tf}, x::AbstractVector{T}) where {d,Tθ, Tf, T <: Real}
I = Base.reverse(digits(rand(rng,fgm.fᵢ), base=2, pad=d))
V₀ = rand(rng, d)
V₁ = rand(rng, d)
x .= 1 .- sqrt.(V₀) .* (V₁ .^ I)
return x
end
τ(fgm::FGMCopula) = (2*fgm.θ[1])/9
τ(fgm::FGMCopula{2, Tθ, Tf}) where {Tθ,Tf} = (2*fgm.θ[1])/9
function τ⁻¹(::Type{FGMCopula}, τ)
if !all(-2/9 <= τi <= 2/9 for τi in τ)
throw(ArgumentError("For the FGM copula, tau must be in [-2/9, 2/9]."))
end
return max.(min.(9 * τ / 2, 1), -1)
end
ρ(fgm::FGMCopula) = (1*fgm.θ)/3 # this is weird as it will return a vector ?
ρ(fgm::FGMCopula{2, Tθ, Tf}) where {Tθ,Tf} = fgm.θ[1]/3
function ρ⁻¹(::Type{FGMCopula}, ρ)
if !all(-1/3 <= ρi <= 1/3 for ρi in ρ)
throw(ArgumentError("For the FGM copula, rho must be in [-1/3, 1/3]."))
Expand Down

0 comments on commit 254018c

Please sign in to comment.