From c0b8ba67d1be0ce504066bb6e902e236c48b28ec Mon Sep 17 00:00:00 2001 From: Mahmoud Bentriou <mahmoud.bentriou@centralesupelec.fr> Date: Fri, 27 Nov 2020 02:18:52 +0100 Subject: [PATCH] Reimplementation + methods of ParametricModel. It is more clear and more efficient. --- core/common.jl | 33 ++++++----- core/model.jl | 117 +++++++++++++++++++++----------------- tests/unit/model_prior.jl | 12 ++-- 3 files changed, 91 insertions(+), 71 deletions(-) diff --git a/core/common.jl b/core/common.jl index a0026fb..a28d819 100644 --- a/core/common.jl +++ b/core/common.jl @@ -1,4 +1,7 @@ +import Distributions: Distribution, Univariate, Continuous, UnivariateDistribution, + MultivariateDistribution, product_distribution + abstract type Model end abstract type AbstractTrajectory end @@ -32,11 +35,6 @@ struct Trajectory <: AbstractTrajectory transitions::Vector{Transition} end -struct ParametricModel - m::Model - map_l_param_dist::Dict{Vector{String},Distribution} -end - struct Edge transitions::Vector{Transition} check_constraints::Function @@ -76,6 +74,12 @@ struct SynchronizedTrajectory <: AbstractTrajectory transitions::Vector{Transition} end +struct ParametricModel + m::Model + l_param::Vector{String} + dist::Distribution +end + # Constructors function ContinuousTimeModel(d::Int, k::Int, map_var_idx::Dict, map_param_idx::Dict, l_transitions::Vector{String}, p::Vector{Float64}, x0::Vector{Int}, t0::Float64, @@ -105,16 +109,19 @@ LHA(A::LHA, map_var::Dict{String,Int}) = LHA(A.l_transitions, A.l_loc, A.Λ, Base.:*(m::ContinuousTimeModel, A::LHA) = SynchronizedModel(m, A) Base.:*(A::LHA, m::ContinuousTimeModel) = SynchronizedModel(m, A) -function ParametricModel(m::Model, priors::Tuple{Vector{String},Distribution}...) - map = Dict{Vector{String},Distribution}() +function ParametricModel(am::Model, priors::Tuple{String,UnivariateDistribution}...) + m = get_proba_model(am) + l_param = String[] + l_dist = Distribution{Univariate,Continuous}[] for prior in priors check_vars = true - for var in prior[1] - check_vars = check_vars && var in keys(get_proba_model(m).map_param_idx) - end - @assert check_vars - map[prior[1]] = prior[2] + str_p = prior[1] + dist = prior[2] + @assert str_p in keys(m.map_param_idx) + push!(l_param, str_p) + push!(l_dist, dist) end - return ParametricModel(m, map) + return ParametricModel(m, l_param, product_distribution(l_dist)) end + diff --git a/core/model.jl b/core/model.jl index 359133d..7147553 100644 --- a/core/model.jl +++ b/core/model.jl @@ -1,4 +1,7 @@ +import Random: rand, rand! +import Distributions: pdf + load_model(name_model::String) = include(get_module_path() * "/models/" * name_model * ".jl") function _resize_trajectory!(values::Vector{Vector{Int}}, times::Vector{Float64}, @@ -29,7 +32,11 @@ end Simulates a model. If `m::SynchronizedModel`, the simulation is synchronized with a Linear Hybrid Automaton. """ -function simulate(m::ContinuousTimeModel) +function simulate(m::ContinuousTimeModel; p::Union{Nothing,Vector{Float64}} = nothing) + p_sim = m.p + if p != nothing + p_sim = p + end # First alloc full_values = Vector{Vector{Int}}(undef, m.d) for i = eachindex(full_values) full_values[i] = zeros(Int, m.estim_min_states) end @@ -44,7 +51,7 @@ function simulate(m::ContinuousTimeModel) xn = m.x0 # View for type stability tn = m.t0 # at time n+1 - isabsorbing::Bool = m.isabsorbing(m.p,xn) + isabsorbing::Bool = m.isabsorbing(p_sim,xn) # If x0 is absorbing if isabsorbing _resize_trajectory!(full_values, times, transitions, 1) @@ -60,7 +67,7 @@ function simulate(m::ContinuousTimeModel) l_tr = Transition[nothing] # First we fill the allocated array for i = 2:m.estim_min_states - m.f!(vec_x, l_t, l_tr, xn, tn, m.p) + m.f!(vec_x, l_t, l_tr, xn, tn, p_sim) tn = l_t[1] if tn > m.time_bound break @@ -68,7 +75,7 @@ function simulate(m::ContinuousTimeModel) n += 1 xn = vec_x _update_values!(full_values, times, transitions, xn, tn, l_tr[1], i) - isabsorbing = m.isabsorbing(m.p,xn) + isabsorbing = m.isabsorbing(p_sim,xn) if isabsorbing break end @@ -90,7 +97,7 @@ function simulate(m::ContinuousTimeModel) i = 0 while i < m.buffer_size i += 1 - m.f!(vec_x, l_t, l_tr, xn, tn, m.p) + m.f!(vec_x, l_t, l_tr, xn, tn, p_sim) tn = l_t[1] if tn > m.time_bound i -= 1 @@ -99,7 +106,7 @@ function simulate(m::ContinuousTimeModel) xn = vec_x _update_values!(full_values, times, transitions, xn, tn, l_tr[1], m.estim_min_states+size_tmp+i) - isabsorbing = m.isabsorbing(m.p,xn) + isabsorbing = m.isabsorbing(p_sim,xn) if isabsorbing break end @@ -119,7 +126,11 @@ function simulate(m::ContinuousTimeModel) end return Trajectory(m, values, times, transitions) end -function simulate(product::SynchronizedModel) +function simulate(product::SynchronizedModel; p::Union{Nothing,Vector{Float64}} = nothing) + p_sim = m.p + if p != nothing + p_sim = p + end m = product.m A = product.automaton # First alloc @@ -137,7 +148,7 @@ function simulate(product::SynchronizedModel) xn = m.x0 # View for type stability tn = m.t0 Sn = copy(S0) - isabsorbing::Bool = m.isabsorbing(m.p,xn) + isabsorbing::Bool = m.isabsorbing(p_sim,xn) isacceptedLHA::Bool = isaccepted(Sn) # If x0 is absorbing if isabsorbing || isacceptedLHA @@ -155,7 +166,7 @@ function simulate(product::SynchronizedModel) Snplus1 = copy(Sn) # First we fill the allocated array for i = 2:m.estim_min_states - m.f!(vec_x, l_t, l_tr, xn, tn, m.p) + m.f!(vec_x, l_t, l_tr, xn, tn, p_sim) tn = l_t[1] if tn > m.time_bound break @@ -166,7 +177,7 @@ function simulate(product::SynchronizedModel) next_state!(Snplus1, A, xn, tn, tr_n, Sn) _update_values!(full_values, times, transitions, xn, tn, tr_n, i) Sn = Snplus1 - isabsorbing = m.isabsorbing(m.p,xn) + isabsorbing = m.isabsorbing(p_sim,xn) isacceptedLHA = isaccepted(Snplus1) if isabsorbing || isacceptedLHA break @@ -189,7 +200,7 @@ function simulate(product::SynchronizedModel) i = 0 while i < m.buffer_size i += 1 - m.f!(vec_x, l_t, l_tr, xn, tn, m.p) + m.f!(vec_x, l_t, l_tr, xn, tn, p_sim) tn = l_t[1] if tn > m.time_bound i -= 1 @@ -201,7 +212,7 @@ function simulate(product::SynchronizedModel) _update_values!(full_values, times, transitions, xn, tn, tr_n, m.estim_min_states+size_tmp+i) Sn = Snplus1 - isabsorbing = m.isabsorbing(m.p,xn) + isabsorbing = m.isabsorbing(p_sim,xn) isacceptedLHA = isaccepted(Snplus1) if isabsorbing || isacceptedLHA break @@ -223,11 +234,24 @@ function simulate(product::SynchronizedModel) return SynchronizedTrajectory(Sn, product, values, times, transitions) end - function simulate(m::ContinuousTimeModel, n::Int) end -function set_observed_var!(m::Model, g::Vector{String}) +isbounded(m::ContinuousTimeModel) = m.time_bound < Inf +function check_consistency(m::ContinuousTimeModel) + @assert m.d == length(m.map_var_idx) + @assert m.k == length(m.map_param_idx) + @assert m.k == length(m.p) + @assert length(m.g) <= m.d + @assert length(m._g_idx) == length(m.g) + @assert m.buffer_size >= 0 + @assert typeof(m.isabsorbing(m.p, m.x0)) == Bool + return true +end + +# Set and get Model fields +function set_observed_var!(am::Model, g::Vector{String}) + m = get_proba_model(am) dobs = length(g) _map_obs_var_idx = Dict{String}{Int}() _g_idx = zeros(Int, dobs) @@ -239,8 +263,8 @@ function set_observed_var!(m::Model, g::Vector{String}) m._g_idx = _g_idx m._map_obs_var_idx = _map_obs_var_idx end - -function observe_all!(m::Model) +function observe_all!(am::Model) + m = get_proba_model(am) g = Vector{String}(undef, m.d) _g_idx = collect(1:m.d) for var in keys(m.map_var_idx) @@ -250,32 +274,17 @@ function observe_all!(m::Model) m._g_idx = _g_idx m._map_obs_var_idx = m.map_var_idx end - -isbounded(m::ContinuousTimeModel) = m.time_bound < Inf -function check_consistency(m::ContinuousTimeModel) - @assert m.d == length(m.map_var_idx) - @assert m.k == length(m.map_param_idx) - @assert m.k == length(m.p) - @assert length(m.g) <= m.d - @assert length(m._g_idx) == length(m.g) - @assert m.buffer_size >= 0 - @assert typeof(m.isabsorbing(m.p, m.x0)) == Bool - return true -end - set_param!(m::ContinuousTimeModel, p::Vector{Float64}) = (m.p = p) set_param!(m::ContinuousTimeModel, name_p::String, p_i::Float64) = (m.p[m.map_param_idx[name_p]] = p_i) function set_param!(m::ContinuousTimeModel, l_name_p::Vector{String}, p::Vector{Float64}) - nb_param = length(l_name_p) - for i = 1:nb_param + for i = eachindex(l_name_p) set_param!(m, l_name_p[i], p[i]) end end get_param(m::ContinuousTimeModel) = m.p getindex(m::ContinuousTimeModel, name_p::String) = m.p[m.map_param_idx[name_p]] -set_time_bound!(m::ContinuousTimeModel, b::Float64) = (m.time_bound = b) -set_time_bound!(sm::SynchronizedModel, b::Float64) = set_time_bound!(sm.m, b) +set_time_bound!(am::Model, b::Float64) = (get_proba_model(am).time_bound = b) function getproperty(m::ContinuousTimeModel, sym::Symbol) if sym == :dobs @@ -284,33 +293,37 @@ function getproperty(m::ContinuousTimeModel, sym::Symbol) return getfield(m, sym) end end + +function getproperty(pm::ParametricModel, sym::Symbol) + if sym == :df + return length(pm.l_param) + else + return getfield(pm, sym) + end +end get_proba_model(m::ContinuousTimeModel) = m get_proba_model(sm::SynchronizedModel) = sm.m -get_observed_var(m::ContinuousTimeModel) = m.g -get_observed_var(sm::SynchronizedModel) = (sm.m).g +get_proba_model(pm::ParametricModel) = get_proba_model(pm.m) +get_observed_var(m::Model) = get_proba_model(am).g # Prior methods -function draw_model!(pm::ParametricModel) - dict_dist = pm.map_l_param_dist - for l_name in keys(dict_dist) - if length(l_name) == 1 - set_param!(get_proba_model(pm.m), l_name[1], rand(dict_dist[l_name])) - else - set_param!(get_proba_model(pm.m), l_name, rand(dict_dist[l_name])) - end +draw_model!(pm::ParametricModel) = set_param!(get_proba_model(pm), pm.l_param, rand(pm.dist)) +draw!(vec_p::AbstractVector{Float64}, pm::ParametricModel) = rand!(pm.dist, vec_p) +function draw!(mat_p::Matrix{Float64}, pm::ParametricModel) + for i = 1:size(mat_p)[2] + draw!(view(mat_p, :, i), pm) end end -function dim_free_param(pm::ParametricModel) - return 1 -end - -function draw!(vec_p::Vector{Float64}, pm::ParametricModel) +prior_density(vec_p::AbstractVector{Float64}, pm::ParametricModel) = pdf(pm.dist, vec_p) +function prior_density!(res_density::Vector{Float64}, mat_p::Matrix{Float64}, pm::ParametricModel) + for i = eachindex(res_density) + res_density[i] = prior_density(view(mat_p, :, i), pm) + end end -function draw!(mat_p::Matrix{Float64}, pm::ParametricModel) -end +function check_bounds() end -function prior_density!(wl::Vector{Float64}, mat_p::Matrix{Float64}, pm::ParametricModel) -end +# to do: simulate(pm), create_res_dir, check_bounds, ajouter un champ _param_idx pour pm. +# diff --git a/tests/unit/model_prior.jl b/tests/unit/model_prior.jl index b634aec..5d7721b 100644 --- a/tests/unit/model_prior.jl +++ b/tests/unit/model_prior.jl @@ -5,17 +5,17 @@ load_model("ER") test_all = true load_model("ER") -prior1 = ParametricModel(ER, (["k2"], Uniform(2.0, 4.0))) +prior1 = ParametricModel(ER, ("k2", Uniform(2.0, 4.0))) draw_model!(prior1) -test_all = test_all && 2.0 <= ER["k2"] <= 4.0 +test_all = test_all && 2.0 <= ER["k2"] <= 4.0 && prior1.df == 1 -prior2 = ParametricModel(ER, (["k3","k2"], Product(Uniform.([2.5,6.0], [3.5,7.0])))) +prior2 = ParametricModel(ER, ["k3","k2"], Product(Uniform.([2.5,6.0], [3.5,7.0]))) draw_model!(prior2) -test_all = test_all && 2.5 <= ER["k3"] <= 3.5 && 6.0 <= ER["k2"] <= 7.0 +test_all = test_all && 2.5 <= ER["k3"] <= 3.5 && 6.0 <= ER["k2"] <= 7.0 && prior2.df == 2 -prior3 = ParametricModel(ER, (["k3"], Uniform(10.0, 11.0)), (["k2"], Uniform(13.0, 14.0))) +prior3 = ParametricModel(ER, ("k3", Uniform(10.0, 11.0)), ("k2", Uniform(13.0, 14.0))) draw_model!(prior3) -test_all = test_all && 10.0 <= ER["k3"] <= 11.0 && 13.0 <= ER["k2"] <= 14.0 +test_all = test_all && 10.0 <= ER["k3"] <= 11.0 && 13.0 <= ER["k2"] <= 14.0 && prior3.df == 2 return test_all -- GitLab