Skip to content

Commit

Permalink
Make fitting tests pass on all archimedeans
Browse files Browse the repository at this point in the history
  • Loading branch information
lrnv committed Nov 20, 2023
1 parent e015525 commit a3ca09a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 61 deletions.
38 changes: 27 additions & 11 deletions src/ArchimedeanCopulas/AMHCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,39 @@ end

τ(C::AMHCopula) = _amh_tau_f(C.θ) # no closed form inverse...

_amh_tau_f) = θ == 1 ? 1/3 : 1 - 2+(1-θ)^2*log1p(-θ))/(3θ^2)
function _amh_tau_f(θ)

# if θ = -1, we obtain (5 -8*log(2))/3

function τ⁻¹(::Type{AMHCopula},τ)
if τ == zero(τ)
return τ
# unstable around zero, we instead cut its taylor expansion:
if abs(θ) < 0.01
return 2/9 * θ
+ 1/18 * θ^2
+ 1/45 * θ^3
+ 1/90 * θ^4
+ 2/315 * θ^5
+ 1/252 * θ^6
+ 1/378 * θ^7
+ 1/540 * θ^8
+ 2/1485 * θ^9
+ 1/990 * θ^10

Check warning on line 50 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L42-L50

Added lines #L42 - L50 were not covered by tests
end
if iszero(θ)
return zero(θ)

Check warning on line 53 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L53

Added line #L53 was not covered by tests
end
if τ > 1/3
u = isone(θ) ? θ : θ + (1-θ)^2 * log1p(-θ)
return 1 - (2/3)*u/θ^2
end
function τ⁻¹(::Type{AMHCopula},tau)
if tau == zero(tau)
return tau

Check warning on line 60 in src/ArchimedeanCopulas/AMHCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/AMHCopula.jl#L60

Added line #L60 was not covered by tests
elseif tau > 1/3
@warn "AMHCopula cannot handle kendall tau's greater than 1/3. We capped it to 1/3."
return one(τ)
end
if τ < (5 - 8*log(2))/3
elseif tau < (5 - 8*log(2))/3
@warn "AMHCopula cannot handle kendall tau's smaller than (5- 8ln(2))/3 (approx -0.1817). We capped it to this value."
return -one(τ)
return -one(tau)
end
return Roots.find_zero-> _amh_tau_f(θ) - τ, (-one(τ), one(τ)))
search_range = tau > 0 ? (0,1) : (-1,0)
return Roots.find_zero-> tau - _amh_tau_f(θ), search_range)
end
williamson_dist(C::AMHCopula{d,T}) where {d,T} = C.θ >= 0 ? WilliamsonFromFrailty(1 + Distributions.Geometric(1-C.θ),d) : WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d)

Expand Down
29 changes: 18 additions & 11 deletions src/ArchimedeanCopulas/FrankCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct FrankCopula{d,T} <: ArchimedeanCopula{d}
θ::T
function FrankCopula(d,θ)
if d > 2 && θ < 0
throw(ArgumentError("Negatively dependent Frank copulas cannot exists in dimensions > 2"))
throw(ArgumentError("Negatively dependent Frank copulas cannot exists in dimensions > 2. You passed θ = "))

Check warning on line 26 in src/ArchimedeanCopulas/FrankCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/FrankCopula.jl#L26

Added line #L26 was not covered by tests
end
if θ == -Inf
return WCopula(d)
Expand All @@ -47,22 +47,29 @@ end
# D₁ = GSL.sf_debye_1 # sadly, this is C code.
# could be replaced by :
# using QuadGK
D₁(x) = QuadGK.quadgk(t -> t/(exp(t)-1), 0, x)[1]/x
D₁(x) = QuadGK.quadgk(t -> t/expm1(t), 0, x)[1]/x
# to make it more general. but once gain, it requires changing the integrator at each evlauation,
# which is problematic.
# Better option is to try to include this function into SpecialFunctions.jl.


τ(C::FrankCopula) = 1+4(D₁(C.θ)-1)/C.θ
function τ⁻¹(::Type{FrankCopula},τ)
if τ == zero(τ)
return τ
function _frank_tau_f(θ)
if abs(θ) < sqrt(eps(θ))
# return the taylor approx.
return θ/9 * (1 -/10)^2)
else
return 1+4(D₁(θ)-1)/θ
end
if abs==1)
return Inf * τ
end
τ(C::FrankCopula) = _frank_tau_f(C.θ)
function τ⁻¹(::Type{FrankCopula},τ)
s,v = sign(τ),abs(τ)
if v == 0
return v

