Skip to content
Snippets Groups Projects
  • Bentriou Mahmoud's avatar
    de9fbd7e
    Add of a new automaton related to ABC. · de9fbd7e
    Bentriou Mahmoud authored
    It computes the euclidean distance and finish the simulation if the
    distance is above the tolerance epsilon.
    I ha to change some function signature of types in core/model.jl but it
    was minor changes.
    I add a new test + change some others related to automata.
    A minor rewriting core/model for better readability
    de9fbd7e
    History
    Add of a new automaton related to ABC.
    Bentriou Mahmoud authored
    It computes the euclidean distance and finish the simulation if the
    distance is above the tolerance epsilon.
    I ha to change some function signature of types in core/model.jl but it
    was minor changes.
    I add a new test + change some others related to automata.
    A minor rewriting core/model for better readability
_utils_abc.jl 5.87 KiB

function _init_abc_automaton!(mat_p_old::Matrix{Float64}, vec_dist::Vector{Float64}, 
                              pm::ParametricModel, sym_var_aut::VariableAutomaton;
                              l_obs::Union{Nothing,AbstractVector} = nothing, 
                              func_dist::Union{Nothing,Function} = nothing)
    vec_p = zeros(pm.df)
    for i = eachindex(vec_dist)
        draw!(vec_p, pm)
        mat_p_old[:,i] = vec_p
        if l_obs == nothing
            S = volatile_simulate(pm, vec_p; epsilon = Inf)
            vec_dist[i] = S[sym_var_aut]
        else
            l_sim = [simulate(pm, vec_p) for i = 1:length(l_obs)]
            vec_dist[i] = func_dist(l_sim, l_obs)
        end
    end
end

function _compute_epsilon(vec_dist::Vector{Float64}, α::Float64, 
                          old_eps::Float64, last_eps::Float64)
    u_vec_dist = unique(vec_dist)
    if length(u_vec_dist) == 2
        epsilon = quantile(u_vec_dist, α)
    else
        epsilon = quantile(vec_dist, α)
    end
    if old_eps == epsilon
        print("eps == old_eps,  we readjust")
        s_dist = sort(u_vec_dist)
        max_dist = 0.0
        for d in s_dist
            if (d < epsilon) && (max_dist < d)
                max_dist = d
            end
        end
        max_dist = (max_dist == 0) ? 0.9*epsilon : max_dist
        epsilon = max_dist
    end
    epsilon = (epsilon < last_eps) ? last_eps : epsilon
end

function _task_worker!(d_mat_p::DArray{Float64,2}, d_vec_dist::DArray{Float64,1},
                       d_wl_current::DArray{Float64,1},
                       pm::ParametricModel, epsilon::Float64,
                       wl_old::Vector{Float64}, mat_p_old::Matrix{Float64},
                       mat_cov::Matrix{Float64}, tree_mat_p::Union{Nothing,KDTree}, 
                       kernel_type::String, sym_var_aut::VariableAutomaton;
                       l_obs::Union{Nothing,AbstractVector} = nothing, 
                       func_dist::Union{Nothing,Function} = nothing)
    mat_p = localpart(d_mat_p)
    vec_dist = localpart(d_vec_dist)
    wl_current = localpart(d_wl_current)
    l_nbr_sim = zeros(Int, length(vec_dist))
    Threads.@threads for i = eachindex(vec_dist)
        _update_param!(mat_p, vec_dist, l_nbr_sim, wl_current, i, pm, epsilon,
                       wl_old, mat_p_old, mat_cov, tree_mat_p, kernel_type, sym_var_aut;
                       l_obs = l_obs, func_dist = func_dist)
    end
    return sum(l_nbr_sim)
end

function _draw_param_kernel!(vec_p_prime::Vector{Float64}, 
                             vec_p_star::SubArray{Float64,1}, 
                             mat_p_old::Matrix{Float64}, wl_old::Vector{Float64},
                             mat_cov::Matrix{Float64}, 
                             tree_mat_p::Union{KDTree,Nothing}, kernel_type::String)
    if kernel_type == "mvnormal"
        d_mvnormal = MvNormal(vec_p_star, mat_cov)
        rand!(d_mvnormal, vec_p_prime)
    elseif kernel_type == "knn_mvnormal"
        k = Int(round(0.25 * size(mat_p_old)[2]))
        idxs, dist = knn(tree_mat_p, vec_p_star, k, true)
        knn_mat_cov = 2 * cov(mat_p_old[:,idxs], ProbabilityWeights(wl_old[idxs]), 2; corrected=false)
        d_knn_mvnormal = Distributions.MvNormal(vec_p_star, knn_mat_cov)
        rand!(d_knn_mvnormal, vec_p_prime)
        return knn_mat_cov
    else
        error("Unknown specified kernel")
    end
end

function _update_param!(mat_p::Matrix{Float64}, vec_dist::Vector{Float64}, 
                        l_nbr_sim::Vector{Int}, wl_current::Vector{Float64}, 
                        idx::Int,
                        pm::ParametricModel, epsilon::Float64,
                        wl_old::Vector{Float64}, mat_p_old::Matrix{Float64},
                        mat_cov::Matrix{Float64}, tree_mat_p::Union{Nothing,KDTree}, 
                        kernel_type::String, sym_var_aut::VariableAutomaton;
                        l_obs::Union{Nothing,AbstractVector} = nothing, 
                        func_dist::Union{Nothing,Function} = nothing)
    d_weights = Categorical(wl_old)
    dist_sim = Inf
    nbr_sim = 0
    vec_p_prime = zeros(pm.df)
    while dist_sim > epsilon
        ind_p_star = rand(d_weights)
        vec_p_star = view(mat_p_old, :, ind_p_star)
        knn_mat_cov = _draw_param_kernel!(vec_p_prime, vec_p_star, mat_p_old, wl_old, mat_cov, tree_mat_p, kernel_type)
        if !insupport(pm, vec_p_prime)
            continue
        end
        if l_obs == nothing
            S = volatile_simulate(pm, vec_p_prime; epsilon = epsilon)
            dist_sim = S[sym_var_aut]
        else
            l_sim = [simulate(pm, vec_p_prime) for i = 1:length(l_obs)]
            dist_sim = func_dist(l_sim, l_obs)
        end
        nbr_sim += 1
    end
    
    if kernel_type == "mvnormal"
        mat_cov_kernel = mat_cov 
    elseif kernel_type == "knn_mvnormal"
        mat_cov_kernel = knn_mat_cov
    else
        error("Unknown kernel")
    end
    # Update
    mat_p[:,idx] = vec_p_prime
    vec_dist[idx] = dist_sim
    l_nbr_sim[idx] = nbr_sim
    _update_weight!(wl_current, idx, pm, wl_old, mat_p_old, vec_p_prime, mat_cov_kernel, kernel_type)
end

function _update_weight!(wl_current::Vector{Float64}, idx::Int,
                         pm::ParametricModel,
                         wl_old::Vector{Float64}, mat_p_old::Matrix{Float64}, 
                         vec_p_prime::Vector{Float64},
                         mat_cov_kernel::Matrix{Float64}, kernel_type::String) 
    denom = 0.0
    for j in 1:length(wl_old)
        #denom += wl_old[j] * Distributions.pdf(d_normal, inv_sqrt_mat_cov * (vec_p_current - mat_p_old[:,j]))::Float64 
        denom += wl_old[j] * pdf(MvNormal(mat_p_old[:,j], mat_cov_kernel), vec_p_prime)::Float64 
    end
    wl_current[idx] = prior_pdf(pm, vec_p_prime) / denom 
end

function effective_sample_size(wl::AbstractVector{Float64})
    n_eff = 0.0
    wls = sum(wl)
    if wls > 0.0
        n_eff = wls ^ 2 / sum(wl .^ 2)
    end
    return n_eff
end