diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 997b5426..ed747b4c 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ jobs: matrix: group: - Core - - ModelingToolkitExt + - ModelingToolkitSIExt version: - '1' - '1.6' diff --git a/Project.toml b/Project.toml index 9c061245..005a89cc 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [extensions] -ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] +ModelingToolkitSIExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] [compat] AbstractAlgebra = "0.34.5, 0.35" diff --git a/docs/src/tutorials/creating_ode.md b/docs/src/tutorials/creating_ode.md index 915a742a..a09b069c 100644 --- a/docs/src/tutorials/creating_ode.md +++ b/docs/src/tutorials/creating_ode.md @@ -54,7 +54,7 @@ assess_identifiability(ode) ## Defining using `ModelingToolkit` -`StructuralIdentifiability` has an extension `ModelingToolkitExt` which allows to use `ODESystem` from `ModelingToolkit` to describe +`StructuralIdentifiability` has an extension `ModelingToolkitSIExt` which allows to use `ODESystem` from `ModelingToolkit` to describe a model. The extension is loaded automatically once `ModelingToolkit` is loaded via `using ModelingToolkit`. In this case, one should encode the equations for the states as `ODESystem` and specify the outputs separately. In order to do this, we first introduce all functions and scalars: diff --git a/docs/src/tutorials/discrete_time.md b/docs/src/tutorials/discrete_time.md index c8f0571f..4138c47b 100644 --- a/docs/src/tutorials/discrete_time.md +++ b/docs/src/tutorials/discrete_time.md @@ -87,7 +87,7 @@ assess_local_identifiability(dds; funcs_to_check = [β * S]) As other main functions in the package, `assess_local_identifiability` accepts an optional parameter `loglevel` (default: `Logging.Info`) to adjust the verbosity of logging. -If one loads `ModelingToolkit` (and thus the `ModelingToolkitExt` extension), one can use `DiscreteSystem` from `ModelingToolkit` to +If one loads `ModelingToolkit` (and thus the `ModelingToolkitSIExt` extension), one can use `DiscreteSystem` from `ModelingToolkit` to describe the input model (now in terms of difference!): ```@example discrete_mtk diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitSIExt.jl similarity index 99% rename from ext/ModelingToolkitExt.jl rename to ext/ModelingToolkitSIExt.jl index 33447508..65af94c9 100644 --- a/ext/ModelingToolkitExt.jl +++ b/ext/ModelingToolkitSIExt.jl @@ -1,4 +1,4 @@ -module ModelingToolkitExt +module ModelingToolkitSIExt using DataStructures using Logging @@ -15,9 +15,6 @@ else using ..ModelingToolkit end -export mtk_to_si -export assess_local_identifiability, assess_identifiability, find_identifiable_functions - # ------------------------------------------------------------------------------ function eval_at_nemo(e::Num, vals::Dict) @@ -96,7 +93,7 @@ Output: - `conversion` dictionary from the symbols in the input MTK model to the variable involved in the produced `ODE` object """ -function mtk_to_si( +function StructuralIdentifiability.mtk_to_si( de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{ModelingToolkit.Equation}, ) @@ -106,7 +103,7 @@ function mtk_to_si( ) end -function mtk_to_si( +function StructuralIdentifiability.mtk_to_si( de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{<:Symbolics.Num}, ) @@ -116,7 +113,7 @@ function mtk_to_si( ) end -function mtk_to_si( +function StructuralIdentifiability.mtk_to_si( de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{<:SymbolicUtils.BasicSymbolic}, ) diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index f5a8cb8e..66713cca 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -172,4 +172,10 @@ end using PrecompileTools include("precompile.jl") +### Extensions ### + +# ModelingToolkit extension. +function mtk_to_si end +export mtk_to_si + end diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index 7753e565..bdeaf619 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,4 +1,4 @@ -if GROUP == "All" || GROUP == "ModelingToolkitExt" +if GROUP == "All" || GROUP == "ModelingToolkitSIExt" @testset "Check identifiability of `ODESystem` object" begin using ModelingToolkit using ModelingToolkit: parameters @@ -656,4 +656,48 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" ) == c[:res] end end + + @testset "Exporting ModelingToolkit Model to SI Model" begin + + # Creates MTK model and assesses its identifiability. + @parameters r1, r2, c1, c2, beta1, beta2, chi1, chi2 + @variables t, x1(t), x2(t), y(t), u(t) + D = Differential(t) + eqs = [ + D(x1) ~ r1 * x1 * (1 - c1 * x1) + beta1 * x1 * x2 / (chi1 + x2) + u, + D(x2) ~ r2 * x2 * (1 - c2 * x2) + beta2 * x1 * x2 / (chi2 + x1), + ] + measured_quantities = [y ~ x1] + ode_mtk = ODESystem(eqs, t, name = :mutualist) + + global_id_1 = + assess_identifiability(ode_mtk, measured_quantities = measured_quantities) + local_id_1 = + assess_local_identifiability(ode_mtk, measured_quantities = measured_quantities) + ifs_1 = + find_identifiable_functions(ode_mtk, measured_quantities = measured_quantities) + + # Converts mtk model to si model, and assesses its identifiability. + si_model, _ = mtk_to_si(ode_mtk, measured_quantities) + global_id_2 = assess_identifiability(si_model) + local_id_2 = assess_local_identifiability(si_model) + ifs_2 = find_identifiable_functions(si_model) + + # Converts the output dicts from StructuralIdentifiability functions from "weird symbol => stuff" to "symbol => stuff" (the output have some strange meta data which prevents equality checks, this enables this). + # Structural identifiability also provides variables like x (rather than x(t)). This is a bug, but we have to convert to make it work (now just remove any (t) to make them all equal). + function sym_dict(dict_in) + dict_out = Dict{Symbol, Any}() + for key in keys(dict_in) + sym_key = Symbol(key) + sym_key = Symbol(replace(String(sym_key), "(t)" => "")) + dict_out[sym_key] = dict_in[key] + end + return dict_out + end + + # Checks that the two approaches yields the same result + @test issetequal(sym_dict(local_id_1), sym_dict(local_id_2)) + @test issetequal(sym_dict(local_id_1), sym_dict(local_id_2)) + @test length(ifs_1) == length(ifs_2) + end end diff --git a/test/runtests.jl b/test/runtests.jl index 1c75f517..e5ed9fe7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -68,7 +68,7 @@ using StructuralIdentifiability: const GROUP = get(ENV, "GROUP", "All") @static if VERSION >= v"1.10.0" - if GROUP == "All" || GROUP == "ModelingToolkitExt" + if GROUP == "All" || GROUP == "ModelingToolkitSIExt" using Pkg Pkg.add("ModelingToolkit") Pkg.add("Symbolics") @@ -123,7 +123,7 @@ function get_test_files(group) if group == "All" || (group == "Core" && dir != "./extensions") || ( - group == "ModelingToolkitExt" && + group == "ModelingToolkitSIExt" && dir == "./extensions" && VERSION >= v"1.10.0" )