Skip to content

Commit

Permalink
add gamma distribution and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
MathNog committed Sep 2, 2024
1 parent 9301d9d commit 8bfbc53
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 12 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ test_m3.jl
test_dynamic_comb.jl
inovations_ssmodels.jl
test_component_expression.jl
results_variables_expressions.csv
test_gas.jl

# Files with csv extension
*.csv
16 changes: 11 additions & 5 deletions src/UnobservedComponentsGAS.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module UnobservedComponentsGAS
include("distributions/normal.jl")
include("distributions/t_location_scale.jl")
include("distributions/log_normal.jl")
include("distributions/gamma.jl")
include("initialization.jl")
include("fit.jl")
include("utils.jl")
Expand All @@ -32,17 +33,22 @@ module UnobservedComponentsGAS
include("residuals_diagnostics.jl")

const DICT_CODE = Dict(1 => "Normal",
2 => "tLocationScale" )
2 => "tLocationScale",
3 => "Gamma")

const DICT_SCORE = Dict("Normal" => score_normal,
"tLocationScale" => score_tlocationscale)
"tLocationScale" => score_tlocationscale,
"Gamma" => score_gamma)

const DICT_FISHER_INFORMATION = Dict("Normal" => fisher_information_normal,
"tLocationScale" => fisher_information_tlocationscale)
"tLocationScale" => fisher_information_tlocationscale,
"Gamma" => fisher_information_gamma)

const DICT_LOGPDF = Dict("Normal" => logpdf_normal,
"tLocationScale" => logpdf_tlocationscale)
"tLocationScale" => logpdf_tlocationscale,
"Gamma" => logpdf_gamma)