Check warning on line 67 in src/ArchimedeanCopulas/FrankCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/FrankCopula.jl#L67

Added line #L67 was not covered by tests
elseif v == 1
return s * Inf
else
return s*Roots.fzero(x -> _frank_tau_f(x)-v, 0, Inf)
end
x₀ = (1-τ)/4
return Roots.fzero(x -> (1-D₁(x))/x - x₀, 1e-4, Inf)
end

williamson_dist(C::FrankCopula{d,T}) where {d,T} = C.θ > 0 ? WilliamsonFromFrailty(Logarithmic(-C.θ), d) : WilliamsonTransforms.𝒲₋₁(t -> ϕ(C,t),d)
Expand Down
10 changes: 8 additions & 2 deletions src/ArchimedeanCopulas/GumbelBarnettCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@ function τ(C::GumbelBarnettCopula)
return 1+4*result
end
function τ⁻¹(::Type{GumbelBarnettCopula}, tau)
if tau == zero(tau)
return tau
if tau == 0
return zero(tau)

Check warning on line 47 in src/ArchimedeanCopulas/GumbelBarnettCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/GumbelBarnettCopula.jl#L47

Added line #L47 was not covered by tests
elseif tau > 0
@warn "GumbelBarnettCopula cannot handle positive kendall tau's, returning independence.."
return zero(tau)
elseif tau < τ(GumbelBarnettCopula(2,1))
@warn "GumbelBarnettCopula cannot handle negative kendall tau's smaller than ≈ -0.3613, so we capped to that value."
return one(tau)
end

# Define an anonymous function that takes a value x and computes τ
Expand Down
47 changes: 27 additions & 20 deletions src/ArchimedeanCopulas/InvGaussianCopula.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,45 @@ struct InvGaussianCopula{d,T} <: ArchimedeanCopula{d}
throw(ArgumentError("Theta must be non-negative."))
elseif θ == 0
return IndependentCopula(d)
elseif isinf(θ)
throw(ArgumentError("Theta cannot be infinite"))
# elseif isinf(θ)
# throw(ArgumentError("Theta cannot be infinite"))
else
return new{d,typeof(θ)}(θ)
end
end
end

ϕ( C::InvGaussianCopula, t) = exp((1-sqrt(1+2*((C.θ)^(2))*t))/C.θ)
ϕ⁻¹(C::InvGaussianCopula, t) = ((1-C.θ*log(t))^(2)-1)/(2*(C.θ)^(2))
function τ(C::InvGaussianCopula)

# Calculate the integral using an appropriate numerical integration method
result, _ = QuadGK.quadgk( x -> (x*((1-C.θ*log(x))^2-1))/(-2*C.θ*(1-C.θ*log(x))),0,1)

return 1+4*result
ϕ( C::InvGaussianCopula, t) = isinf(C.θ) ? exp(-sqrt(2*t)) : exp((1-sqrt(1+2*((C.θ)^(2))*t))/C.θ)
ϕ⁻¹(C::InvGaussianCopula, t) = isinf(C.θ) ? ln(t)^2/2 : ((1-C.θ*log(t))^(2)-1)/(2*(C.θ)^(2))
function _invg_tau_f(θ)
if θ == 0
return zero(θ)

Check warning on line 43 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L43

Added line #L43 was not covered by tests
elseif θ > 1e153 # should be Inf, but integrand has issues...
return 1/2
elseif θ < sqrt(eps(θ))
return zero(θ)

Check warning on line 47 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L47

Added line #L47 was not covered by tests
end
function _integrand(x,θ)
y = 1-θ*log(x)
ret = - x*(y^2-1)/(2θ*y)
return ret
end
rez, err = QuadGK.quadgk(x -> _integrand(x,θ),zero(θ),one(θ))
rez = 1+4*rez
return rez
end
τ(C::InvGaussianCopula) = _invg_tau_f(C.θ)
function τ⁻¹(::Type{InvGaussianCopula}, tau)
if tau == zero(tau)
return tau

