From 5b7fc78fc7daa9739e326196bd7622bcd35a0283 Mon Sep 17 00:00:00 2001 From: Mahmoud Bentriou <mahmoud.bentriou@centralesupelec.fr> Date: Mon, 23 Nov 2020 22:27:35 +0100 Subject: [PATCH] Change of Trajectory.values type from Matrix to Vector of Vector in order to replace vcat by push!. The benchmarks are at least equal, and better when the buffer size is small. However, no significative difference of performance with adequate buffer size. My opinion the gain can be seen when simulations are going to be very long with more complicated models than we have implemented for now. All tests passed. --- bench/pygmalion/multiple_long_sim_er.jl | 4 +- core/MarkovProcesses.jl | 2 +- core/_tests_simulate.jl | 82 +++++++++++++++++++--- core/common.jl | 4 +- core/lha.jl | 9 ++- core/trajectory.jl | 24 ++++--- tests/dist_lp/dist_case_2.jl | 12 ++-- tests/dist_lp/dist_case_3.jl | 12 ++-- tests/dist_lp/dist_l1_case_1.jl | 12 ++-- tests/unit/check_trajectory_consistency.jl | 20 +++++- 10 files changed, 135 insertions(+), 46 deletions(-) diff --git a/bench/pygmalion/multiple_long_sim_er.jl b/bench/pygmalion/multiple_long_sim_er.jl index 6729aba..23244dc 100644 --- a/bench/pygmalion/multiple_long_sim_er.jl +++ b/bench/pygmalion/multiple_long_sim_er.jl @@ -38,8 +38,8 @@ b2 = @benchmark for i=1:$(nb_sim) MarkovProcesses.simulate(ER) end @show minimum(b2), mean(b2), maximum(b2) println("Buffer size 100") ER.buffer_size = 100 -b1 = @benchmark for i=1:$(nb_sim) MarkovProcesses.simulate($ER) end -b2 = @benchmark for i=1:$(nb_sim) MarkovProcesses.simulate(ER) end +b1_100 = @benchmark for i=1:$(nb_sim) MarkovProcesses.simulate($ER) end +b2_100 = @benchmark for i=1:$(nb_sim) MarkovProcesses.simulate(ER) end @show minimum(b1), mean(b1), maximum(b1) @show minimum(b2), mean(b2), maximum(b2) diff --git a/core/MarkovProcesses.jl b/core/MarkovProcesses.jl index 5fb0ec4..85eb0f6 100644 --- a/core/MarkovProcesses.jl +++ b/core/MarkovProcesses.jl @@ -22,7 +22,7 @@ export load_automaton, get_index, get_value, length_var, isaccepted # Model related methods export simulate, set_param!, get_param, set_observed_var! -export set_time_bound! +export set_time_bound!, getproperty export isbounded, isaccepted, check_consistency export load_model, get_module_path diff --git a/core/_tests_simulate.jl b/core/_tests_simulate.jl index 632b3ff..9c40acb 100644 --- a/core/_tests_simulate.jl +++ b/core/_tests_simulate.jl @@ -1,21 +1,28 @@ +struct OldTrajectory <: AbstractTrajectory + m::ContinuousTimeModel + values::Matrix{Int} + times::Vector{Float64} + transitions::Vector{Union{String,Nothing}} +end + # File for benchmarking simulation and memory access of the package. # Trajectories -_get_values_col(σ::AbstractTrajectory, var::String) = +_get_values_col(σ::OldTrajectory, var::String) = @view σ.values[(σ.m)._map_obs_var_idx[var],:] -_get_values_row(σ::AbstractTrajectory, var::String) = +_get_values_row(σ::OldTrajectory, var::String) = @view σ.values[:,(σ.m)._map_obs_var_idx[var]] -_get_state_col(σ::AbstractTrajectory, idx::Int) = +_get_state_col(σ::OldTrajectory, idx::Int) = @view σ.values[:,idx] -_get_state_row(σ::AbstractTrajectory, idx::Int) = +_get_state_row(σ::OldTrajectory, idx::Int) = @view σ.values[idx,:] -_get_value_col(σ::AbstractTrajectory, var::String, idx::Int) = +_get_value_col(σ::OldTrajectory, var::String, idx::Int) = σ.values[(σ.m)._map_obs_var_idx[var],idx] -_get_value_row(σ::AbstractTrajectory, var::String, idx::Int) = +_get_value_row(σ::OldTrajectory, var::String, idx::Int) = σ.values[idx,(σ.m)._map_obs_var_idx[var]] # Model @@ -51,7 +58,7 @@ function _simulate_col(m::ContinuousTimeModel) transitions[end] = nothing end end - return Trajectory(m, values, times, transitions) + return OldTrajectory(m, values, times, transitions) end function _simulate_row(m::ContinuousTimeModel) @@ -85,7 +92,7 @@ function _simulate_row(m::ContinuousTimeModel) transitions[end] = nothing end end - return Trajectory(m, values, times, transitions) + return OldTrajectory(m, values, times, transitions) end @@ -126,7 +133,7 @@ function _simulate_col_buffer(m::ContinuousTimeModel; buffer_size::Int = 5) transitions[end] = nothing end end - return Trajectory(m, values, times, transitions) + return OldTrajectory(m, values, times, transitions) end function _simulate_row_buffer(m::ContinuousTimeModel; buffer_size::Int = 5) @@ -166,7 +173,7 @@ function _simulate_row_buffer(m::ContinuousTimeModel; buffer_size::Int = 5) transitions[end] = nothing end end - return Trajectory(m, values, times, transitions) + return OldTrajectory(m, values, times, transitions) end function _simulate_without_view(m::ContinuousTimeModel) @@ -211,6 +218,59 @@ function _simulate_without_view(m::ContinuousTimeModel) end end values = full_values[:,m._g_idx] - return Trajectory(m, values, times, transitions) + return OldTrajectory(m, values, times, transitions) end +# With trajectory values in Matrix type +function _simulate_27d56(m::ContinuousTimeModel) + # trajectory fields + full_values = Matrix{Int}(undef, 1, m.d) + full_values[1,:] = m.x0 + times = Float64[m.t0] + transitions = Union{String,Nothing}[nothing] + # values at time n + n = 0 + xn = view(reshape(m.x0, 1, m.d), 1, :) # View for type stability + tn = m.t0 + # at time n+1 + mat_x = zeros(Int, m.buffer_size, m.d) + l_t = zeros(Float64, m.buffer_size) + l_tr = Vector{String}(undef, m.buffer_size) + isabsorbing::Bool = m.isabsorbing(m.p,xn) + # use sizehint! ? + while !isabsorbing && tn <= m.time_bound + i = 0 + while i < m.buffer_size && !isabsorbing && tn <= m.time_bound + i += 1 + m.f!(mat_x, l_t, l_tr, i, xn, tn, m.p) + tn = l_t[i] + if tn > m.time_bound + i -= 1 # 0 is an ok value, 1:0 is allowed + break + end + xn = view(mat_x, i, :) + isabsorbing = m.isabsorbing(m.p,xn) + end + full_values = vcat(full_values, view(mat_x, 1:i, :)) + append!(times, view(l_t, 1:i)) + append!(transitions, view(l_tr, 1:i)) + n += i + end + if isbounded(m) + #= + if times[end] > m.time_bound + full_values[end,:] = full_values[end-1,:] + times[end] = m.time_bound + transitions[end] = nothing + else + end + =# + full_values = vcat(full_values, reshape(full_values[end,:], 1, m.d)) + push!(times, m.time_bound) + push!(transitions, nothing) + end + values = view(full_values, :, m._g_idx) + return OldTrajectory(m, values, times, transitions) +end + + diff --git a/core/common.jl b/core/common.jl index bbe0914..8f7b015 100644 --- a/core/common.jl +++ b/core/common.jl @@ -26,7 +26,7 @@ end struct Trajectory <: AbstractTrajectory m::ContinuousTimeModel - values::Matrix{Int} + values::Vector{Vector{Int}} times::Vector{Float64} transitions::Vector{Transition} end @@ -65,7 +65,7 @@ end struct SynchronizedTrajectory <: AbstractTrajectory S::StateLHA m::SynchronizedModel - values::Matrix{Int} + values::Vector{Vector{Int}} times::Vector{Float64} transitions::Vector{Transition} end diff --git a/core/lha.jl b/core/lha.jl index c47b7b6..2445b23 100644 --- a/core/lha.jl +++ b/core/lha.jl @@ -24,7 +24,7 @@ end isaccepted(S::StateLHA) = (S.loc in (S.A).l_loc_final) # Methods for synchronize / read the trajectory -function init_state(A::LHA, x0::SubArray{Int,1}, t0::Float64) +function init_state(A::LHA, x0::Vector{Int}, t0::Float64) S0 = StateLHA(A, "", zeros(length_var(A)), t0) for loc in A.l_loc_init if A.Λ[loc](A,S0) @@ -119,12 +119,17 @@ function read_trajectory(A::LHA, σ::Trajectory; verbose = false) A_new = LHA(A, (σ.m)._map_obs_var_idx) l_t = times(σ) l_tr = transitions(σ) + mat_x = zeros(Int, length_states(σ), σ.m.d) + for (i,var) in enumerate(σ.m.g) + mat_x[:,i] = σ[var] + end Sn = init_state(A_new, σ[1], l_t[1]) Snplus1 = copy(Sn) if verbose println("Init: ") end if verbose @show Sn end for n in 1:length_states(σ) - next_state!(Snplus1, A_new, σ[n], l_t[n], l_tr[n], Sn; verbose = verbose) + xn = view(mat_x, n, :) + next_state!(Snplus1, A_new, xn, l_t[n], l_tr[n], Sn; verbose = verbose) if Snplus1.loc in A_new.l_loc_final break end diff --git a/core/trajectory.jl b/core/trajectory.jl index cb8901b..a02735c 100644 --- a/core/trajectory.jl +++ b/core/trajectory.jl @@ -64,7 +64,7 @@ function dist_lp(σ1::AbstractTrajectory, σ2::AbstractTrajectory, var::String; end # Distance function. Vectorized version -function dist_lp(x_obs::SubArray{Int,1}, t_x::Vector{Float64}, y_obs::SubArray{Int,1}, t_y::Vector{Float64}; +function dist_lp(x_obs::Vector{Int}, t_x::Vector{Float64}, y_obs::Vector{Int}, t_y::Vector{Float64}; verbose::Bool = false, p::Int = 1) current_y_obs = y_obs[1] current_t_y = t_y[2] @@ -150,23 +150,31 @@ function _riemann_sum(f::Function, t_begin::Real, t_end::Real, step::Float64) end function check_consistency(σ::AbstractTrajectory) - @assert length(σ.times) == length(σ.transitions) == size(σ.values)[1] - @assert length_obs_var(σ) == length(σ.m.g) == size(σ.values)[2] + test_length_var = true + for i = 1:(σ.m).dobs + test_length_i = (length(σ.values[1]) == length(σ.values[i])) + test_length_var = test_length_var && test_length_i + end + @assert begin (length(σ.times) == length(σ.transitions)) && + (length(σ.times) == length(σ.values[1])) && + test_length_var + end + @assert length_obs_var(σ) == σ.m.dobs return true end -issteadystate(σ::AbstractTrajectory) = (σ.m).isabsorbing((σ.m).p, σ[end]) +issteadystate(σ::AbstractTrajectory) = (σ.m).isabsorbing((σ.m).p, view(reshape(σ[end], 1, m.d), 1, :)) # Properties of the trajectory length_states(σ::AbstractTrajectory) = length(σ.times) -length_obs_var(σ::AbstractTrajectory) = size(σ.values)[2] +length_obs_var(σ::AbstractTrajectory) = length(σ.values) get_obs_var(σ::AbstractTrajectory) = (σ.m).g isbounded(σ::AbstractTrajectory) = σ.transitions[end] == nothing isaccepted(σ::SynchronizedTrajectory) = isaccepted(σ.S) # Access to trajectory values -get_var_values(σ::AbstractTrajectory, var::String) = view(σ.values, :, (σ.m)._map_obs_var_idx[var]) -get_state(σ::AbstractTrajectory, idx::Int) = view(σ.values, idx, :) -get_value(σ::AbstractTrajectory, var::String, idx::Int) = σ.values[idx,(σ.m)._map_obs_var_idx[var]] +get_var_values(σ::AbstractTrajectory, var::String) = σ.values[(σ.m)._map_obs_var_idx[var]] +get_state(σ::AbstractTrajectory, idx::Int) = [σ.values[i][idx] for i = 1:length(σ.values)] # /!\ Creates an array +get_value(σ::AbstractTrajectory, var::String, idx::Int) = get_var_values(σ, var)[idx] # Operation @ function get_state_from_time(σ::AbstractTrajectory, t::Float64) @assert t >= 0.0 diff --git a/tests/dist_lp/dist_case_2.jl b/tests/dist_lp/dist_case_2.jl index be7c86d..c66ce3b 100644 --- a/tests/dist_lp/dist_case_2.jl +++ b/tests/dist_lp/dist_case_2.jl @@ -11,21 +11,21 @@ for p = 1:2 y_obs = [1, 2, 5, 3, 3] t_y = [0.0, 0.5, 0.7, 1.4, 3.0] - values = zeros(length(y_obs), 1) - values[:,1] = y_obs + values = [zeros(length(y_obs))] + values[1] = y_obs l_tr = Vector{Nothing}(nothing, length(y_obs)) σ2 = Trajectory(SIR, values, t_y, l_tr) x_obs = [4, 2, 3, 1, 1] t_x = [0.0, 0.6, 0.7, 0.8, 3.0] - values = zeros(length(x_obs), 1) - values[:,1] = x_obs + values = [zeros(length(x_obs))] + values[1] = x_obs l_tr = Vector{Nothing}(nothing, length(x_obs)) σ1 = Trajectory(SIR, values, t_x, l_tr) - test_1 = isapprox(dist_lp(@view(x_obs[:]), t_x, @view(y_obs[:]), t_y; p=p), res; atol = 1E-10) + test_1 = isapprox(dist_lp(x_obs, t_x, y_obs, t_y; p=p), res; atol = 1E-10) test_1 = test_1 && isapprox(dist_lp(σ1,σ2; p=p), res; atol = 1E-10) - test_1_bis = isapprox(dist_lp(@view(y_obs[:]), t_y, @view(x_obs[:]), t_x; p=p), res; atol = 1E-10) + test_1_bis = isapprox(dist_lp(y_obs, t_y, x_obs, t_x; p=p), res; atol = 1E-10) test_1_bis = test_1_bis && isapprox(dist_lp(σ2,σ1; p=p), res; atol = 1E-10) f_x(t::Real) = MarkovProcesses._f_step(x_obs, t_x, t) diff --git a/tests/dist_lp/dist_case_3.jl b/tests/dist_lp/dist_case_3.jl index b271771..8254be0 100644 --- a/tests/dist_lp/dist_case_3.jl +++ b/tests/dist_lp/dist_case_3.jl @@ -7,15 +7,15 @@ test_all = true for p = 1:2 x_obs =[5, 6, 5, 4, 3, 2, 1, 1] t_x = [0.0, 3.10807, 4.29827, 4.40704, 5.67024, 7.1299, 11.2763, 20.0] - values = zeros(length(x_obs), 1) - values[:,1] = x_obs + values = [zeros(length(x_obs))] + values[1] = x_obs l_tr = Vector{Nothing}(nothing, length(x_obs)) σ1 = Trajectory(SIR, values, t_x, l_tr) y_obs =[5, 4, 5, 4, 3, 4, 3, 2, 3, 2, 1, 2, 3, 4, 3, 4, 4] t_y = [0.0, 0.334082, 1.21012, 1.40991, 1.58866, 2.45879, 2.94545, 4.66746, 5.44723, 5.88066, 7.25626, 11.4036, 13.8373, 17.1363, 17.8193, 18.7613, 20.0] - values = zeros(length(y_obs), 1) - values[:,1] = y_obs + values = [zeros(length(y_obs))] + values[1] = y_obs l_tr = Vector{Nothing}(nothing, length(y_obs)) σ2 = Trajectory(SIR, values, t_y, l_tr) @@ -26,9 +26,9 @@ for p = 1:2 int_riemann = MarkovProcesses._riemann_sum(diff_f, 0.0, 20.0, 1E-5) int_riemann = int_riemann^(1/p) - res1 = dist_lp(@view(x_obs[:]), t_x, @view(y_obs[:]), t_y; p=p) + res1 = dist_lp(x_obs, t_x, y_obs, t_y; p=p) res2 = dist_lp(σ1,σ2; p=p) - res1_bis = dist_lp(@view(y_obs[:]), t_y, @view(x_obs[:]), t_x; p=p) + res1_bis = dist_lp(y_obs, t_y, x_obs, t_x; p=p) res2_bis = dist_lp(σ2,σ1; p=p) test_1 = isapprox(res1, int_riemann; atol = 1E-3) test_1 = test_1 && isapprox(res2, int_riemann; atol = 1E-3) diff --git a/tests/dist_lp/dist_l1_case_1.jl b/tests/dist_lp/dist_l1_case_1.jl index a9152e0..262c8c7 100644 --- a/tests/dist_lp/dist_l1_case_1.jl +++ b/tests/dist_lp/dist_l1_case_1.jl @@ -8,20 +8,20 @@ load_model("SIR") x_obs = [2, 4, 3, 3] t_x = [0.0, 0.5, 1.2, 2.2] t_y_bis = [0.0, 0.5, 1.2, 2.2] -values = zeros(length(x_obs), 1) -values[:,1] = x_obs +values = [zeros(length(x_obs))] +values[1] = x_obs l_tr = Vector{Nothing}(nothing, length(x_obs)) σ1 = Trajectory(SIR, values, t_x, l_tr) y_obs = [6, 6] t_y = [0.0, 2.2] t_x_bis = [0.0, 2.2] -values = zeros(length(y_obs), 1) -values[:,1] = y_obs +values = [zeros(length(y_obs))] +values[1] = y_obs l_tr = Vector{Nothing}(nothing, length(y_obs)) σ2 = Trajectory(SIR, values, t_y, l_tr) -test_1 = dist_lp(@view(x_obs[:]), t_x, @view(y_obs[:]), t_y; p=1) == 6.4 +test_1 = dist_lp(x_obs, t_x, y_obs, t_y; p=1) == 6.4 test_1 = test_1 && dist_lp(σ1, σ2; p=1) == 6.4 f_x(t::Real) = MarkovProcesses._f_step(x_obs, t_x, t) @@ -33,7 +33,7 @@ test_2 = isapprox(6.4, int; atol=err) # Case 1 bis : inverse of case 1 -test_1_bis = dist_lp(@view(y_obs[:]), t_y, @view(x_obs[:]), t_x; p=1) == 6.4 +test_1_bis = dist_lp(y_obs, t_y, x_obs, t_x; p=1) == 6.4 test_1_bis = test_1_bis && dist_lp(σ2, σ1; p=1) == 6.4 return test_1 && test_1_bis && test_2 diff --git a/tests/unit/check_trajectory_consistency.jl b/tests/unit/check_trajectory_consistency.jl index 4ae5da9..96ff4b4 100644 --- a/tests/unit/check_trajectory_consistency.jl +++ b/tests/unit/check_trajectory_consistency.jl @@ -1,13 +1,29 @@ using MarkovProcesses + load_model("SIR") -SIR.time_bound = 100.0 load_model("ER") -ER.time_bound = 10.0 test_all = true nb_sim = 1000 +for i = 1:nb_sim + σ = simulate(SIR) + σ2 = simulate(ER) + try + global test_all = test_all && MarkovProcesses.check_consistency(σ) && MarkovProcesses.check_consistency(σ2) + catch err + @show err + @show length(σ.values), length(σ.times), length(σ.transitions) + println() + @show σ.values + @show σ.times + @show σ.transitions + throw(err) + end +end +SIR.time_bound = 100.0 +ER.time_bound = 10.0 for i = 1:nb_sim σ = simulate(SIR) σ2 = simulate(ER) -- GitLab