const DICT_CDF = Dict("Normal" => cdf_normal,
"tLocationScale" => cdf_tlocationscale)
"tLocationScale" => cdf_tlocationscale,
"Gamma" => cdf_gamma)
end
12 changes: 6 additions & 6 deletions src/components_dynamics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ function add_AR!(model::Ml, s::Vector{Fl}, T::Int64, ar::Union{Int64, Vector{Int
@variable(model, ϕ[1:max_order, idx_params])
@variable(model, κ_AR[idx_params])

@constraint(model, [i in idx_params], -2 κ_AR[i] 2)
@constraint(model, [i in idx_params], 0 κ_AR[i] 2)

# Revisar essas restrições com o Alves !!
for i in unique_orders
Expand Down Expand Up @@ -273,8 +273,8 @@ function add_random_walk_slope!(model::Ml, s::Vector{Fl}, T::Int64, random_walk_

@constraint(model, [t = 2:T, j in idx_params], b[t, j] == b[t - 1, j] + κ_b[j] * s[j][t])
@constraint(model, [t = 2:T, j in idx_params], RWS[t, j] == RWS[t - 1, j] + b[t - 1, j] + κ_RWS[j] * s[j][t])
@constraint(model, [j in idx_params], -2 κ_RWS[j] 2)
@constraint(model, [j in idx_params], -2 κ_b[j] 2)
@constraint(model, [j in idx_params], 0 κ_RWS[j] 0)
@constraint(model, [j in idx_params], 0 κ_b[j] 0)
end

"""
Expand All @@ -299,7 +299,7 @@ function add_random_walk!(model::Ml, s::Vector{Fl}, T::Int64, random_walk::Dict{
@variable(model, κ_RW[idx_params])

@constraint(model, [t = 2:T, j in idx_params], RW[t, j] == RW[t-1, j] + κ_RW[j] * s[j][t])
@constraint(model, [j in idx_params], -2 κ_RW[j] 2)
@constraint(model, [j in idx_params], 0 κ_RW[j] 2)
end

"""
Expand All @@ -325,7 +325,7 @@ function add_ar1!(model::Ml, s::Vector{Fl}, T::Int64, ar1::Dict{Int64, Bool}) w
@variable(model, κ_AR1_LEVEL[idx_params])
@variable(model, ω_AR1_LEVEL[idx_params])

@constraint(model, [i in idx_params], -2 κ_AR1_LEVEL[i] 2)
@constraint(model, [i in idx_params], 0 κ_AR1_LEVEL[i] 2)
@constraint(model, [i in idx_params], -0.95 <= ϕ_AR1_LEVEL[i] <= 0.95)

@constraint(model, [t = 2:T, j in idx_params], AR1_LEVEL[t, j] == ω_AR1_LEVEL[j] + ϕ_AR1_LEVEL[j] * AR1_LEVEL[t-1, j] + κ_AR1_LEVEL[j] * s[j][t])
Expand Down Expand Up @@ -435,7 +435,7 @@ function add_trigonometric_seasonality!(model::Ml, s::Vector{Fl}, T::Int64, seas


@variable(model, κ_S[idx_params_stochastic])
@constraint(model, [i in idx_params_stochastic], -2 κ_S[i] 2)
@constraint(model, [i in idx_params_stochastic], 0 κ_S[i] 2)
#JuMP.fix.(model[:κ_S][idx_params_deterministic], 1e-4)

@variable(model, γ_sto[1:unique_num_harmonic, 1:T, idx_params_stochastic])
Expand Down
203 changes: 203 additions & 0 deletions src/distributions/gamma.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"
Defines a Gamma distribution with parameters α λ.
From a shape (α) and ratio (β) parametrization, we obtain our parametrization making λ = α/β
"
mutable struct GammaDistribution <: ScoreDrivenDistribution
λ::Union{Missing, Float64}
α::Union{Missing, Float64}
end

"
Outer constructor for the Normal distribution.
"
function GammaDistribution()
return GammaDistribution(missing, missing)
end

"
Gamma Function Γ(x)
"
function Γ(x)
return SpecialFunctions.gamma(x)
end


"Auxiliar function Ψ1(α)"
function Ψ1(α)
return SpecialFunctions.digamma(α)
end


"Auxiliar function Ψ2(α)"
function Ψ2(α)
return SpecialFunctions.trigamma(α)
end


"
Evaluate the score of a Normal distribution with mean μ and variance σ², in observation y.
Colocar link aqui
"
function score_gamma(λ, α, y)

α <= 0 ? α = 1e-2 : nothing
λ <= 0 ? λ = 1e-4 : nothing

∇_α = log(y) - y/λ + log(α) - Ψ1(α) - log(λ) + 1
∇_λ =/λ)*((y/λ)-1)

return [∇_λ; ∇_α]
end

"
Evaluate the fisher information of a Normal distribution with mean μ and variance σ².
Colocar link aqui
"
function fisher_information_gamma(λ, α)

α <= 0 ? α = 1e-2 : nothing
λ <= 0 ? λ = 1e-4 : nothing

I_λ = α/^2)
I_α = Ψ2(α) - 1/α

return [I_λ 0; 0 I_α]
end

"
Evaluate the log pdf of a Normal distribution with mean μ and variance σ², in observation y.
"
function logpdf_gamma(λ, α, y)

return logpdf_gamma([λ, α], y)
end

"
Evaluate the log pdf of a Normal distribution with mean μ and variance σ², in observation y.
param[1] = α
param[2] = λ
"
function logpdf_gamma(param, y)

param[2] > 0 ? α = param[2] : α = 1e-2
param[1] > 0 ? λ = param[1] : λ = 1e-2

return Distributions.logpdf(Distributions.Gamma(α, λ/α), y)
end

"
Evaluate the cdf of a Gamma distribution with α,λ, in observation y.
"
function cdf_gamma(param::Vector{Float64}, y::Fl) where Fl

param[2] > 0 ? α = param[2] : α = 1e-2
param[1] > 0 ? λ = param[1] : λ = 1e-2

return Distributions.cdf(Distributions.Gamma(α, λ/α), y)
end

"
Returns the code of the Normal distribution. Is the key of DICT_CODE.
"
function get_dist_code(dist::GammaDistribution)
return 3
end

"
Returns the number of parameters of the Normal distribution.
"
function get_num_params(dist::GammaDistribution)
return 2
end

"
Simulates a value from a given Normal distribution.
param[1] = λ
param[2] = α
"
function sample_dist(param::Vector{Float64}, dist::GammaDistribution)

"A Gamma do pacote Distributions é parametrizada com shape α e scale θ"
"Como θ = 1/β e β = α/λ, segue-se que θ = λ/α"
param[2] > 0 ? α = param[2] : α = 1e-2
param[1] > 0 ? λ = param[1] : λ = 1e-2

return rand(Distributions.Gamma(α, λ/α))
end

"
Indicates which parameters of the Normal distribution must be positive.
"
function check_positive_constrainst(dist::GammaDistribution)
return [true, true]
end


function get_initial_α(y::Vector{Float64})

T = length(y)
model = JuMP.Model(Ipopt.Optimizer)
set_silent(model)
@variable(model, α >= 1e-2) # Ensure α is positive
@variable(model, λ[1:T] .>= 1e-4)
register(model, , 1, Γ; autodiff = true)
@NLobjective(model, Max, sum(-log(Γ(α)) - α*log(1/α) - α*log(λ[i]) +-1)*log(y[i]) -/λ[i])*y[i] for i in 1:T))
optimize!(model)
if has_values(model)
return JuMP.value.(α)
else
return fit_mle(Gamma, y).α
end
end

"
Returns a dictionary with the initial values of the parameters of Normal distribution that will be used in the model initialization.
"
function get_initial_params(y::Vector{Fl}, time_varying_params::Vector{Bool}, dist::GammaDistribution, seasonality::Dict{Int64, Union{Bool, Int64}}) where Fl

T = length(y)
dist_code = get_dist_code(dist)
seasonal_period = get_num_harmonic_and_seasonal_period(seasonality)[2]

initial_params = Dict()
fitted_distribution = fit_mle(Gamma, y)

# param[2] = λ = média
if time_varying_params[1]
initial_params[1] = y
else
initial_params[1] = fitted_distribution.α*fitted_distribution.θ
end

# param[1] = α
if time_varying_params[2]
initial_params[2] = get_seasonal_var(y, maximum(seasonal_period), dist)#(scaled_score.(y, ones(T) * var(diff(y)) , y, 0.5, dist_code, 2)).^2
else
initial_params[2] = get_initial_α(y)#mean(y)^2/var((y))
end

return initial_params
end


function get_seasonal_var(y::Vector{Fl}, seasonal_period::Int64, dist::GammaDistribution) where Fl

num_periods = ceil(Int, length(y) / seasonal_period)
seasonal_variances = zeros(Fl, length(y))

for i in 1:seasonal_period
month_data = y[i:seasonal_period:end]
num_observations = length(month_data)
if num_observations > 0
g = Distributions.fit(Gamma, month_data)
α = g.α
θ = g.θ
variance = α*^2)
for j in 1:num_observations
seasonal_variances[i + (j - 1) * seasonal_period] = variance
end
end
end
return seasonal_variances
end

1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ using JSON3, CSV, DataFrames, StateSpaceModels
include("test_fit_forecast_normal.jl")
include("test_fit_forecast_lognormal.jl")
include("test_fit_forecast_t.jl")
include("test_fit_forecast_gamma.jl")
include("test_components_dynamics.jl")
include("test_optimization.jl")
include("test_distributions.jl")
Expand Down
Loading

0 comments on commit 8bfbc53

Please sign in to comment.