-
Bentriou Mahmoud authored
models. It's a better semantic and improve performance as well as readability of the code. All the tests passes (except the remark in the last commit).
Bentriou Mahmoud authoredmodels. It's a better semantic and improve performance as well as readability of the code. All the tests passes (except the remark in the last commit).
model.jl 17.75 KiB
import Random: rand, rand!
import Distributions: insupport, pdf
function _resize_trajectory!(values::Vector{Vector{Int}}, times::Vector{Float64},
transitions::Vector{Transition}, size::Int)
for i = eachindex(values) resize!(@inbounds(values[i]), size) end
resize!(times, size)
resize!(transitions, size)
end
function _finish_bounded_trajectory!(values::Vector{Vector{Int}}, times::Vector{Float64},
transitions::Vector{Transition}, time_bound::Float64)
for i = eachindex(values) push!(@inbounds(values[i]), @inbounds(values[i][end])) end
push!(times, time_bound)
push!(transitions, nothing)
end
function _update_values!(values::Vector{Vector{Int}}, times::Vector{Float64}, transitions::Vector{Transition},
xn::Vector{Int}, tn::Float64, tr_n::Transition, idx::Int)
for k = eachindex(values) @inbounds(values[k][idx] = xn[k]) end
@inbounds(times[idx] = tn)
@inbounds(transitions[idx] = tr_n)
end
"""
`simulate(m)`
Simulates a model. If `m::SynchronizedModel`, the simulation is synchronized with a
Linear Hybrid Automaton.
"""
function simulate(m::ContinuousTimeModel; p::Union{Nothing,AbstractVector{Float64}} = nothing)
p_sim = m.p
if p != nothing
p_sim = p
end
# First alloc
full_values = Vector{Vector{Int}}(undef, m.dim_state)
for i = eachindex(full_values) full_values[i] = zeros(Int, m.estim_min_states) end
times = zeros(Float64, m.estim_min_states)
transitions = Vector{Transition}(undef, m.estim_min_states)
# Initial values
for i = eachindex(full_values) full_values[i][1] = m.x0[i] end
times[1] = m.t0
transitions[1] = nothing
# Values at time n
n = 1
xn = copy(m.x0)
tn = m.t0
# at time n+1
isabsorbing::Bool = m.isabsorbing(p_sim,xn)
# If x0 is absorbing
if isabsorbing
_resize_trajectory!(full_values, times, transitions, 1)
values = full_values[m._g_idx]
if isbounded(m)
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
return Trajectory(m, values, times, transitions)
end
# Alloc of vectors where we stock n+1 values
vec_x = zeros(Int, m.dim_state)
l_t = Float64[0.0]
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, p_sim)
tn = l_t[1]
if tn > m.time_bound || vec_x == xn
isabsorbing = (vec_x == xn)
break
end
n += 1
copyto!(xn, vec_x)
_update_values!(full_values, times, transitions, xn, tn, l_tr[1], i)
end
# If simulation ended before the estimation of states
if n < m.estim_min_states
_resize_trajectory!(full_values, times, transitions, n)
values = full_values[m._g_idx]
if isbounded(m)
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
return Trajectory(m, values, times, transitions)
end
# Otherwise, buffering system
size_tmp = 0
while !isabsorbing && tn <= m.time_bound
# Alloc buffer
_resize_trajectory!(full_values, times, transitions, m.estim_min_states+size_tmp+m.buffer_size)
i = 0
while i < m.buffer_size
i += 1
m.f!(vec_x, l_t, l_tr, xn, tn, p_sim)
tn = l_t[1]
if tn > m.time_bound
i -= 1
break
end
if vec_x == xn
isabsorbing = true
i -= 1
break
end
copyto!(xn, vec_x)
_update_values!(full_values, times, transitions,
xn, tn, l_tr[1], m.estim_min_states+size_tmp+i)
end
# If simulation ended before the end of buffer
if i < m.buffer_size
_resize_trajectory!(full_values, times, transitions, m.estim_min_states+size_tmp+i)
end
size_tmp += i
n += i
end
values = full_values[m._g_idx]
if isbounded(m)
# Add last value: the convention is the last transition is nothing,
# the trajectory is bounded
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
return Trajectory(m, values, times, transitions)
end
function simulate(product::SynchronizedModel; p::Union{Nothing,AbstractVector{Float64}} = nothing,
verbose::Bool = false)
m = product.m
A = product.automaton
p_sim = m.p
if p != nothing
p_sim = p
end
# First alloc
full_values = Vector{Vector{Int}}(undef, m.dim_state)
for i = eachindex(full_values) full_values[i] = zeros(Int, m.estim_min_states) end
times = zeros(Float64, m.estim_min_states)
transitions = Vector{Transition}(undef, m.estim_min_states)
# Initial values
for i = eachindex(full_values) full_values[i][1] = m.x0[i] end
times[1] = m.t0
transitions[1] = nothing
S0 = init_state(A, m.x0, m.t0)
# Values at time n
n = 1
xn = copy(m.x0)
tn = m.t0
Sn = copy(S0)
isabsorbing::Bool = m.isabsorbing(p_sim,xn)
isacceptedLHA::Bool = isaccepted(Sn)
# Alloc of vectors where we stock n+1 values
vec_x = zeros(Int, m.dim_state)
l_t = Float64[0.0]
l_tr = Transition[nothing]
Snplus1 = copy(Sn)
# If x0 is absorbing
if isabsorbing || isacceptedLHA
_resize_trajectory!(full_values, times, transitions, 1)
values = full_values[m._g_idx]
if isbounded(m)
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
if isabsorbing && !isacceptedLHA
next_state!(Snplus1, A, xn, m.time_bound, nothing, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
end
return SynchronizedTrajectory(Sn, product, values, times, transitions)
end
# First we fill the allocated array
for i = 2:m.estim_min_states
m.f!(vec_x, l_t, l_tr, xn, tn, p_sim)
tn = l_t[1]
if tn > m.time_bound || vec_x == xn
isabsorbing = (vec_x == xn)
break
end
n += 1
copyto!(xn, vec_x)
tr_n = l_tr[1]
next_state!(Snplus1, A, xn, tn, tr_n, Sn; verbose = verbose)
_update_values!(full_values, times, transitions, xn, tn, tr_n, i)
copyto!(Sn, Snplus1)
isacceptedLHA = isaccepted(Snplus1)
if isabsorbing || isacceptedLHA
break
end
end
# If simulation ended before the estimation of states
if n < m.estim_min_states
_resize_trajectory!(full_values, times, transitions, n)
values = full_values[m._g_idx]
if isbounded(m)
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
if isabsorbing && !isacceptedLHA
next_state!(Snplus1, A, xn, m.time_bound, nothing, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
end
return SynchronizedTrajectory(Sn, product, values, times, transitions)
end
# Otherwise, buffering system
size_tmp = 0
while !isabsorbing && tn <= m.time_bound && !isacceptedLHA
# Alloc buffer
_resize_trajectory!(full_values, times, transitions, m.estim_min_states+size_tmp+m.buffer_size)
i = 0
while i < m.buffer_size
i += 1
m.f!(vec_x, l_t, l_tr, xn, tn, p_sim)
tn = l_t[1]
if tn > m.time_bound
i -= 1
break
end
if vec_x == xn
isabsorbing = true
i -= 1
break
end
copyto!(xn, vec_x)
tr_n = l_tr[1]
next_state!(Snplus1, A, xn, tn, tr_n, Sn; verbose = verbose)
_update_values!(full_values, times, transitions,
xn, tn, tr_n, m.estim_min_states+size_tmp+i)
copyto!(Sn, Snplus1)
isacceptedLHA = isaccepted(Snplus1)
if isabsorbing || isacceptedLHA
break
end
end
# If simulation ended before the end of buffer
if i < m.buffer_size
_resize_trajectory!(full_values, times, transitions, m.estim_min_states+size_tmp+i)
end
size_tmp += i
n += i
end
values = full_values[m._g_idx]
if isbounded(m) && !isaccepted(Sn)
# Add last value: the convention is the last transition is nothing,
# the trajectory is bounded
_finish_bounded_trajectory!(values, times, transitions, m.time_bound)
end
if isabsorbing && !isacceptedLHA
next_state!(Snplus1, A, xn, m.time_bound, nothing, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
end
return SynchronizedTrajectory(Sn, product, values, times, transitions)
end
"""
`volatile_simulate(sm::SynchronizedModel; p, verbose)`
Simulates a model synchronized with an automaton but does not store the values of the simulation
in order to improve performance.
It returns the last state of the simulation `S::StateLHA` not a trajectory `σ::SynchronizedTrajectory`.
"""
function volatile_simulate(product::SynchronizedModel;
p::Union{Nothing,AbstractVector{Float64}} = nothing, verbose::Bool = false)
m = product.m
A = product.automaton
p_sim = m.p
if p != nothing
p_sim = p
end
S0 = init_state(A, m.x0, m.t0)
# Values at time n
n = 1
xn = copy(m.x0)
tn = m.t0
Sn = copy(S0)
isabsorbing::Bool = m.isabsorbing(p_sim,xn)
isacceptedLHA::Bool = isaccepted(Sn)
# Alloc of vectors where we stock n+1 values
vec_x = zeros(Int, m.dim_state)
l_t = Float64[0.0]
l_tr = Transition[nothing]
Snplus1 = copy(Sn)
# If x0 is absorbing
if isabsorbing || isacceptedLHA
if !isacceptedLHA
next_state!(Snplus1, A, xn, m.time_bound, nothing, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
end
return Sn
end
while !isabsorbing && tn <= m.time_bound && !isacceptedLHA
m.f!(vec_x, l_t, l_tr, xn, tn, p_sim)
tn = l_t[1]
if tn > m.time_bound
i -= 1
break
end
if vec_x == xn
isabsorbing = true
break
end
copyto!(xn, vec_x)
tr_n = l_tr[1]
next_state!(Snplus1, A, xn, tn, tr_n, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
isacceptedLHA = isaccepted(Snplus1)
n += 1
end
if isabsorbing && !isacceptedLHA
next_state!(Snplus1, A, xn, m.time_bound, nothing, Sn; verbose = verbose)
copyto!(Sn, Snplus1)
end
return Sn
end
"""
`simulate(pm::ParametricModel, p_prior::AbstractVector{Float64})
Simulates the model contained in pm with p_prior values.
It simulates the model by taking the parameters contained in get_proba_model(pm).p and
replace the 1D parameters pm.params with p_prior.
"""
function simulate(pm::ParametricModel, p_prior::AbstractVector{Float64})
full_p = copy(get_proba_model(pm).p)
full_p[pm._param_idx] = p_prior
return simulate(pm.m; p = full_p)
end
"""
`volatile_simulate(pm::ParametricModel, p_prior::AbstractVector{Float64})
A volatile version of `simulate(pm::ParametricModel, p_prior::AbstractVector{Float64})`.
The model in pm should be of type SynchronizedModel (`typeof(pm.m) <: SynchronizedModel`).
It returns `S::StateLHA`, not a trajectory.
"""
function volatile_simulate(pm::ParametricModel, p_prior::AbstractVector{Float64})
@assert typeof(pm.m) <: SynchronizedModel
full_p = copy(get_proba_model(pm).p)
full_p[pm._param_idx] = p_prior
return volatile_simulate(pm.m; p = full_p)
end
"""
`distribute_mean_value_lha(sm::SynchronizedModel, sym_var::Symbol, nbr_stim::Int)`
Distribute over workers the computation of the mean value
of a LHA over `nbr_sim` simulations of the model.
"""
function distribute_mean_value_lha(sm::SynchronizedModel, sym_var::Union{VariableAutomaton,Vector{VariableAutomaton}}, nbr_sim::Int)
sum_val = @distributed (+) for i = 1:nbr_sim
volatile_simulate(sm)[sym_var]
end
return sum_val / nbr_sim
end
function mean_value_lha(sm::SynchronizedModel, sym_var::VariableAutomaton, nbr_sim::Int)
sum_val = 0.0
for i = 1:nbr_sim
sum_val += volatile_simulate(sm)[sym_var]
end
return sum_val / nbr_sim
end
function distribute_prob_accept_lha(sm::SynchronizedModel, nbr_sim::Int)
sum_val = @distributed (+) for i = 1:nbr_sim
Int(isaccepted(volatile_simulate(sm)))
end
return sum_val / nbr_sim
end
function Base.show(io::IO, m::ContinuousTimeModel)
print(io, "$(m.name) model (ContinuousTimeModel)\n")
print(io, "- variables :\n")
for (var, idx) in m.map_var_idx
print(io, "* $var (index = $idx in state space)\n")
end
print(io, "- parameters :\n")
for (param, idx) in m.map_param_idx
print(io, "* $param (index = $idx in parameter space)\n")
end
print(io, "- transitions : $(join(m.transitions,','))\n")
print(io, "- observed variables :\n")
for i in eachindex(m.g)
print(io, "* $(m.g[i]) (index = $i in observed state space, index = $(m._g_idx[i]) in state space)\n")
end
print(io, "p = $(m.p)\n")
print(io, "x0 = $(m.x0)\n")
print(io, "t0 = $(m.t0)\n")
print(io, "time bound = $(m.time_bound)")
end
isbounded(m::Model) = get_proba_model(m).time_bound < Inf
function check_consistency(m::ContinuousTimeModel)
@assert m.dim_state == length(m.map_var_idx)
@assert m.dim_state == length(m.x0)
@assert m.dim_params == length(m.map_param_idx)
@assert m.dim_params == length(m.p)
@assert length(m.g) <= m.dim_state
@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{VariableModel})
m = get_proba_model(am)
dim_obs_state = length(g)
_map_obs_var_idx = Dict{VariableModel}{Int}()
_g_idx = zeros(Int, dim_obs_state)
for i = 1:dim_obs_state
_g_idx[i] = m.map_var_idx[g[i]] # = ( (g[i] = i-th obs var)::VariableModel => idx in state space )
_map_obs_var_idx[g[i]] = i
end
m.g = g
m._g_idx = _g_idx
m._map_obs_var_idx = _map_obs_var_idx
end
function observe_all!(am::Model)
m = get_proba_model(am)
g = Vector{VariableModel}(undef, m.dim_state)
_g_idx = collect(1:m.dim_state)
for var in keys(m.map_var_idx)
g[m.map_var_idx[var]] = var
end
m.g = g
m._g_idx = _g_idx
m._map_obs_var_idx = m.map_var_idx
end
function set_param!(am::Model, new_p::Vector{Float64})
m = get_proba_model(am)
@assert length(new_p) == m.dim_params "New parameter vector hasn't the same dimension of parameter space"
m.p = new_p
end
function set_param!(am::Model, name_p::ParameterModel, p_i::Float64)
m = get_proba_model(am)
m.p[m.map_param_idx[name_p]] = p_i
end
function set_param!(am::Model, l_name_p::Vector{ParameterModel}, p::Vector{Float64})
m = get_proba_model(am)
@assert length(l_name_p) == length(p) "Parameter names vector and parameter values haven't the same dimensions"
for i = eachindex(l_name_p)
set_param!(m, l_name_p[i], p[i])
end
end
function set_x0!(am::Model, new_x0::Vector{Int})
m = get_proba_model(am)
@assert length(new_x0) == m.dim_state "New x0 vector hasn't the dimension of state space"
m.x0 = new_x0
end
set_time_bound!(am::Model, b::Float64) = (get_proba_model(am).time_bound = b)
get_param(am::Model) = get_proba_model(am).p
function getindex(am::Model, name_p::ParameterModel)
m = get_proba_model(am)
m.p[m.map_param_idx[name_p]]
end
function getproperty(m::ContinuousTimeModel, sym::Symbol)
if sym == :dim_obs_state
return length(m.g)
else
return getfield(m, sym)
end
end
function getproperty(pm::ParametricModel, sym::Symbol)
if sym == :df
return length(pm.params)
else
return getfield(pm, sym)
end
end
get_proba_model(m::ContinuousTimeModel) = m
get_proba_model(sm::SynchronizedModel) = sm.m
get_proba_model(pm::ParametricModel) = get_proba_model(pm.m)
get_observed_var(m::Model) = get_proba_model(am).g
# Prior methods
"""
`draw_model!(pm::ParametricModel)`
Draw a parameter from the prior disitribution defined in `pm::ParametricModel`
and save it in the model contained in `pm`.
"""
draw_model!(pm::ParametricModel) = set_param!(get_proba_model(pm), pm.params, rand(pm.distribution))
"""
`draw!(vec_p, pm)`
Draw a parameter from the prior distribution defined in pm and stores it in vec_p.
"""
draw!(vec_p::AbstractVector{Float64}, pm::ParametricModel) = rand!(pm.distribution, vec_p)
"""
`draw!(mat_p, pm)`
Draw `size(mat_p)[2]` (number of columns of mat_p) parameters from the prior distribution
defined in pm and stores it in mat_p.
"""
function draw!(mat_p::AbstractMatrix{Float64}, pm::ParametricModel)
for i = 1:size(mat_p)[2]
draw!(view(mat_p, :, i), pm)
end
end
"""
`prior_pdf(p_prior, pm)`
Computes the density of the prior distribution defined in pm on argument p_prior.
"""
prior_pdf(pm::ParametricModel, p_prior::AbstractVector{Float64}) = pdf(pm.distribution, p_prior)
"""
`prior_pdf(vec_res, mat_p, pm)`
Computes the density of the prior distribution defined in pm on each column
ov mat_p. Stores it in mat_p. (`length(vec_res) == size(mat_p)[2]`)
"""
function prior_pdf!(res_pdf::AbstractVector{Float64}, pm::ParametricModel, mat_p::AbstractMatrix{Float64})
for i = eachindex(res_pdf)
res_pdf[i] = prior_pdf(pm, view(mat_p, :, i))
end
end
fill!(pm::ParametricModel, p_prior::AbstractVector{Float64}) = get_proba_model(pm).p[pm._param_idx] = p_prior
insupport(pm::ParametricModel, p_prior::AbstractVector{Float64}) = insupport(pm.distribution, p_prior)
# to do: simulate(pm), create_res_dir, check_bounds, ajouter un champ _param_idx pour pm.
#