Check warning on line 61 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L61

Added line #L61 was not covered by tests
end
if tau < 0
elseif tau < 0
@warn "InvGaussianCopula cannot handle negative dependencies, returning independence..."
return zero(τ)
return zero(tau)

Check warning on line 64 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L63-L64

Added lines #L63 - L64 were not covered by tests
elseif tau > 0.5
@warn "InvGaussianCopula cannot handle kendall tau greater than 0.5, using 0.5.."
return tau * Inf

Check warning on line 67 in src/ArchimedeanCopulas/InvGaussianCopula.jl

View check run for this annotation

Codecov / codecov/patch

src/ArchimedeanCopulas/InvGaussianCopula.jl#L66-L67

Added lines #L66 - L67 were not covered by tests
end

# Define an anonymous function that takes a value x and computes τ for an InvGaussianCopula copula with θ = x
τ_func(x) = τ(InvGaussianCopula(2,x))

# Set an initial value for x₀ (adjustable)
x = Roots.find_zero(x -> τ_func(x) - tau, (0.0, Inf))
return τ
return Roots.find_zero(x -> _invg_tau_f(x) - tau, (sqrt(eps(tau)), Inf))
end

williamson_dist(C::InvGaussianCopula{d,T}) where {d,T} = WilliamsonFromFrailty(Distributions.InverseGaussian(C.θ,1),d)
31 changes: 14 additions & 17 deletions test/archimedean_tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@
inv_works(T,tau) = Copulas.τ(T(2,Copulas.τ⁻¹(T,tau))) tau
check_rnd(T,min,max,N) = all(inv_works(T,x) for x in min .+ (max-min) .* rand(rng,N))

# working:
@test check_rnd(ClaytonCopula,-1,1,100)
@test check_rnd(GumbelCopula,0,1,100)
@test check_rnd(JoeCopula,0,1,100)

# not working:
@test_broken check_rnd(AMHCopula,(5 - 8*log(2))/3,1/3,100)
@test_broken check_rnd(FrankCopula,-1,1,100)
@test_broken check_rnd(GumbelBarnettCopula,0,1,100)
@test_broken check_rnd(InvGaussianCopula,0,1,100)
@test check_rnd(ClaytonCopula, -1, 1, 10)
@test check_rnd(GumbelCopula, 0, 1, 10)
@test check_rnd(JoeCopula, 0, 1, 10)
@test check_rnd(GumbelBarnettCopula, -0.35, 0, 10)
@test check_rnd(AMHCopula, -0.18, 1/3, 10)
@test check_rnd(FrankCopula, -1, 1, 10)
@test check_rnd(InvGaussianCopula,0,1/2,10)

end

Expand Down Expand Up @@ -71,22 +68,22 @@ end
data0 = rand(rng,C0,100)
@test all(pdf(C0,data0) .>= 0)
@test all(0 .<= cdf(C0,data0) .<= 1)
@test_broken fit(FrankCopula,data0)
fit(FrankCopula,data0)

C1 = FrankCopula(2,log(rand(rng)))
data1 = rand(rng,C1,100)
@test all(pdf(C1,data1) .>= 0)
@test all(0 .<= cdf(C1,data1) .<= 1)
@test_broken fit(FrankCopula,data1)
fit(FrankCopula,data1)


for d in 2:10
for θ [0.0,rand(rng),1.0,-log(rand(rng)), Inf]
for θ [1.0,1-log(rand(rng)), Inf]
C = FrankCopula(d,θ)
data = rand(rng,C,100)
data = rand(rng,C,10000)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
# fit(FrankCopula,data)
fit(FrankCopula,data)
end
end
@test true
Expand Down Expand Up @@ -132,7 +129,7 @@ end
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(GumbelBarnettCopula,data)
fit(GumbelBarnettCopula,data)
end
end
@test true
Expand All @@ -148,7 +145,7 @@ end
data = rand(rng,C,100)
@test all(pdf(C,data) .>= 0)
@test all(0 .<= cdf(C,data) .<= 1)
@test_broken fit(InvGaussianCopula,data)
fit(InvGaussianCopula,data)
end
end
@test true
Expand Down

0 comments on commit a3ca09a

Please sign in to comment.