diff --git a/src/ODE.jl b/src/ODE.jl index 0c7644649..8e512d0ee 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -365,6 +365,8 @@ Here, macro ODEmodel(ex::Expr...) equations = [ex...] x_vars, y_vars, u_vars, all_symb = macrohelper_extract_vars(equations) + # ensures that the parameters will be ordered + all_symb = sort(all_symb) # creating the polynomial ring vars_list = :([$(all_symb...)]) diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 40fad56d4..165570f3a 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -93,7 +93,7 @@ Input: Assesses identifiability of a given ODE model. The result is guaranteed to be correct with the probability at least `p`. -The function returns a dictionary from the functions to check to their identifiability properties +The function returns an (ordered) dictionary from the functions to check to their identifiability properties (one of `:nonidentifiable`, `:locally`, `:globally`). """ function assess_identifiability( @@ -118,7 +118,7 @@ function _assess_identifiability( p_loc = 1 - (1 - p) * 0.1 if isempty(funcs_to_check) - funcs_to_check = vcat(ode.parameters, ode.x_vars) + funcs_to_check = vcat(ode.x_vars, ode.parameters) end @info "Assessing local identifiability" @@ -148,7 +148,7 @@ function _assess_identifiability( @info "Global identifiability assessed in $runtime seconds" _runtime_logger[:glob_time] = runtime - result = Dict{Any, Symbol}() + result = OrderedDict{Any, Symbol}() glob_ind = 1 for i in 1:length(funcs_to_check) if !local_result[funcs_to_check[i]] @@ -210,13 +210,13 @@ function _assess_identifiability( ode, conversion = mtk_to_si(ode, measured_quantities) conversion_back = Dict(v => k for (k, v) in conversion) if isempty(funcs_to_check) - funcs_to_check = [conversion_back[x] for x in [ode.parameters..., ode.x_vars...]] + funcs_to_check = [conversion_back[x] for x in [ode.x_vars..., ode.parameters...]] end funcs_to_check_ = [eval_at_nemo(each, conversion) for each in funcs_to_check] result = _assess_identifiability(ode, funcs_to_check = funcs_to_check_, p = p) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = Dict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return out_dict end diff --git a/src/discrete.jl b/src/discrete.jl index 99f46f75f..3e84cafc9 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -267,7 +267,7 @@ function _assess_local_identifiability_discrete( @debug "Computing the result" base_rank = LinearAlgebra.rank(Jac) - result = Dict{Any, Bool}() + result = OrderedDict{Any, Bool}() for i in 1:length(funcs_to_check) for (k, p) in enumerate(dds_ext.parameters) Jac[k, 1] = @@ -280,7 +280,7 @@ function _assess_local_identifiability_discrete( result[funcs_to_check[i]] = LinearAlgebra.rank(Jac) == base_rank end - return Dict(result) + return result end # ------------------------------------------------------------------------------ @@ -301,7 +301,7 @@ Input: - `p` - probability of correctness Output: -- the result is a dictionary from each function to to boolean; +- the result is an (ordered) dictionary from each function to to boolean; The result is correct with probability at least `p`. """ @@ -331,8 +331,8 @@ function assess_local_identifiability( dds_aux, conversion = mtk_to_si(dds, measured_quantities) if length(funcs_to_check) == 0 funcs_to_check = vcat( - parameters(dds), [x for x in states(dds) if conversion[x] in dds_aux.x_vars], + parameters(dds), ) end funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] @@ -340,7 +340,7 @@ function assess_local_identifiability( result = _assess_local_identifiability_discrete(dds_aux, funcs_to_check_, known_ic_, p) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = Dict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) if length(known_ic) > 0 @warn "Since known initial conditions were provided, identifiability of states (e.g., `x(t)`) is at t = 0 only !" end diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index 45454ff88..3d680c336 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -154,7 +154,7 @@ Input: - `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) Output: -- for `type=:SE`, the result is a dictionary from each parameter to boolean; +- for `type=:SE`, the result is an (ordered) dictionary from each parameter to boolean; - for `type=:ME`, the result is a tuple with the dictionary as in `:SE` case and array of number of experiments. The function determines local identifiability of parameters in `funcs_to_check` or all possible parameters if `funcs_to_check` is empty @@ -208,8 +208,8 @@ end end if length(funcs_to_check) == 0 funcs_to_check = vcat( - ModelingToolkit.parameters(ode), [e for e in ModelingToolkit.states(ode) if !ModelingToolkit.isoutput(e)], + ModelingToolkit.parameters(ode), ) end ode, conversion = mtk_to_si(ode, measured_quantities) @@ -223,7 +223,8 @@ end type = type, ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = Dict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + out_dict = + OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return out_dict elseif isequal(type, :ME) result, bd = _assess_local_identifiability( @@ -233,7 +234,8 @@ end type = type, ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = Dict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + out_dict = + OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return (out_dict, bd) end end @@ -280,7 +282,7 @@ function _assess_local_identifiability( if isempty(funcs_to_check) funcs_to_check = ode.parameters if type == :SE - funcs_to_check = vcat(funcs_to_check, ode.x_vars) + funcs_to_check = vcat(ode.x_vars, ode.parameters) end end @@ -440,7 +442,7 @@ function _assess_local_identifiability( @debug "Computing the result" base_rank = LinearAlgebra.rank(Jac) - result = Dict{Any, Bool}() + result = OrderedDict{Any, Bool}() for i in 1:length(funcs_to_check) for (k, p) in enumerate(ode_red.parameters) Jac[k, 1] = @@ -459,9 +461,9 @@ function _assess_local_identifiability( # NB: the Jac contains now the derivatives of the last from `funcs_to_check` if type == :SE - return Dict(result) + return result end - return (Dict(result), num_exp) + return (result, num_exp) end # ------------------------------------------------------------------------------ diff --git a/test/identifiability.jl b/test/identifiability.jl index 361cb5cd5..cec848d14 100644 --- a/test/identifiability.jl +++ b/test/identifiability.jl @@ -23,7 +23,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -38,13 +38,13 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) # Also test when `funcs_to_test` is empty! funcs_to_test = Vector{typeof(x1)}() - correct = Dict(x1 => :nonidentifiable, x2 => :nonidentifiable) + correct = OrderedDict(x1 => :nonidentifiable, x2 => :nonidentifiable) push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) #-------------------------------------------------------------------------- @@ -69,7 +69,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -89,7 +89,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -118,7 +118,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -137,7 +137,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -155,7 +155,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -173,7 +173,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -192,7 +192,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -222,7 +222,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) diff --git a/test/local_identifiability.jl b/test/local_identifiability.jl index 976583097..0601baead 100644 --- a/test/local_identifiability.jl +++ b/test/local_identifiability.jl @@ -24,7 +24,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -42,7 +42,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -62,7 +62,7 @@ Dict( :ode => ode, :funcs => funcs_to_test, - :correct => Dict(funcs_to_test .=> correct), + :correct => OrderedDict(funcs_to_test .=> correct), ), ) @@ -76,7 +76,7 @@ y(t) = x1(t) ) funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] - correct = Dict([ + correct = OrderedDict([ b => true, c => true, alpha => false, @@ -96,7 +96,7 @@ y(t) = x1(t) * x2(t) ) funcs_to_test = [a1, a2, a3, a4, a5, a6, a7, a8] - correct = Dict(a => false for a in funcs_to_test) + correct = OrderedDict(a => false for a in funcs_to_test) push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) #-------------------------------------------------------------------------- @@ -109,7 +109,7 @@ y2(t) = x3(t) ) funcs_to_test = [x1, x2, x1 + x2] - correct = Dict(x1 => false, x2 => false, x1 + x2 => true) + correct = OrderedDict(x1 => false, x2 => false, x1 + x2 => true) push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) #-------------------------------------------------------------------------- diff --git a/test/local_identifiability_discrete.jl b/test/local_identifiability_discrete.jl index e51823795..ae08eca4f 100644 --- a/test/local_identifiability_discrete.jl +++ b/test/local_identifiability_discrete.jl @@ -11,7 +11,7 @@ cases, Dict( :dds => sir, - :res => Dict(S => true, I => true, R => false, α => true, β => true), + :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), :y => [y ~ I], :y2 => [I], :known_ic => Array{}[], @@ -30,7 +30,7 @@ cases, Dict( :dds => eqs, - :res => Dict(x => true, θ => true), + :res => OrderedDict(x => true, θ => true), :y => [y ~ x], :y2 => [x], :known_ic => Array{}[], @@ -49,7 +49,7 @@ cases, Dict( :dds => eqs, - :res => Dict(x1 => true, x2 => true, θ => false, β => false), + :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), :y => [y ~ x1], :y2 => [x1], :known_ic => Array{}[], @@ -68,8 +68,14 @@ cases, Dict( :dds => lv, - :res => - Dict(a => true, b => false, c => true, d => true, x1 => true, x2 => false), + :res => OrderedDict( + x1 => true, + x2 => false, + a => true, + b => false, + c => true, + d => true, + ), :y => [y ~ x1], :y2 => [x1], :known_ic => Array{}[], @@ -81,7 +87,7 @@ cases, Dict( :dds => lv, - :res => Dict(b * x2 => true), + :res => OrderedDict(b * x2 => true), :y => [y ~ x1], :y2 => [x1], :known_ic => Array{}[], @@ -93,8 +99,14 @@ cases, Dict( :dds => lv, - :res => - Dict(a => true, b => true, c => true, d => true, x1 => true, x2 => true), + :res => OrderedDict( + x1 => true, + x2 => true, + a => true, + b => true, + c => true, + d => true, + ), :y => [y ~ x1, y2 ~ x1 / x2], :y2 => [x1, x1 / x2], :known_ic => Array{}[], @@ -106,8 +118,14 @@ cases, Dict( :dds => lv, - :res => - Dict(a => true, b => true, c => true, d => true, x1 => true, x2 => true), + :res => OrderedDict( + x1 => true, + x2 => true, + a => true, + b => true, + c => true, + d => true, + ), :y => [y ~ x1], :y2 => [x1], :known_ic => [x2], @@ -127,7 +145,7 @@ cases, Dict( :dds => abmd1, - :res => Dict(x1 => true, x2 => true, theta1 => true, theta2 => true), + :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), :y => [y ~ x1], :y2 => [x1], :known_ic => Array{}[], @@ -147,12 +165,12 @@ cases, Dict( :dds => abmd2, - :res => Dict( + :res => OrderedDict( + x1 => true, + x2 => false, theta1 => true, theta2 => false, theta3 => false, - x1 => true, - x2 => false, ), :y => [y ~ x1], :y2 => [x1], @@ -165,11 +183,11 @@ Dict( :dds => abmd2, :res => Dict( + x1 => true, + x2 => true, theta1 => true, theta2 => true, theta3 => true, - x1 => true, - x2 => true, ), :y => [y ~ x1, y2 ~ x2], :y2 => [x1, x2], diff --git a/test/local_identifiability_discrete_aux.jl b/test/local_identifiability_discrete_aux.jl index ccc8d7a68..c0dab76a3 100644 --- a/test/local_identifiability_discrete_aux.jl +++ b/test/local_identifiability_discrete_aux.jl @@ -7,7 +7,7 @@ cases, Dict( :dds => dds, - :res => Dict(a => true, b => false, c => false, b + c => true), + :res => OrderedDict(a => true, b => false, c => false, b + c => true), :known => :none, ), ) @@ -16,7 +16,7 @@ cases, Dict( :dds => dds, - :res => Dict(a => true, b => false, c => false, b + c => true), + :res => OrderedDict(a => true, b => false, c => false, b + c => true), :known => :all, ), ) @@ -29,7 +29,7 @@ cases, Dict( :dds => dds, - :res => Dict( + :res => OrderedDict( b => true, a => false, c => false, @@ -45,7 +45,7 @@ cases, Dict( :dds => dds, - :res => Dict(b => true, a => true, c => true, d => true), + :res => OrderedDict(b => true, a => true, c => true, d => true), :known => [a], ), ) @@ -54,7 +54,7 @@ cases, Dict( :dds => dds, - :res => Dict(b => true, a => true, c => true, d => true), + :res => OrderedDict(b => true, a => true, c => true, d => true), :known => :all, ), ) @@ -64,7 +64,10 @@ # Example 4 from https://doi.org/10.1016/j.automatica.2016.01.054 dds = @ODEmodel(x'(t) = theta^3 * x(t), y(t) = x(t)) - push!(cases, Dict(:dds => dds, :res => Dict(theta => true, x => true), :known => :none)) + push!( + cases, + OrderedDict(:dds => dds, :res => Dict(theta => true, x => true), :known => :none), + ) # ------------------- diff --git a/test/local_identifiability_me.jl b/test/local_identifiability_me.jl index 6c6541473..b64c0bb3a 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -164,7 +164,7 @@ end Dict( :ode => ode, :funcs => funcs_to_test, - :correct => (Dict(funcs_to_test .=> correct), 1), + :correct => (OrderedDict(funcs_to_test .=> correct), 1), ), ) @@ -179,7 +179,7 @@ end Dict( :ode => ode, :funcs => funcs_to_test, - :correct => (Dict(funcs_to_test .=> correct), 2), + :correct => (OrderedDict(funcs_to_test .=> correct), 2), ), ) @@ -196,7 +196,7 @@ end y3(t) = N ) funcs_to_test = [b, nu, d, a] - correct = Dict([b => true, nu => true, d => true, a => true]) + correct = OrderedDict([b => true, nu => true, d => true, a => true]) push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1))) #-------------------------------------------------------------------------- @@ -204,7 +204,7 @@ end # example with 0 replicas required ode = @ODEmodel(x'(t) = a * z(t), z'(t) = a * z(t)^2, y(t) = x(t)) funcs_to_test = [a] - correct = Dict([a => false]) + correct = OrderedDict([a => false]) push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0))) #-------------------------------------------------------------------------- diff --git a/test/mtk_compat.jl b/test/mtk_compat.jl index 37d5492d3..0927500b9 100644 --- a/test/mtk_compat.jl +++ b/test/mtk_compat.jl @@ -6,7 +6,7 @@ eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] de = ODESystem(eqs, t, name = :Test) - correct = Dict( + correct = OrderedDict( a01 => :nonidentifiable, a21 => :nonidentifiable, a12 => :nonidentifiable, @@ -52,7 +52,7 @@ eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] de = ODESystem(eqs, t, name = :Test) - correct = Dict( + correct = OrderedDict( a01 => :nonidentifiable, a21 => :nonidentifiable, a12 => :nonidentifiable, @@ -71,12 +71,12 @@ eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] de = ODESystem(eqs, t, name = :Test) funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = Dict( + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, a12 => :nonidentifiable, - a01 + a12 + a21 => :globally, a01 * a12 => :globally, - a21 => :nonidentifiable, - a01 => :nonidentifiable, + a01 + a12 + a21 => :globally, ) @test isequal(correct, assess_identifiability(de; funcs_to_check = funcs_to_check)) @@ -90,12 +90,12 @@ measured_quantities = [y1 ~ x0] de = ODESystem(eqs, t, name = :Test) funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = Dict( + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, a12 => :nonidentifiable, - a01 + a12 + a21 => :globally, a01 * a12 => :globally, - a21 => :nonidentifiable, - a01 => :nonidentifiable, + a01 + a12 + a21 => :globally, ) @test isequal( correct, @@ -130,7 +130,7 @@ # check specific parameters funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( correct, assess_local_identifiability( @@ -142,7 +142,7 @@ # checking ME identifiability funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( (correct, 1), assess_local_identifiability( @@ -181,7 +181,7 @@ # check specific parameters funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( correct, assess_local_identifiability(de; funcs_to_check = funcs_to_check), @@ -189,7 +189,7 @@ # checking ME identifiability funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( (correct, 1), assess_local_identifiability( @@ -213,7 +213,7 @@ de = ODESystem(eqs, t, name = :TestSIWR) measured_quantities = [y ~ 1.57 * I * k] funcs_to_check = [mu, bi, bw, a, xi, gm, mu, gm + mu, k, S, I, W, R] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( correct, assess_local_identifiability( @@ -225,7 +225,7 @@ # checking ME identifiability funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] - correct = Dict(f => true for f in funcs_to_check) + correct = OrderedDict(f => true for f in funcs_to_check) @test isequal( (correct, 1), assess_local_identifiability( @@ -297,7 +297,7 @@ [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1, D(c) ~ 0] de = ODESystem(eqs, t, name = :Test) measured_quantities = [y1 ~ x2, y2 ~ c] - correct = Dict(a => :globally, b => :globally) + correct = OrderedDict(a => :globally, b => :globally) to_check = [a, b] @test isequal( correct, @@ -368,7 +368,7 @@ rabbits₊x => :nonidentifiable, wolves₊y => :globally, ) - @test result == correct + @test Dict(result) == correct #---------------------------------- @@ -392,6 +392,6 @@ eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] sys = ODESystem(eqs, t, name = :example_vector) - correct = Dict(k1 => true, k2 => true, x[1] => true, x[2] => true) + correct = OrderedDict(x[1] => true, x[2] => true, k1 => true, k2 => true) @test assess_local_identifiability(sys, measured_quantities = [x[1], x[2]]) == correct end diff --git a/test/runtests.jl b/test/runtests.jl index f85781783..db7c27e78 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,7 @@ using StructuralIdentifiability using Test using TestSetExtensions +using StructuralIdentifiability.DataStructures using StructuralIdentifiability.Nemo using StructuralIdentifiability.ModelingToolkit using StructuralIdentifiability: