From 9164e4b5786ccc3218c6a685b31bf7293013eb9e Mon Sep 17 00:00:00 2001 From: Gleb Pogudin Date: Fri, 17 Nov 2023 21:07:01 +0100 Subject: [PATCH 01/82] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6cec3338d..a17570a4b 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StructuralIdentifiability" uuid = "220ca800-aa68-49bb-acd8-6037fa93a544" authors = ["Alexander Demin, Ruiwen Dong, Christian Goodbrake, Heather Harrington, Gleb Pogudin "] -version = "0.4.15" +version = "0.4.16" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" From ee9a721295a81bfe3753782831d214a4690153f7 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Fri, 24 Nov 2023 00:08:02 +0000 Subject: [PATCH 02/82] CompatHelper: bump compat for AbstractAlgebra to 0.34, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a17570a4b..19a0658a9 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -AbstractAlgebra = "0.13, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33" +AbstractAlgebra = "0.13, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" From 9c1f1edc67fdad4fef2dec380e90e49e0e067b79 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Fri, 24 Nov 2023 00:08:06 +0000 Subject: [PATCH 03/82] CompatHelper: bump compat for Nemo to 0.38, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a17570a4b..3cdc668c5 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "7, 8" -Nemo = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37" +Nemo = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1" Primes = "0.5" From 535f00cc26b53a37b920f169bda09db2be487f8c Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Tue, 28 Nov 2023 23:35:33 +0100 Subject: [PATCH 04/82] reparametrization tutorial + adding (t) in the reparametrized system --- docs/pages.jl | 1 + docs/src/tutorials/reparametrization.md | 95 +++++++++++++++++++++++++ src/ODE.jl | 2 +- src/parametrizations.jl | 6 +- 4 files changed, 100 insertions(+), 4 deletions(-) create mode 100644 docs/src/tutorials/reparametrization.md diff --git a/docs/pages.jl b/docs/pages.jl index a71718123..b10a14ed9 100644 --- a/docs/pages.jl +++ b/docs/pages.jl @@ -5,6 +5,7 @@ pages = [ "tutorials/identifiability.md", "tutorials/identifiable_functions.md", "tutorials/discrete_time.md", + "tutorials/reparametrization.md", ], "Basics" => Any["input/input.md", "identifiability/identifiability.md"], "Library" => Any[ diff --git a/docs/src/tutorials/reparametrization.md b/docs/src/tutorials/reparametrization.md new file mode 100644 index 000000000..5aacd936e --- /dev/null +++ b/docs/src/tutorials/reparametrization.md @@ -0,0 +1,95 @@ +# Reparametrizations + +## Overview + +Once one has found that not all parameters and/or states of the model at hand are identifiable, one natural desire is to +reparametrize the model into a one with better identifiability properties. +`StructuralIdentifiability` offers such a functionality via the function `reparametrize_global`. +It takes as input an ODE model and produces its transformation into another model with the same +input-output behaviour but with the states and parameters being globally identifiable. +Note that, in general, such a transformation may not exist in the class of rational models, +so sometimes the function returns an ODE not on the whole affine space but on a manifold. + +More precisely, the function returns a dictionary with three keys: + + - `:new_vars` is a dictionary which maps the new parameters and new states into the formulas expressing them in terms of the original parameters and states; + + - `:new_ode` is the ODE satisfied by these new states (and the expression of the output in terms of the new states); + - `:implicit_relations` is a list of algebraic relations between the new states and parameters. Being nonempty, this is exactly the list of equations defining the manifold, on which the new ODE model is defined. In many interesting cases, however, this list is empty meaning that the new ODE is a standard rational ODE model. + +## Example: SEUIR model + +Consider a SEUIR epidemiological model from[^1]: + +$\begin{cases} +S(t)' = -\beta \frac{(U(t) + I(t))S(t)}{N},\\ +E(t)' = \beta \frac{(U(t) + I(t))S(t)}{N} - \gamma E(t),\\ +U(t)' = (1 - \alpha) \gamma E(t) - \delta U(t),\\ +I(t)' = \alpha \gamma E(t) - \delta I(t),\\ +R(t)' = \delta(U(t) + I(t)),\\ +y(t) = I(t) +\end{cases}$ + +In this model `S` is, as usually, the number of susceptible people, `E` is the number of people exposed to virus but not yet infected +(as in a simple SEIR model[^1]), and `I` and `U` correspond to number of infected people who report the infection and who do not, respectively. +We define the model but omit `R` compartment since it does not affect the output dynamics: + +```@example seuir +using StructuralIdentifiability + +ode = @ODEmodel( + S'(t) = -b * (U(t) + I(t)) * S(t) / N, + E'(t) = b * (U(t) + I(t)) * S(t) / N - g * E(t), + U'(t) = (1 - a) * g * E(t) - d * U(t), + I'(t) = a * g * E(t) - d * I(t), + y(t) = I(t) +) +``` + +Majority of the states and parameters are not identifiable in this case: + +```@example seuir +assess_identifiability(ode) +``` + +Let us attempt to reparametrize the model, and print new variables: + +```@example seuir +reparam = reparametrize_global(ode) +@assert isempty(reparam[:implicit_relations]) # checking that the result is an ODE on the whole space, not on a manifold +reparam[:new_vars] +``` + +In these new variables and parameters, the original ODE can be rewritten as follows: + +```@example seuir +reparam[:new_ode] +``` + +In order to analyze this result, let us give more interpretable names to the new variables and parameters: + +$I := I, \; \widetilde{E} := \alpha E, \widetilde{S} := \alpha, \; \widetilde{I} := \alpha (I + U), \; \gamma := \gamma,\;\delta := \delta,\;\widetilde{\beta} := \frac{\beta}{\alpha N}$ + +Then the reparametrize system becomes + +$\begin{cases} +\widetilde{S}'(t) = -\widetilde{\beta} \widetilde{S}(t) \widetilde{I}(t),\\ +\widetilde{E}'(t) = \widetilde{\beta} \widetilde{S}(t) \widetilde{I}(t) - \gamma \widetilde{E}(t),\\ +\widetilde{I}'(t) = -\delta \widetilde{I}(t) + \gamma\widetilde{E}(t),\\ +I'(t) = \gamma\widetilde{E}(t) - \delta I(t),\\ +y(t) = I(t) +\end{cases}$ + +This reparametrization not only reduces the dimension of the parameter space from 5 to 3 but reveals interesting structural properties of the model: + + - The first three equations form a self-contained model which is equivalent to a simple SEIR model, so the model gets "decomposed"; + + - New variables $\widetilde{S}$, $\widetilde{E}$, $\widetilde{I}$ are obtained from $S$, $E$, and $I$ by scaling by $\alpha$ which is the ratio of people who report being infected. One can interpret this as there is a part of population who would report infection and the other part who would not. Ultimately, we can model only the ones who would as this is mainly they who contribute to the output. + +Finally, we can check that the new model is indeed globally identifiable: + +```@example seuir +assess_identifiability(reparam[:new_ode]) +``` + +[^1]: > T. Sauer, T. Berry, D. Ebeigbe, M. Norton, A. Whalen, S. Schiff, [*Identifiability of infection model parameters early in an epidemic*](https://doi.org/10.1137/20m1353289), SIAM Journal on Control and Optimization, 2022; diff --git a/src/ODE.jl b/src/ODE.jl index 9805bcb0e..625c0d21e 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -502,7 +502,7 @@ function Base.show(io::IO, ode::ODE) if endswith(var_to_str(x), "(t)") print(io, var_to_str(x)[1:(end - 3)] * "'(t) = ") else - print(io, var_to_str(x) * " = ") + print(io, var_to_str(x) * "' = ") end print(io, ode.x_equations[x]) print(io, "\n") diff --git a/src/parametrizations.jl b/src/parametrizations.jl index c712b3328..aef46cc12 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -370,10 +370,10 @@ function reparametrize_with_respect_to(ode, new_states, new_params) new_vars_vector_field[state] = new_dynamics_states[i] end @info "Converting variable names to human-readable ones" - internal_variable_names = map(i -> "X$i", 1:length(new_states)) + internal_variable_names = map(i -> "X$i(t)", 1:length(new_states)) parameter_variable_names = map(i -> "a$i", 1:length(new_params)) - input_variable_names = map(i -> "u$i", 1:length(tag_inputs)) - output_variable_names = map(i -> "y$i", 1:length(tag_outputs)) + input_variable_names = map(i -> "u$i(t)", 1:length(tag_inputs)) + output_variable_names = map(i -> "y$i(t)", 1:length(tag_outputs)) all_variable_names = vcat( internal_variable_names, parameter_variable_names, From fc6349aae24de761b1cddf57c6aca18b35386c81 Mon Sep 17 00:00:00 2001 From: Gleb Pogudin Date: Wed, 29 Nov 2023 12:05:10 +0100 Subject: [PATCH 05/82] bump --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6e9226655..c3f5677e2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StructuralIdentifiability" uuid = "220ca800-aa68-49bb-acd8-6037fa93a544" authors = ["Alexander Demin, Ruiwen Dong, Christian Goodbrake, Heather Harrington, Gleb Pogudin "] -version = "0.4.16" +version = "0.5.0" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" From 0877bcf19a65820e6d6e20a06577b40536306cbd Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 29 Nov 2023 15:23:37 +0100 Subject: [PATCH 06/82] bumping actions --- .github/workflows/CI.yml | 2 +- .github/workflows/Documentation.yml | 2 +- .github/workflows/FormatCheck.yml | 2 +- .github/workflows/Invalidations.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4955b36c8..f74e28d4e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,7 +17,7 @@ jobs: - '1' - '1.6' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index adc1628ef..433a9f06d 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/setup-julia@latest with: version: '1' diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index dd551501c..f23d3c45e 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -21,7 +21,7 @@ jobs: with: version: ${{ matrix.julia-version }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install JuliaFormatter and format # This will use the latest version by default but you can set the version like so: # diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml index 28b9ce2fa..7f084d239 100644 --- a/.github/workflows/Invalidations.yml +++ b/.github/workflows/Invalidations.yml @@ -19,12 +19,12 @@ jobs: - uses: julia-actions/setup-julia@v1 with: version: '1' - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-invalidations@v1 id: invs_pr - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ github.event.repository.default_branch }} - uses: julia-actions/julia-buildpkg@v1 From af1257a412467427d5b4d2f84d53537d83e171e6 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 29 Nov 2023 15:54:41 +0100 Subject: [PATCH 07/82] workflows back --- .github/workflows/CI.yml | 2 +- .github/workflows/Documentation.yml | 2 +- .github/workflows/FormatCheck.yml | 2 +- .github/workflows/Invalidations.yml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f74e28d4e..4955b36c8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,7 +17,7 @@ jobs: - '1' - '1.6' steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml index 433a9f06d..adc1628ef 100644 --- a/.github/workflows/Documentation.yml +++ b/.github/workflows/Documentation.yml @@ -11,7 +11,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@latest with: version: '1' diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml index f23d3c45e..dd551501c 100644 --- a/.github/workflows/FormatCheck.yml +++ b/.github/workflows/FormatCheck.yml @@ -21,7 +21,7 @@ jobs: with: version: ${{ matrix.julia-version }} - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - name: Install JuliaFormatter and format # This will use the latest version by default but you can set the version like so: # diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml index 7f084d239..28b9ce2fa 100644 --- a/.github/workflows/Invalidations.yml +++ b/.github/workflows/Invalidations.yml @@ -19,12 +19,12 @@ jobs: - uses: julia-actions/setup-julia@v1 with: version: '1' - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-invalidations@v1 id: invs_pr - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - uses: julia-actions/julia-buildpkg@v1 From 958593bdc99e1597ee77d911313df5e9237f56a4 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 29 Nov 2023 16:23:31 +0100 Subject: [PATCH 08/82] compat for the docs --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index e4c519b6b..21e7b1811 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -8,4 +8,4 @@ StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" BenchmarkTools = "1.3" Documenter = "0.27, 1" ModelingToolkit = "8.34" -StructuralIdentifiability = "0.4" +StructuralIdentifiability = "0.5" From 7550ac54c36c738c025de5a3b02c5fdeb6fb471e Mon Sep 17 00:00:00 2001 From: Gleb Pogudin Date: Wed, 29 Nov 2023 17:18:00 +0100 Subject: [PATCH 09/82] bump docs --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c3f5677e2..b69f3f9e6 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StructuralIdentifiability" uuid = "220ca800-aa68-49bb-acd8-6037fa93a544" authors = ["Alexander Demin, Ruiwen Dong, Christian Goodbrake, Heather Harrington, Gleb Pogudin "] -version = "0.5.0" +version = "0.5.1" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" From 9779f3afb7340481d85cc25156579f5dfe6da521 Mon Sep 17 00:00:00 2001 From: Alexander Demin Date: Mon, 11 Dec 2023 15:36:36 +0300 Subject: [PATCH 10/82] Add Downgrade.yml --- .github/workflows/Downgrade.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/Downgrade.yml diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml new file mode 100644 index 000000000..f4b77a36d --- /dev/null +++ b/.github/workflows/Downgrade.yml @@ -0,0 +1,30 @@ +name: Downgrade +on: + pull_request: + branches: + - master + paths-ignore: + - 'docs/**' + push: + branches: + - master + paths-ignore: + - 'docs/**' +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + version: ['1'] + steps: + - uses: actions/checkout@v3 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + - uses: cjdoris/julia-downgrade-compat-action@v1 +# if: ${{ matrix.version == '1.6' }} + with: + # skip standard libraries + skip: Pkg,TOML,Logging,Random,Dates + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 From 550238ede6adbc82000fb6be503cf53edf3ee5f1 Mon Sep 17 00:00:00 2001 From: Alexander Demin Date: Mon, 11 Dec 2023 15:43:16 +0300 Subject: [PATCH 11/82] up --- .github/workflows/Downgrade.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index f4b77a36d..aed1e50c9 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -24,7 +24,7 @@ jobs: - uses: cjdoris/julia-downgrade-compat-action@v1 # if: ${{ matrix.version == '1.6' }} with: - # skip standard libraries - skip: Pkg,TOML,Logging,Random,Dates + # skip standard libraries.. + skip: Pkg,TOML,Logging,Random,Dates,LinearAlgebra - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From d6b8f6c5ea47bb05ecf481e3ff4e8e952cca26ee Mon Sep 17 00:00:00 2001 From: Alexander Demin Date: Mon, 11 Dec 2023 15:51:01 +0300 Subject: [PATCH 12/82] strict: 'false' --- .github/workflows/Downgrade.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index aed1e50c9..3d93f861d 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -26,5 +26,6 @@ jobs: with: # skip standard libraries.. skip: Pkg,TOML,Logging,Random,Dates,LinearAlgebra + strict: 'false' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From 59de11d5ffbf5eb6acb59a2f8289d06545d0f1e1 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 16 Dec 2023 16:58:42 +0100 Subject: [PATCH 13/82] bumping Nemo --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b69f3f9e6..14d6e4d78 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "7, 8" -Nemo = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" +Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1" Primes = "0.5" From b8dcc0477d45a64437c5855247784c674f472c63 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 16 Dec 2023 17:05:06 +0100 Subject: [PATCH 14/82] bump MTK --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 14d6e4d78..c7a189e1e 100644 --- a/Project.toml +++ b/Project.toml @@ -36,7 +36,7 @@ IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" -ModelingToolkit = "7, 8" +ModelingToolkit = "8" Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1" From 6189c22ddd6019d498278d9deb5edec71b75f0e0 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 16 Dec 2023 17:23:12 +0100 Subject: [PATCH 15/82] bump MTK --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c7a189e1e..74e64736e 100644 --- a/Project.toml +++ b/Project.toml @@ -36,7 +36,7 @@ IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" -ModelingToolkit = "8" +ModelingToolkit = "8.47" Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1" From 21abbf50f4c7962328a3c0262b58621442b6e576 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 16 Dec 2023 21:21:59 +0100 Subject: [PATCH 16/82] bump AA --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 74e64736e..0a2089c79 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -AbstractAlgebra = "0.13, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" +AbstractAlgebra = "0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" From f162f3f10348647443a9624a822e95553da8ee40 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 16 Dec 2023 21:27:43 +0100 Subject: [PATCH 17/82] bump AA again --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 0a2089c79..c3f2718a1 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -AbstractAlgebra = "0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" +AbstractAlgebra = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" From b037cb1a8e872d6f8443f7b6c3803bfa69218f00 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Tue, 19 Dec 2023 11:10:26 +0100 Subject: [PATCH 18/82] bump Symbolics --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index c3f2718a1..36bb53a70 100644 --- a/Project.toml +++ b/Project.toml @@ -44,7 +44,7 @@ Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "1, 2" SymbolicUtils = "1" -Symbolics = "5" +Symbolics = "5.5" TestSetExtensions = "2" TimerOutputs = "0.5" julia = "1.6, 1.7" From b9aa739b038343b10aa8b2c58dfe5291ec737868 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Tue, 19 Dec 2023 13:29:20 +0100 Subject: [PATCH 19/82] bump MTK --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 36bb53a70..3812417d5 100644 --- a/Project.toml +++ b/Project.toml @@ -36,7 +36,7 @@ IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" -ModelingToolkit = "8.47" +ModelingToolkit = "8.51" Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1" From 90e56e9313c9de25ba095e2754d5d72d3c2bcba4 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Fri, 5 Jan 2024 00:08:26 +0000 Subject: [PATCH 20/82] CompatHelper: bump compat for AbstractAlgebra to 0.35, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b69f3f9e6..d0d2f28a6 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -AbstractAlgebra = "0.13, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" +AbstractAlgebra = "0.13, 0.18, 0.19, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" From 97ee790b3c33d1090583b810d4b85141f8c81aff Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Fri, 5 Jan 2024 00:08:30 +0000 Subject: [PATCH 21/82] CompatHelper: bump compat for Nemo to 0.39, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b69f3f9e6..b1f1d32e7 100644 --- a/Project.toml +++ b/Project.toml @@ -37,7 +37,7 @@ LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "7, 8" -Nemo = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" +Nemo = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39" ParamPunPam = "0.2" PrecompileTools = "1" Primes = "0.5" From 1661442bbd8bede3eee50947b107fc1732651750 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Sun, 7 Jan 2024 00:08:59 +0000 Subject: [PATCH 22/82] CompatHelper: bump compat for Groebner to 0.6, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 053a2735f..6d621b93d 100644 --- a/Project.toml +++ b/Project.toml @@ -31,7 +31,7 @@ BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" Dates = "1.6, 1.7" -Groebner = "0.4, 0.5" +Groebner = "0.4, 0.5, 0.6" IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" From c3bab788864d61b2fd84679ffcdce2086cec9e0d Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Mon, 8 Jan 2024 00:38:02 +0100 Subject: [PATCH 23/82] PrecompileTools --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 45a962f85..32bc21b06 100644 --- a/Project.toml +++ b/Project.toml @@ -39,7 +39,7 @@ MacroTools = "0.5" ModelingToolkit = "8.51" Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39" ParamPunPam = "0.2" -PrecompileTools = "1" +PrecompileTools = "1.1, 1.2" Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "1, 2" From 94546b196d23fbc0e306c0f380344f3caca39dbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jan 2024 21:18:34 +0000 Subject: [PATCH 24/82] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/Downgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 3d93f861d..3a84c6477 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -17,7 +17,7 @@ jobs: matrix: version: ['1'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} From 9d6a27ad0f55f1a83843d51453187f68cca0ec03 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Mon, 8 Jan 2024 22:26:10 +0100 Subject: [PATCH 25/82] renaming p to prob_threschold --- docs/src/tutorials/discrete_time.md | 4 +- docs/src/tutorials/identifiability.md | 6 +-- .../RationalFunctionField.jl | 42 +++++++++---------- src/StructuralIdentifiability.jl | 32 +++++++------- src/discrete.jl | 22 +++++----- src/global_identifiability.jl | 36 ++++++++-------- src/identifiable_functions.jl | 22 +++++----- src/local_identifiability.jl | 28 ++++++------- src/parametrizations.jl | 10 ++--- test/local_identifiability_me.jl | 2 +- test/mtk_compat.jl | 6 +-- 11 files changed, 105 insertions(+), 105 deletions(-) diff --git a/docs/src/tutorials/discrete_time.md b/docs/src/tutorials/discrete_time.md index 6978fb4a7..212f59db8 100644 --- a/docs/src/tutorials/discrete_time.md +++ b/docs/src/tutorials/discrete_time.md @@ -67,8 +67,8 @@ The `assess_local_identifiability` function has three important keyword argument assess_local_identifiability(sir; measured_quantities = [I], funcs_to_check = [β * S]) ``` - - `p` is the probability of correctness (default value `0.99`, i.e., 99%). The underlying algorithm is a Monte-Carlo algorithm, so in - principle it may produce incorrect result but the probability of correctness of the returned result is guaranteed to be at least `p` + - `prob_threshold` is the probability of correctness (default value `0.99`, i.e., 99%). The underlying algorithm is a Monte-Carlo algorithm, so in + principle it may produce incorrect result but the probability of correctness of the returned result is guaranteed to be at least `prob_threshold` (in fact, the employed bounds are quite conservative, so in practice incorrect result is almost never produced). - `known_ic` is a list of the states for which initial conditions are known. In this case, the identifiability results will be valid not diff --git a/docs/src/tutorials/identifiability.md b/docs/src/tutorials/identifiability.md index d89ceb5f4..c88ba3d94 100644 --- a/docs/src/tutorials/identifiability.md +++ b/docs/src/tutorials/identifiability.md @@ -59,8 +59,8 @@ Function `assess_local_identifiability` has several optional parameters - `funcs_to_check` a list of specific functions of parameters and states to check identifiability for (see an example below). If not provided, the identifiability is assessed for all parameters and states. - - `p` (default $0.99$) is the probability of correctness. The algorithm can, in theory, produce wrong result, but the probability that it is correct - is guaranteed to be at least `p`. However, the probability bounds we use are quite conservative, so the actual probability of correctness is + - `prob_threshold` (default $0.99$, i.e. 99%) is the probability of correctness. The algorithm can, in theory, produce wrong result, but the probability that it is correct + is guaranteed to be at least `prob_threshold`. However, the probability bounds we use are quite conservative, so the actual probability of correctness is likely to be much higher. - `type` (default `:SE`). By default, the algorithm checks the standard single-experiment identifiability. If one sets `type = :ME`, then the algorithm checks multi-experiment identifiability, that is, identifiability from several experiments with independent initial conditions (the algorithm from [^2] is used). @@ -105,7 +105,7 @@ Similarly to `assess_local_identifiability`, this function has optional paramete more involved than for the parameters, so one may want to call the function with `funcs_to_check = ode.parameters` if the call `assess_identifiability(ode)` takes too long. - - `p` (default $0.99$) is the probability of correctness. Same story as above: the probability estimates are very conservative, so the actual + - `prob_threshold` (default $0.99$, i.e. 99%) is the probability of correctness. Same story as above: the probability estimates are very conservative, so the actual error probability is much lower than 1%. Also, currently, the probability of correctness does not include the probability of correctness of the modular reconstruction for Groebner bases. This probability is ensured by an additional check modulo a large prime, and can be neglected for practical purposes. diff --git a/src/RationalFunctionFields/RationalFunctionField.jl b/src/RationalFunctionFields/RationalFunctionField.jl index 9c8e68fbf..baedac597 100644 --- a/src/RationalFunctionFields/RationalFunctionField.jl +++ b/src/RationalFunctionFields/RationalFunctionField.jl @@ -76,17 +76,17 @@ end # ------------------------------------------------------------------------------ """ - field_contains(field, ratfuncs, p) + field_contains(field, ratfuncs, prob_threshold) Checks whether given rational function field `field` contains given rational functions `ratfuncs` (represented as a list of lists). The result is correct with -probability at least `p` +probability at least `prob_threshold` Inputs: - `field` - a rational function field - `ratfuncs` - a list of lists of polynomials. Each of the lists, say, `[f1, ..., fn]`, defines generators `f2/f1, ..., fn/f1`. -- `p` real number from (0, 1) +- `prob_threshold` real number from (0, 1) Output: - a list `L[i]` of bools of length `length(rat_funcs)` such that `L[i]` is true iff @@ -95,7 +95,7 @@ Output: @timeit _to function field_contains( field::RationalFunctionField{T}, ratfuncs::Vector{Vector{T}}, - p, + prob_threshold, ) where {T} if isempty(ratfuncs) return Bool[] @@ -133,7 +133,7 @@ Output: 3 * BigInt(degree)^(length(total_vars) + 3) * (length(ratfuncs)) * - ceil(1 / (1 - p)), + ceil(1 / (1 - prob_threshold)), ) @debug "\tSampling from $(-sampling_bound) to $(sampling_bound)" @@ -153,24 +153,24 @@ end function field_contains( field::RationalFunctionField{T}, ratfuncs::Vector{Generic.Frac{T}}, - p, + prob_threshold, ) where {T} - return field_contains(field, fractions_to_dennums(ratfuncs), p) + return field_contains(field, fractions_to_dennums(ratfuncs), prob_threshold) end -function field_contains(field::RationalFunctionField{T}, polys::Vector{T}, p) where {T} +function field_contains(field::RationalFunctionField{T}, polys::Vector{T}, prob_threshold) where {T} id = one(parent(first(polys))) - return field_contains(field, [[id, p] for p in polys], p) + return field_contains(field, [[id, p] for p in polys], prob_threshold) end # ------------------------------------------------------------------------------ -function issubfield(F::RationalFunctionField{T}, E::RationalFunctionField{T}, p) where {T} - return all(field_contains(E, F.dennums, p)) +function issubfield(F::RationalFunctionField{T}, E::RationalFunctionField{T}, prob_threshold) where {T} + return all(field_contains(E, F.dennums, prob_threshold)) end -function fields_equal(F::RationalFunctionField{T}, E::RationalFunctionField{T}, p) where {T} - new_p = 1 - (1 - p) / 2 +function fields_equal(F::RationalFunctionField{T}, E::RationalFunctionField{T}, prob_threshold) where {T} + new_p = 1 - (1 - prob_threshold) / 2 return issubfield(F, E, new_p) && issubfield(E, F, new_p) end @@ -475,14 +475,14 @@ function monomial_generators_up_to_degree( end """ - simplified_generating_set(rff; p = 0.99, seed = 42) + simplified_generating_set(rff; prob_threshold = 0.99, seed = 42) Returns a simplified set of generators for `rff`. -Result is correct (in Monte-Carlo sense) with probability at least `p`. +Result is correct (in the Monte-Carlo sense) with probability at least `prob_threshold`. """ @timeit _to function simplified_generating_set( rff::RationalFunctionField; - p = 0.99, + prob_threshold = 0.99, seed = 42, simplify = :standard, check_variables = false, # almost always slows down and thus turned off @@ -500,8 +500,8 @@ Result is correct (in Monte-Carlo sense) with probability at least `p`. # Checking membership of particular variables and adding them to the field if check_variables vars = gens(poly_ring(rff)) - containment = field_contains(rff, vars, (1.0 + p) / 2) - p = (1.0 + p) / 2 + containment = field_contains(rff, vars, (1.0 + prob_threshold) / 2) + prob_threshold = (1.0 + prob_threshold) / 2 if all(containment) return [v // one(poly_ring(rff)) for v in vars] end @@ -557,14 +557,14 @@ Final cleaning and simplification of generators. Out of $(length(new_fracs)) fractions $(length(new_fracs_unique)) are syntactically unique.""" runtime = @elapsed new_fracs = beautifuly_generators(RationalFunctionField(new_fracs_unique)) - @debug "Checking inclusion with probability $p" - runtime = @elapsed result = issubfield(rff, RationalFunctionField(new_fracs), p) + @debug "Checking inclusion with probability $prob_threshold" + runtime = @elapsed result = issubfield(rff, RationalFunctionField(new_fracs), prob_threshold) _runtime_logger[:id_inclusion_check] = runtime if !result @warn "Field membership check failed. Error will follow." throw("The new subfield generators are not correct.") end - @info "Inclusion checked with probability $p in $(_runtime_logger[:id_inclusion_check]) seconds" + @info "Inclusion checked with probability $prob_threshold in $(_runtime_logger[:id_inclusion_check]) seconds" @debug "Out of $(length(rff.mqs.nums_qq)) initial generators there are $(length(new_fracs)) indepdendent" ranking = generating_set_rank(new_fracs) _runtime_logger[:id_ranking] = ranking diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 165570f3a..40056e887 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -82,40 +82,40 @@ function __init__() end """ - assess_identifiability(ode; funcs_to_check = [], p=0.99, loglevel=Logging.Info) + assess_identifiability(ode; funcs_to_check = [], prob_threshold=0.99, loglevel=Logging.Info) Input: - `ode` - the ODE model - `funcs_to_check` - list of functions to check identifiability for; if empty, all parameters and states are taken -- `p` - probability of correctness. +- `prob_threshold` - probability of correctness. - `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) Assesses identifiability of a given ODE model. The result is guaranteed to be correct with the probability -at least `p`. +at least `prob_threshold`. The function returns an (ordered) dictionary from the functions to check to their identifiability properties (one of `:nonidentifiable`, `:locally`, `:globally`). """ function assess_identifiability( ode::ODE{P}; funcs_to_check = Vector(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, loglevel = Logging.Info, ) where {P <: MPolyElem{fmpq}} restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do - return _assess_identifiability(ode, funcs_to_check = funcs_to_check, p = p) + return _assess_identifiability(ode, funcs_to_check = funcs_to_check, prob_threshold = prob_threshold) end end function _assess_identifiability( ode::ODE{P}; funcs_to_check = Vector(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, ) where {P <: MPolyElem{fmpq}} - p_glob = 1 - (1 - p) * 0.9 - p_loc = 1 - (1 - p) * 0.1 + p_glob = 1 - (1 - prob_threshold) * 0.9 + p_loc = 1 - (1 - prob_threshold) * 0.1 if isempty(funcs_to_check) funcs_to_check = vcat(ode.x_vars, ode.parameters) @@ -126,7 +126,7 @@ function _assess_identifiability( runtime = @elapsed local_result = _assess_local_identifiability( ode, funcs_to_check = funcs_to_check, - p = p_loc, + prob_threshold = p_loc, type = :SE, trbasis = trbasis, ) @@ -167,23 +167,23 @@ function _assess_identifiability( end """ - assess_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=[], p = 0.99, loglevel=Logging.Info) + assess_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=[], prob_threshold = 0.99, loglevel=Logging.Info) Input: - `ode` - the ModelingToolkit.ODESystem object that defines the model - `measured_quantities` - the output functions of the model - `funcs_to_check` - functions of parameters for which to check the identifiability -- `p` - probability of correctness. +- `prob_threshold` - probability of correctness. - `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) Assesses identifiability (both local and global) of a given ODE model (parameters detected automatically). The result is guaranteed to be correct with the probability -at least `p`. +at least `prob_threshold`. """ function assess_identifiability( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = [], - p = 0.99, + prob_threshold = 0.99, loglevel = Logging.Info, ) restart_logging(loglevel = loglevel) @@ -192,7 +192,7 @@ function assess_identifiability( ode, measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, - p = p, + prob_threshold = prob_threshold, ) end end @@ -201,7 +201,7 @@ function _assess_identifiability( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = [], - p = 0.99, + prob_threshold = 0.99, ) if isempty(measured_quantities) measured_quantities = get_measured_quantities(ode) @@ -214,7 +214,7 @@ function _assess_identifiability( 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) + result = _assess_identifiability(ode, funcs_to_check = funcs_to_check_, prob_threshold = prob_threshold) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return out_dict diff --git a/src/discrete.jl b/src/discrete.jl index a8bf8da70..ed0e6020d 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -178,11 +178,11 @@ function _degree_with_common_denom(polys) end """ - _assess_local_identifiability_discrete_aux(dds::ODE{P}, funcs_to_check::Array{<: Any, 1}, known_ic, p::Float64=0.99) where P <: MPolyElem{Nemo.fmpq} + _assess_local_identifiability_discrete_aux(dds::ODE{P}, funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99) where P <: MPolyElem{Nemo.fmpq} Checks the local identifiability/observability of the functions in `funcs_to_check` treating `dds` as a discrete-time system with **shift** instead of derivative in the right-hand side. -The result is correct with probability at least `p`. +The result is correct with probability at least `prob_threshold`. `known_ic` can take one of the following * `:none` - no initial conditions are assumed to be known * `:all` - all initial conditions are assumed to be known @@ -192,7 +192,7 @@ function _assess_local_identifiability_discrete_aux( dds::ODE{P}, funcs_to_check::Array{<:Any, 1}, known_ic = :none, - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, ) where {P <: MPolyElem{Nemo.fmpq}} bring = base_ring(dds.poly_ring) @@ -222,7 +222,7 @@ function _assess_local_identifiability_discrete_aux( else Jac_degree += 2 * deg_y * prec end - D = Int(ceil(Jac_degree * length(funcs_to_check) / (1 - p))) + D = Int(ceil(Jac_degree * length(funcs_to_check) / (1 - prob_threshold))) @debug "Sampling range $D" # Parameter values are the same across all the replicas @@ -293,26 +293,26 @@ end measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], known_ic=Array{}[], - p::Float64=0.99) + prob_threshold::Float64=0.99) Input: - `dds` - the DiscreteSystem object from ModelingToolkit (with **difference** operator in the right-hand side) - `measured_quantities` - the measurable outputs of the model - `funcs_to_check` - functions of parameters for which to check identifiability (all parameters and states if not specified) - `known_ic` - functions (of states and parameter) whose initial conditions are assumed to be known -- `p` - probability of correctness +- `prob_threshold` - probability of correctness Output: - the result is an (ordered) dictionary from each function to to boolean; -The result is correct with probability at least `p`. +The result is correct with probability at least `prob_threshold`. """ function assess_local_identifiability( dds::ModelingToolkit.DiscreteSystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], known_ic = Array{}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, loglevel = Logging.Info, ) restart_logging(loglevel = loglevel) @@ -322,7 +322,7 @@ function assess_local_identifiability( measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, known_ic = known_ic, - p = p, + prob_threshold = prob_threshold, ) end end @@ -332,7 +332,7 @@ function _assess_local_identifiability( measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], known_ic = Array{}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, ) if length(measured_quantities) == 0 if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(dds)) @@ -374,7 +374,7 @@ function _assess_local_identifiability( known_ic_ = [eval_at_nemo(x, conversion) for x in known_ic] result = - _assess_local_identifiability_discrete_aux(dds_aux, funcs_to_check_, known_ic_, p) + _assess_local_identifiability_discrete_aux(dds_aux, funcs_to_check_, known_ic_, prob_threshold) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) if length(known_ic) > 0 diff --git a/src/global_identifiability.jl b/src/global_identifiability.jl index 5f61644e0..984747e3b 100644 --- a/src/global_identifiability.jl +++ b/src/global_identifiability.jl @@ -88,7 +88,7 @@ These are presented by the coefficients of the IO-equations. ## Options This function takes the following optional arguments: -- `p`: probability of correctness +- `prob_threshold`: probability of correctness - `with_states`: Also report the identifiabile functions in states. Default is `false`. If this is `true`, the identifiable functions involving parameters only will be simplified @@ -99,7 +99,7 @@ The function returns a tuple containing the following: """ @timeit _to function initial_identifiable_functions( ode::ODE{T}; - p::Float64, + prob_threshold::Float64, known::Array{T, 1} = Array{T, 1}(), with_states::Bool = false, var_change_policy = :default, @@ -157,7 +157,7 @@ The function returns a tuple containing the following: _runtime_logger[:check_time] = @elapsed no_states_simplified = simplified_generating_set( RationalFunctionField(id_funcs_no_states_param), - p = p, + prob_threshold = prob_threshold, seed = 42, simplify = :standard, rational_interpolator = rational_interpolator, @@ -178,13 +178,13 @@ end # ------------------------------------------------------------------------------ """ - check_identifiability(ode, funcs_to_check; known, p, var_change_policy) + check_identifiability(ode, funcs_to_check; known, prob_threshold, var_change_policy) Input: - `ode` - the ODE model - `funcs_to_check` - the functions to check identifiability for - `known` - a list of functions in states which are assumed to be known and generic -- `p` - probability of correctness +- `prob_threshold` - probability of correctness - `var_change` - a policy for variable change (`:default`, `:yes`, `:no`), affects only the runtime Output: a list L of booleans with L[i] being the identifiability status of the i-th function to check @@ -193,7 +193,7 @@ Output: a list L of booleans with L[i] being the identifiability status of the i ode::ODE{P}, funcs_to_check::Array{<:Any, 1}; known::Array{P, 1} = Array{P, 1}(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, var_change_policy = :default, ) where {P <: MPolyElem{fmpq}} states_needed = false @@ -209,11 +209,11 @@ Output: a list L of booleans with L[i] being the identifiability status of the i return [true for _ in funcs_to_check] end - half_p = 0.5 + p / 2 + half_p = 0.5 + prob_threshold / 2 identifiable_functions_raw, bring = initial_identifiable_functions( ode, known = known, - p = p, + prob_threshold = half_p, var_change_policy = var_change_policy, with_states = states_needed, ) @@ -234,26 +234,26 @@ end function check_identifiability( ode::ODE{P}; known::Array{P, 1} = Array{P, 1}(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, var_change_policy = :default, ) where {P <: MPolyElem{fmpq}} return check_identifiability( ode, ode.parameters, known = known, - p = p, + prob_threshold = prob_threshold, var_change_policy = var_change_policy, ) end #------------------------------------------------------------------------------ """ - assess_global_identifiability(ode::ODE{P}, p::Float64=0.99; var_change=:default) where P <: MPolyElem{fmpq} + assess_global_identifiability(ode::ODE{P}, prob_threshold::Float64=0.99; var_change=:default) where P <: MPolyElem{fmpq} Input: - `ode` - the ODE model - `known` - a list of functions in states which are assumed to be known and generic -- `p` - probability of correctness +- `prob_threshold` - probability of correctness - `var_change` - a policy for variable change (`:default`, `:yes`, `:no`), affects only the runtime Output: @@ -264,14 +264,14 @@ Checks global identifiability for parameters of the model provided in `ode`. Cal function assess_global_identifiability( ode::ODE{P}, known::Array{P, 1} = Array{P, 1}(), - p::Float64 = 0.99; + prob_threshold::Float64 = 0.99; var_change = :default, ) where {P <: MPolyElem{fmpq}} result_list = assess_global_identifiability( ode, ode.parameters, known, - p; + prob_threshold; var_change = var_change, ) @@ -281,13 +281,13 @@ end #------------------------------------------------------------------------------ """ - assess_global_identifiability(ode, [funcs_to_check, p=0.99, var_change=:default]) + assess_global_identifiability(ode, [funcs_to_check, prob_threshold=0.99, var_change=:default]) Input: - `ode` - the ODE model - `funcs_to_check` - rational functions in parameters - `known` - function in parameters that are assumed to be known and generic -- `p` - probability of correctness +- `prob_threshold` - probability of correctness - `var_change` - a policy for variable change (`:default`, `:yes`, `:no`), affects only the runtime @@ -301,7 +301,7 @@ Checks global identifiability of functions of parameters specified in `funcs_to_ ode::ODE{P}, funcs_to_check::Array{<:Any, 1}, known::Array{P, 1} = Array{P, 1}(), - p::Float64 = 0.99; + prob_threshold::Float64 = 0.99; var_change = :default, ) where {P <: MPolyElem{fmpq}} submodels = find_submodels(ode) @@ -313,7 +313,7 @@ Checks global identifiability of functions of parameters specified in `funcs_to_ ode, funcs_to_check, known = known, - p = p, + prob_threshold = prob_threshold, var_change_policy = var_change, ) diff --git a/src/identifiable_functions.jl b/src/identifiable_functions.jl index 4edb29b83..79db0ffbd 100644 --- a/src/identifiable_functions.jl +++ b/src/identifiable_functions.jl @@ -18,7 +18,7 @@ This functions takes the following optional arguments: - `:strong`: Strong simplification. This option is the slowest, but the output functions are nice and simple. - `:absent`: No simplification. -- `p`: A float in the range from 0 to 1, the probability of correctness. Default +- `prob_threshold`: A float in the range from 0 to 1, the probability of correctness. Default is `0.99`. - `seed`: The rng seed. Default value is `42`. - `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) @@ -45,7 +45,7 @@ find_identifiable_functions(ode) """ function find_identifiable_functions( ode::ODE{T}; - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, seed = 42, with_states = false, simplify = :standard, @@ -57,7 +57,7 @@ function find_identifiable_functions( with_logger(_si_logger[]) do return _find_identifiable_functions( ode, - p = p, + prob_threshold = prob_threshold, seed = seed, with_states = with_states, simplify = simplify, @@ -68,7 +68,7 @@ end function _find_identifiable_functions( ode::ODE{T}; - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, seed = 42, with_states = false, simplify = :standard, @@ -87,10 +87,10 @@ function _find_identifiable_functions( id_funcs = [one(bring)] return id_funcs end - half_p = 0.5 + p / 2 + half_p = 0.5 + prob_threshold / 2 id_funcs, bring = initial_identifiable_functions( ode, - p = half_p, + prob_threshold = half_p, with_states = with_states, rational_interpolator = rational_interpolator, ) @@ -102,7 +102,7 @@ function _find_identifiable_functions( end id_funcs_fracs = simplified_generating_set( RationalFunctionField(id_funcs), - p = half_p, + prob_threshold = half_p, seed = seed, simplify = simplify, rational_interpolator = rational_interpolator, @@ -158,7 +158,7 @@ find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) function find_identifiable_functions( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, seed = 42, with_states = false, simplify = :standard, @@ -171,7 +171,7 @@ function find_identifiable_functions( return _find_identifiable_functions( ode, measured_quantities = measured_quantities, - p = p, + prob_threshold = prob_threshold, seed = seed, with_states = with_states, simplify = simplify, @@ -183,7 +183,7 @@ end function _find_identifiable_functions( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, seed = 42, with_states = false, simplify = :standard, @@ -197,7 +197,7 @@ function _find_identifiable_functions( result = _find_identifiable_functions( ode, simplify = simplify, - p = p, + prob_threshold = prob_threshold, seed = seed, with_states = with_states, rational_interpolator = rational_interpolator, diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index 3d680c336..ae2a3cbba 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -143,13 +143,13 @@ end # ------------------------------------------------------------------------------ """ - function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], p::Float64=0.99, type=:SE, loglevel=Logging.Info) + function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) Input: - `ode` - the ODESystem object from ModelingToolkit - `measured_quantities` - the measurable outputs of the model - `funcs_to_check` - functions of parameters for which to check identifiability -- `p` - probability of correctness +- `prob_threshold` - probability of correctness - `type` - identifiability type (`:SE` for single-experiment, `:ME` for multi-experiment) - `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) @@ -159,7 +159,7 @@ Output: The function determines local identifiability of parameters in `funcs_to_check` or all possible parameters if `funcs_to_check` is empty -The result is correct with probability at least `p`. +The result is correct with probability at least `prob_threshold`. `type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability). The return value is a tuple consisting of the array of bools and the number of experiments to be performed. @@ -168,7 +168,7 @@ function assess_local_identifiability( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, type = :SE, loglevel = Logging.Info, ) @@ -178,7 +178,7 @@ function assess_local_identifiability( ode, measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, - p = p, + prob_threshold = prob_threshold, type = type, ) end @@ -188,7 +188,7 @@ end ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, type = :SE, ) if length(measured_quantities) == 0 @@ -219,7 +219,7 @@ end result = _assess_local_identifiability( ode, funcs_to_check = funcs_to_check_, - p = p, + prob_threshold = prob_threshold, type = type, ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) @@ -230,7 +230,7 @@ end result, bd = _assess_local_identifiability( ode, funcs_to_check = funcs_to_check_, - p = p, + prob_threshold = prob_threshold, type = type, ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) @@ -242,9 +242,9 @@ end # ------------------------------------------------------------------------------ """ - assess_local_identifiability(ode::ODE{P}; funcs_to_check::Array{<: Any, 1}, p::Float64=0.99, type=:SE, loglevel=Logging.Info) where P <: MPolyElem{Nemo.fmpq} + assess_local_identifiability(ode::ODE{P}; funcs_to_check::Array{<: Any, 1}, prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) where P <: MPolyElem{Nemo.fmpq} -Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `p`. +Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `prob_threshold`. Call this function if you have a specific collection of parameters of which you would like to check local identifiability. @@ -254,7 +254,7 @@ If the type is `:ME`, states are not allowed to appear in the `funcs_to_check`. function assess_local_identifiability( ode::ODE{P}; funcs_to_check::Array{<:Any, 1} = Array{Any, 1}(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, type = :SE, trbasis = nothing, loglevel = Logging.Info, @@ -265,7 +265,7 @@ function assess_local_identifiability( return _assess_local_identifiability( ode, funcs_to_check = funcs_to_check, - p = p, + prob_threshold = prob_threshold, type = type, trbasis = trbasis, ) @@ -275,7 +275,7 @@ end function _assess_local_identifiability( ode::ODE{P}; funcs_to_check::Array{<:Any, 1} = Array{Any, 1}(), - p::Float64 = 0.99, + prob_threshold::Float64 = 0.99, type = :SE, trbasis = nothing, ) where {P <: MPolyElem{Nemo.fmpq}} @@ -307,7 +307,7 @@ function _assess_local_identifiability( d = max(d, df) h = max(h, hf) end - p_per_func = 1 - (1 - p) / length(funcs_to_check) + p_per_func = 1 - (1 - prob_threshold) / length(funcs_to_check) mu = ceil(1 / (1 - sqrt(p_per_func))) n = length(ode.x_vars) diff --git a/src/parametrizations.jl b/src/parametrizations.jl index aef46cc12..8d672ab3a 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -432,7 +432,7 @@ Returns a tuple (`new_ode`, `new_vars`, `implicit_relations`), such that: The function accepts the following optional arguments. - `seed`: A float in the range from 0 to 1, random seed (default is `seed = 42`). -- `p`: The probability of correctness (default is `p = 0.99`). +- `prob_threshold`: The probability of correctness (default is `prob_threshold = 0.99`). ## Example @@ -471,20 +471,20 @@ compared to the original one. """ function reparametrize_global( ode::ODE{P}; - p = 0.99, + prob_threshold = 0.99, seed = 42, loglevel = Logging.Info, ) where {P} restart_logging(loglevel = loglevel) with_logger(_si_logger[]) do - return _reparametrize_global(ode, p = p, seed = seed) + return _reparametrize_global(ode, prob_threshold = prob_threshold, seed = seed) end end -function _reparametrize_global(ode::ODE{P}; p = 0.99, seed = 42) where {P} +function _reparametrize_global(ode::ODE{P}; prob_threshold = 0.99, seed = 42) where {P} Random.seed!(seed) id_funcs = - find_identifiable_functions(ode, with_states = true, simplify = :strong, p = p) + find_identifiable_functions(ode, with_states = true, simplify = :strong, prob_threshold = prob_threshold) ode_ring = parent(ode) @assert base_ring(parent(first(id_funcs))) == ode_ring @info "Constructing a new parametrization" diff --git a/test/local_identifiability_me.jl b/test/local_identifiability_me.jl index b64c0bb3a..01365120c 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -213,7 +213,7 @@ end result = assess_local_identifiability( case[:ode], funcs_to_check = case[:funcs], - p = 0.932, + prob_threshold = 0.932, type = :ME, ) @test result == case[:correct] diff --git a/test/mtk_compat.jl b/test/mtk_compat.jl index 0927500b9..ccbdaf287 100644 --- a/test/mtk_compat.jl +++ b/test/mtk_compat.jl @@ -149,7 +149,7 @@ de; measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, - p = 0.99, + prob_threshold = 0.99, type = :ME, ), ) @@ -195,7 +195,7 @@ assess_local_identifiability( de; funcs_to_check = funcs_to_check, - p = 0.99, + prob_threshold = 0.99, type = :ME, ), ) @@ -232,7 +232,7 @@ de; measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, - p = 0.99, + prob_threshold = 0.99, type = :ME, ), ) From 8c30f652acf31e553866df6c34e705e1fa1ce900 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Mon, 8 Jan 2024 22:30:58 +0100 Subject: [PATCH 26/82] formatter... --- .../RationalFunctionField.jl | 21 +++++++++++++++---- src/StructuralIdentifiability.jl | 12 +++++++++-- src/discrete.jl | 8 +++++-- src/parametrizations.jl | 8 +++++-- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/RationalFunctionFields/RationalFunctionField.jl b/src/RationalFunctionFields/RationalFunctionField.jl index baedac597..cf38a9da4 100644 --- a/src/RationalFunctionFields/RationalFunctionField.jl +++ b/src/RationalFunctionFields/RationalFunctionField.jl @@ -158,18 +158,30 @@ function field_contains( return field_contains(field, fractions_to_dennums(ratfuncs), prob_threshold) end -function field_contains(field::RationalFunctionField{T}, polys::Vector{T}, prob_threshold) where {T} +function field_contains( + field::RationalFunctionField{T}, + polys::Vector{T}, + prob_threshold, +) where {T} id = one(parent(first(polys))) return field_contains(field, [[id, p] for p in polys], prob_threshold) end # ------------------------------------------------------------------------------ -function issubfield(F::RationalFunctionField{T}, E::RationalFunctionField{T}, prob_threshold) where {T} +function issubfield( + F::RationalFunctionField{T}, + E::RationalFunctionField{T}, + prob_threshold, +) where {T} return all(field_contains(E, F.dennums, prob_threshold)) end -function fields_equal(F::RationalFunctionField{T}, E::RationalFunctionField{T}, prob_threshold) where {T} +function fields_equal( + F::RationalFunctionField{T}, + E::RationalFunctionField{T}, + prob_threshold, +) where {T} new_p = 1 - (1 - prob_threshold) / 2 return issubfield(F, E, new_p) && issubfield(E, F, new_p) end @@ -558,7 +570,8 @@ Out of $(length(new_fracs)) fractions $(length(new_fracs_unique)) are syntactica runtime = @elapsed new_fracs = beautifuly_generators(RationalFunctionField(new_fracs_unique)) @debug "Checking inclusion with probability $prob_threshold" - runtime = @elapsed result = issubfield(rff, RationalFunctionField(new_fracs), prob_threshold) + runtime = + @elapsed result = issubfield(rff, RationalFunctionField(new_fracs), prob_threshold) _runtime_logger[:id_inclusion_check] = runtime if !result @warn "Field membership check failed. Error will follow." diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 40056e887..86c83993e 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -105,7 +105,11 @@ function assess_identifiability( restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do - return _assess_identifiability(ode, funcs_to_check = funcs_to_check, prob_threshold = prob_threshold) + return _assess_identifiability( + ode, + funcs_to_check = funcs_to_check, + prob_threshold = prob_threshold, + ) end end @@ -214,7 +218,11 @@ function _assess_identifiability( 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_, prob_threshold = prob_threshold) + result = _assess_identifiability( + ode, + funcs_to_check = funcs_to_check_, + prob_threshold = prob_threshold, + ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return out_dict diff --git a/src/discrete.jl b/src/discrete.jl index ed0e6020d..193d4dcb3 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -373,8 +373,12 @@ function _assess_local_identifiability( funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] known_ic_ = [eval_at_nemo(x, conversion) for x in known_ic] - result = - _assess_local_identifiability_discrete_aux(dds_aux, funcs_to_check_, known_ic_, prob_threshold) + result = _assess_local_identifiability_discrete_aux( + dds_aux, + funcs_to_check_, + known_ic_, + prob_threshold, + ) nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) if length(known_ic) > 0 diff --git a/src/parametrizations.jl b/src/parametrizations.jl index 8d672ab3a..a8b6e6ec4 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -483,8 +483,12 @@ end function _reparametrize_global(ode::ODE{P}; prob_threshold = 0.99, seed = 42) where {P} Random.seed!(seed) - id_funcs = - find_identifiable_functions(ode, with_states = true, simplify = :strong, prob_threshold = prob_threshold) + id_funcs = find_identifiable_functions( + ode, + with_states = true, + simplify = :strong, + prob_threshold = prob_threshold, + ) ode_ring = parent(ode) @assert base_ring(parent(first(id_funcs))) == ode_ring @info "Constructing a new parametrization" From 24869a959a36dce23e715d4bad01848205229685 Mon Sep 17 00:00:00 2001 From: CompatHelper Julia Date: Mon, 15 Jan 2024 00:08:47 +0000 Subject: [PATCH 27/82] CompatHelper: bump compat for ParamPunPam to 0.3, (keep existing compat) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 32bc21b06..c154105c0 100644 --- a/Project.toml +++ b/Project.toml @@ -38,7 +38,7 @@ Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "8.51" Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39" -ParamPunPam = "0.2" +ParamPunPam = "0.2, 0.3" PrecompileTools = "1.1, 1.2" Primes = "0.5" Random = "1.6, 1.7" From bc06a8e79c2f74b5f4d5d1e2ed141bfc9956ac1c Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 12:08:58 +0100 Subject: [PATCH 28/82] unbumping so that everything works --- Project.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 32bc21b06..360701482 100644 --- a/Project.toml +++ b/Project.toml @@ -26,18 +26,18 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [compat] -AbstractAlgebra = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34, 0.35" +AbstractAlgebra = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" Dates = "1.6, 1.7" -Groebner = "0.4, 0.5, 0.6" +Groebner = "0.4, 0.5" IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "8.51" -Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38, 0.39" +Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" ParamPunPam = "0.2" PrecompileTools = "1.1, 1.2" Primes = "0.5" From 94a8f59fce6eb564833cd9671ec6f4274e71a0c3 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 18:23:16 +0100 Subject: [PATCH 29/82] MTK extension draft --- Project.toml | 11 + ext/ModelingToolkitExt.jl | 579 +++++++++++++++++++++++++++++++ src/ODE.jl | 152 -------- src/StructuralIdentifiability.jl | 63 +--- src/discrete.jl | 102 ------ src/identifiable_functions.jl | 90 ----- src/local_identifiability.jl | 98 ------ src/precompile.jl | 24 +- src/util.jl | 65 ---- 9 files changed, 610 insertions(+), 574 deletions(-) create mode 100644 ext/ModelingToolkitExt.jl diff --git a/Project.toml b/Project.toml index 360701482..2ae345461 100644 --- a/Project.toml +++ b/Project.toml @@ -25,6 +25,14 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" +[weakdeps] +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" + +[weakdeps] +ModelingToolkitExt = "ModelingToolkit,SymbolicUtils,Symbolics" + [compat] AbstractAlgebra = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" BenchmarkTools = "1" @@ -53,6 +61,9 @@ julia = "1.6, 1.7" CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [targets] test = ["CPUSummary", "Test", "TestSetExtensions"] diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitExt.jl new file mode 100644 index 000000000..15c6777dc --- /dev/null +++ b/ext/ModelingToolkitExt.jl @@ -0,0 +1,579 @@ +module ModelingToolkitExt + +if !isdefined(Base, :get_extension) + using ModelingToolkit +else + using ..ModelingToolkit +end + +# ------------------------------------------------------------------------------ + +function eval_at_nemo(e::Num, vals::Dict) + e = Symbolics.value(e) + return eval_at_nemo(e, vals) +end + +function eval_at_nemo(e::SymbolicUtils.BasicSymbolic, vals::Dict) + if Symbolics.istree(e) + # checking if it is a function of the form x(t), a bit dirty + if length(Symbolics.arguments(e)) == 1 && "$(first(Symbolics.arguments(e)))" == "t" + return vals[e] + end + # checking if this is a vector entry like x(t)[1] + if Symbolics.operation(e) == getindex + return vals[e] + end + # otherwise, this is a term + args = map(a -> eval_at_nemo(a, vals), Symbolics.arguments(e)) + if Symbolics.operation(e) in (+, -, *) + return Symbolics.operation(e)(args...) + elseif isequal(Symbolics.operation(e), /) + return //(args...) + elseif isequal(Symbolics.operation(e), ^) + if args[2] >= 0 + return args[1]^args[2] + end + return 1 // args[1]^(-args[2]) + end + throw(Base.ArgumentError("Function $(Symbolics.operation(e)) is not supported")) + elseif e isa Symbolics.Symbolic + return get(vals, e, e) + end +end + +function eval_at_nemo(e::Union{Integer, Rational}, vals::Dict) + return e +end + +function eval_at_nemo(e::Union{Float16, Float32, Float64}, vals::Dict) + if isequal(e % 1, 0) + out = Int(e) + else + out = rationalize(e) + end + @warn "Floating point value $e will be converted to $(out)." + return out +end + +function get_measured_quantities(ode::ModelingToolkit.ODESystem) + if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(ode)) + @info "Measured quantities are not provided, trying to find the outputs in input ODE." + return filter( + eq -> (ModelingToolkit.isoutput(eq.lhs)), + ModelingToolkit.equations(ode), + ) + else + throw( + error( + "Measured quantities (output functions) were not provided and no outputs were found.", + ), + ) + end +end + +""" + function mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{ModelingToolkit.Equation}) + function mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{SymbolicUtils.BasicSymbolic}) + +Input: +- `de` - ModelingToolkit.AbstractTimeDependentSystem, a system for identifiability query +- `measured_quantities` - array of output functions (as equations of just functions) + +Output: +- `ODE` object containing required data for identifiability assessment +- `conversion` dictionary from the symbols in the input MTK model to the variable + involved in the produced `ODE` object +""" +function mtk_to_si( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{ModelingToolkit.Equation}, +) + return __mtk_to_si( + de, + [(replace(string(e.lhs), "(t)" => ""), e.rhs) for e in measured_quantities], + ) +end + +function mtk_to_si( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{<:Symbolics.Num}, +) + return __mtk_to_si( + de, + [("y$i", Symbolics.value(e)) for (i, e) in enumerate(measured_quantities)], + ) +end + +function mtk_to_si( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{<:SymbolicUtils.BasicSymbolic}, +) + return __mtk_to_si(de, [("y$i", e) for (i, e) in enumerate(measured_quantities)]) +end + +#------------------------------------------------------------------------------ +# old name kept for compatibility purposes + +function preprocess_ode( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{ModelingToolkit.Equation}, +) + @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." + return mtk_to_si(de, measured_quantities) +end + +function preprocess_ode( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{<:Symbolics.Num}, +) + @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." + return mtk_to_si(de, measured_quantities) +end + +function preprocess_ode( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{<:SymbolicUtils.BasicSymbolic}, +) + @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." + return mtk_to_si(de, measured_quantities) +end + +#------------------------------------------------------------------------------ +""" + function __mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{Tuple{String, SymbolicUtils.BasicSymbolic}}) + +Input: +- `de` - ModelingToolkit.AbstractTimeDependentSystem, a system for identifiability query +- `measured_quantities` - array of input function in the form (name, expression) + +Output: +- `ODE` object containing required data for identifiability assessment +- `conversion` dictionary from the symbols in the input MTK model to the variable + involved in the produced `ODE` object +""" +function __mtk_to_si( + de::ModelingToolkit.AbstractTimeDependentSystem, + measured_quantities::Array{<:Tuple{String, <:SymbolicUtils.BasicSymbolic}}, +) + polytype = StructuralIdentifiability.Nemo.fmpq_mpoly + fractype = StructuralIdentifiability.Nemo.Generic.Frac{polytype} + diff_eqs = + filter(eq -> !(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(de)) + # performing full structural simplification + if length(observed(de)) > 0 + rules = Dict(s.lhs => s.rhs for s in observed(de)) + while any([ + length(intersect(get_variables(r), keys(rules))) > 0 for r in values(rules) + ]) + rules = Dict(k => SymbolicUtils.substitute(v, rules) for (k, v) in rules) + end + diff_eqs = [SymbolicUtils.substitute(eq, rules) for eq in diff_eqs] + end + + y_functions = [each[2] for each in measured_quantities] + inputs = filter(v -> ModelingToolkit.isinput(v), ModelingToolkit.states(de)) + state_vars = filter( + s -> !(ModelingToolkit.isinput(s) || ModelingToolkit.isoutput(s)), + ModelingToolkit.states(de), + ) + params = ModelingToolkit.parameters(de) + t = ModelingToolkit.arguments(diff_eqs[1].lhs)[1] + params_from_measured_quantities = union( + [filter(s -> !istree(s), get_variables(y[2])) for y in measured_quantities]..., + ) + params = union(params, params_from_measured_quantities) + + input_symbols = vcat(state_vars, inputs, params) + generators = vcat(string.(input_symbols), [e[1] for e in measured_quantities]) + generators = map(g -> replace(g, "(t)" => ""), generators) + R, gens_ = Nemo.PolynomialRing(Nemo.QQ, generators) + y_vars = Vector{polytype}([str_to_var(e[1], R) for e in measured_quantities]) + symb2gens = Dict(input_symbols .=> gens_[1:length(input_symbols)]) + + x_vars = Vector{polytype}() + + state_eqn_dict = Dict{polytype, Union{polytype, fractype}}() + out_eqn_dict = Dict{polytype, Union{polytype, fractype}}() + + for i in 1:length(diff_eqs) + x = substitute(state_vars[i], symb2gens) + push!(x_vars, x) + if !(diff_eqs[i].rhs isa Number) + state_eqn_dict[x] = eval_at_nemo(diff_eqs[i].rhs, symb2gens) + else + state_eqn_dict[x] = R(diff_eqs[i].rhs) + end + end + for i in 1:length(measured_quantities) + out_eqn_dict[y_vars[i]] = eval_at_nemo(measured_quantities[i][2], symb2gens) + end + + inputs_ = [substitute(each, symb2gens) for each in inputs] + if isequal(length(inputs_), 0) + inputs_ = Vector{polytype}() + end + return ( + StructuralIdentifiability.ODE{polytype}( + x_vars, + y_vars, + state_eqn_dict, + out_eqn_dict, + inputs_, + ), + symb2gens, + ) +end +# ----------------------------------------------------------------------------- +""" + function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) + +Input: +- `ode` - the ODESystem object from ModelingToolkit +- `measured_quantities` - the measurable outputs of the model +- `funcs_to_check` - functions of parameters for which to check identifiability +- `prob_threshold` - probability of correctness +- `type` - identifiability type (`:SE` for single-experiment, `:ME` for multi-experiment) +- `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) + +Output: +- 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 + +The result is correct with probability at least `prob_threshold`. + +`type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability). +The return value is a tuple consisting of the array of bools and the number of experiments to be performed. +""" +function assess_local_identifiability( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = Array{}[], + prob_threshold::Float64 = 0.99, + type = :SE, + loglevel = Logging.Info, +) + restart_logging(loglevel = loglevel) + with_logger(_si_logger[]) do + return _assess_local_identifiability( + ode, + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = prob_threshold, + type = type, + ) + end +end + +@timeit _to function _assess_local_identifiability( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = Array{}[], + prob_threshold::Float64 = 0.99, + type = :SE, +) + if length(measured_quantities) == 0 + if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(ode)) + @info "Measured quantities are not provided, trying to find the outputs in input ODE." + measured_quantities = filter( + eq -> (ModelingToolkit.isoutput(eq.lhs)), + ModelingToolkit.equations(ode), + ) + else + throw( + error( + "Measured quantities (output functions) were not provided and no outputs were found.", + ), + ) + end + end + if length(funcs_to_check) == 0 + funcs_to_check = vcat( + [e for e in ModelingToolkit.states(ode) if !ModelingToolkit.isoutput(e)], + ModelingToolkit.parameters(ode), + ) + end + ode, conversion = mtk_to_si(ode, measured_quantities) + funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] + + if isequal(type, :SE) + result = _assess_local_identifiability( + ode, + funcs_to_check = funcs_to_check_, + prob_threshold = prob_threshold, + type = type, + ) + nemo2mtk = Dict(funcs_to_check_ .=> 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( + ode, + funcs_to_check = funcs_to_check_, + prob_threshold = prob_threshold, + type = type, + ) + nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) + out_dict = + OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + return (out_dict, bd) + end +end + +# ------------------------------------------------------------------------------ + +""" + assess_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=[], prob_threshold = 0.99, loglevel=Logging.Info) + +Input: +- `ode` - the ModelingToolkit.ODESystem object that defines the model +- `measured_quantities` - the output functions of the model +- `funcs_to_check` - functions of parameters for which to check the identifiability +- `prob_threshold` - probability of correctness. +- `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) + +Assesses identifiability (both local and global) of a given ODE model (parameters detected automatically). The result is guaranteed to be correct with the probability +at least `prob_threshold`. +""" +function StructuralIdentifiability.assess_identifiability( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = [], + prob_threshold = 0.99, + loglevel = Logging.Info, +) + restart_logging(loglevel = loglevel) + with_logger(_si_logger[]) do + return _assess_identifiability( + ode, + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = prob_threshold, + ) + end +end + +function StructuralIdentifiability._assess_identifiability( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = [], + prob_threshold = 0.99, +) + if isempty(measured_quantities) + measured_quantities = get_measured_quantities(ode) + end + + 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.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_, + prob_threshold = prob_threshold, + ) + nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) + out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) + return out_dict +end + +# ------------------------------------------------------------------------------ + +""" + function assess_local_identifiability( + dds::ModelingToolkit.DiscreteSystem; + measured_quantities=Array{ModelingToolkit.Equation}[], + funcs_to_check=Array{}[], + known_ic=Array{}[], + prob_threshold::Float64=0.99) + +Input: +- `dds` - the DiscreteSystem object from ModelingToolkit (with **difference** operator in the right-hand side) +- `measured_quantities` - the measurable outputs of the model +- `funcs_to_check` - functions of parameters for which to check identifiability (all parameters and states if not specified) +- `known_ic` - functions (of states and parameter) whose initial conditions are assumed to be known +- `prob_threshold` - probability of correctness + +Output: +- the result is an (ordered) dictionary from each function to to boolean; + +The result is correct with probability at least `prob_threshold`. +""" +function StructuralIdentifiability.assess_local_identifiability( + dds::ModelingToolkit.DiscreteSystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = Array{}[], + known_ic = Array{}[], + prob_threshold::Float64 = 0.99, + loglevel = Logging.Info, +) + restart_logging(loglevel = loglevel) + with_logger(_si_logger[]) do + return _assess_local_identifiability( + dds, + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + known_ic = known_ic, + prob_threshold = prob_threshold, + ) + end +end + +function StructuralIdentifiability._assess_local_identifiability( + dds::ModelingToolkit.DiscreteSystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + funcs_to_check = Array{}[], + known_ic = Array{}[], + prob_threshold::Float64 = 0.99, +) + if length(measured_quantities) == 0 + if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(dds)) + @info "Measured quantities are not provided, trying to find the outputs in input dynamical system." + measured_quantities = filter( + eq -> (ModelingToolkit.isoutput(eq.lhs)), + ModelingToolkit.equations(dds), + ) + else + throw( + error( + "Measured quantities (output functions) were not provided and no outputs were found.", + ), + ) + end + end + + # Converting the finite difference operator in the right-hand side to + # the corresponding shift operator + eqs = filter(eq -> !(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(dds)) + deltas = [Symbolics.operation(e.lhs).dt for e in eqs] + @assert length(Set(deltas)) == 1 + eqs_shift = [e.lhs ~ e.rhs + first(Symbolics.arguments(e.lhs)) for e in eqs] + dds_shift = DiscreteSystem(eqs_shift, name = gensym()) + @debug "System transformed from difference to shift: $dds_shift" + + dds_aux, conversion = mtk_to_si(dds_shift, measured_quantities) + if length(funcs_to_check) == 0 + params = parameters(dds) + params_from_measured_quantities = union( + [filter(s -> !istree(s), get_variables(y)) for y in measured_quantities]..., + ) + funcs_to_check = vcat( + [x for x in states(dds) if conversion[x] in dds_aux.x_vars], + union(params, params_from_measured_quantities), + ) + end + funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] + known_ic_ = [eval_at_nemo(x, conversion) for x in known_ic] + + result = _assess_local_identifiability_discrete_aux( + dds_aux, + funcs_to_check_, + known_ic_, + prob_threshold, + ) + nemo2mtk = Dict(funcs_to_check_ .=> 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 !" + out_dict = OrderedDict(substitute(k, Dict(t => 0)) => v for (k, v) in out_dict) + end + return out_dict +end +# ------------------------------------------------------------------------------ + +""" + find_identifiable_functions(ode::ModelingToolkit.ODESystem; measured_quantities=[], options...) + +Finds all functions of parameters/states that are identifiable in the given ODE +system. + +## Options + +This functions takes the following optional arguments: +- `measured_quantities` - the output functions of the model. +- `loglevel` - the verbosity of the logging + (can be Logging.Error, Logging.Warn, Logging.Info, Logging.Debug) + +## Example + +```jldoctest +using StructuralIdentifiability +using ModelingToolkit + +@parameters a01 a21 a12 +@variables t x0(t) x1(t) y1(t) [output = true] +D = Differential(t) + +eqs = [ + D(x0) ~ -(a01 + a21) * x0 + a12 * x1, + D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0 +] +de = ODESystem(eqs, t, name = :Test) + +find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) + +# prints +2-element Vector{Num}: + a01*a12 + a01 + a12 + a21 +``` +""" +function find_identifiable_functions( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + prob_threshold::Float64 = 0.99, + seed = 42, + with_states = false, + simplify = :standard, + rational_interpolator = :VanDerHoevenLecerf, + loglevel = Logging.Info, +) + restart_logging(loglevel = loglevel) + reset_timings() + with_logger(_si_logger[]) do + return _find_identifiable_functions( + ode, + measured_quantities = measured_quantities, + prob_threshold = prob_threshold, + seed = seed, + with_states = with_states, + simplify = simplify, + rational_interpolator = rational_interpolator, + ) + end +end + +function _find_identifiable_functions( + ode::ModelingToolkit.ODESystem; + measured_quantities = Array{ModelingToolkit.Equation}[], + prob_threshold::Float64 = 0.99, + seed = 42, + with_states = false, + simplify = :standard, + rational_interpolator = :VanDerHoevenLecerf, +) + Random.seed!(seed) + if isempty(measured_quantities) + measured_quantities = get_measured_quantities(ode) + end + ode, conversion = mtk_to_si(ode, measured_quantities) + result = _find_identifiable_functions( + ode, + simplify = simplify, + prob_threshold = prob_threshold, + seed = seed, + with_states = with_states, + rational_interpolator = rational_interpolator, + ) + result = [parent_ring_change(f, ode.poly_ring) for f in result] + nemo2mtk = Dict(v => Num(k) for (k, v) in conversion) + out_funcs = [eval_at_dict(func, nemo2mtk) for func in result] + return out_funcs +end + +end diff --git a/src/ODE.jl b/src/ODE.jl index 625c0d21e..8481f8d64 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -515,155 +515,3 @@ function Base.show(io::IO, ode::ODE) end #------------------------------------------------------------------------------ -""" - function mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{ModelingToolkit.Equation}) - function mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{SymbolicUtils.BasicSymbolic}) - -Input: -- `de` - ModelingToolkit.AbstractTimeDependentSystem, a system for identifiability query -- `measured_quantities` - array of output functions (as equations of just functions) - -Output: -- `ODE` object containing required data for identifiability assessment -- `conversion` dictionary from the symbols in the input MTK model to the variable - involved in the produced `ODE` object -""" -function mtk_to_si( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{ModelingToolkit.Equation}, -) - return __mtk_to_si( - de, - [(replace(string(e.lhs), "(t)" => ""), e.rhs) for e in measured_quantities], - ) -end - -function mtk_to_si( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{<:Symbolics.Num}, -) - return __mtk_to_si( - de, - [("y$i", Symbolics.value(e)) for (i, e) in enumerate(measured_quantities)], - ) -end - -function mtk_to_si( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{<:SymbolicUtils.BasicSymbolic}, -) - return __mtk_to_si(de, [("y$i", e) for (i, e) in enumerate(measured_quantities)]) -end - -#------------------------------------------------------------------------------ -# old name kept for compatibility purposes - -function preprocess_ode( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{ModelingToolkit.Equation}, -) - @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." - return mtk_to_si(de, measured_quantities) -end - -function preprocess_ode( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{<:Symbolics.Num}, -) - @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." - return mtk_to_si(de, measured_quantities) -end - -function preprocess_ode( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{<:SymbolicUtils.BasicSymbolic}, -) - @warn "Function `preprocess_ode` has been renamed to `mtk_to_si`. The old name can be still used but will disappear in the future releases." - return mtk_to_si(de, measured_quantities) -end - -#------------------------------------------------------------------------------ -""" - function __mtk_to_si(de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{Tuple{String, SymbolicUtils.BasicSymbolic}}) - -Input: -- `de` - ModelingToolkit.AbstractTimeDependentSystem, a system for identifiability query -- `measured_quantities` - array of input function in the form (name, expression) - -Output: -- `ODE` object containing required data for identifiability assessment -- `conversion` dictionary from the symbols in the input MTK model to the variable - involved in the produced `ODE` object -""" -function __mtk_to_si( - de::ModelingToolkit.AbstractTimeDependentSystem, - measured_quantities::Array{<:Tuple{String, <:SymbolicUtils.BasicSymbolic}}, -) - polytype = StructuralIdentifiability.Nemo.fmpq_mpoly - fractype = StructuralIdentifiability.Nemo.Generic.Frac{polytype} - diff_eqs = - filter(eq -> !(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(de)) - # performing full structural simplification - if length(observed(de)) > 0 - rules = Dict(s.lhs => s.rhs for s in observed(de)) - while any([ - length(intersect(get_variables(r), keys(rules))) > 0 for r in values(rules) - ]) - rules = Dict(k => SymbolicUtils.substitute(v, rules) for (k, v) in rules) - end - diff_eqs = [SymbolicUtils.substitute(eq, rules) for eq in diff_eqs] - end - - y_functions = [each[2] for each in measured_quantities] - inputs = filter(v -> ModelingToolkit.isinput(v), ModelingToolkit.states(de)) - state_vars = filter( - s -> !(ModelingToolkit.isinput(s) || ModelingToolkit.isoutput(s)), - ModelingToolkit.states(de), - ) - params = ModelingToolkit.parameters(de) - t = ModelingToolkit.arguments(diff_eqs[1].lhs)[1] - params_from_measured_quantities = union( - [filter(s -> !istree(s), get_variables(y[2])) for y in measured_quantities]..., - ) - params = union(params, params_from_measured_quantities) - - input_symbols = vcat(state_vars, inputs, params) - generators = vcat(string.(input_symbols), [e[1] for e in measured_quantities]) - generators = map(g -> replace(g, "(t)" => ""), generators) - R, gens_ = Nemo.PolynomialRing(Nemo.QQ, generators) - y_vars = Vector{polytype}([str_to_var(e[1], R) for e in measured_quantities]) - symb2gens = Dict(input_symbols .=> gens_[1:length(input_symbols)]) - - x_vars = Vector{polytype}() - - state_eqn_dict = Dict{polytype, Union{polytype, fractype}}() - out_eqn_dict = Dict{polytype, Union{polytype, fractype}}() - - for i in 1:length(diff_eqs) - x = substitute(state_vars[i], symb2gens) - push!(x_vars, x) - if !(diff_eqs[i].rhs isa Number) - state_eqn_dict[x] = eval_at_nemo(diff_eqs[i].rhs, symb2gens) - else - state_eqn_dict[x] = R(diff_eqs[i].rhs) - end - end - for i in 1:length(measured_quantities) - out_eqn_dict[y_vars[i]] = eval_at_nemo(measured_quantities[i][2], symb2gens) - end - - inputs_ = [substitute(each, symb2gens) for each in inputs] - if isequal(length(inputs_), 0) - inputs_ = Vector{polytype}() - end - return ( - StructuralIdentifiability.ODE{polytype}( - x_vars, - y_vars, - state_eqn_dict, - out_eqn_dict, - inputs_, - ), - symb2gens, - ) -end diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 86c83993e..601375d1d 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -20,8 +20,6 @@ using ParamPunPam using ParamPunPam: reduce_mod_p!, specialize_mod_p, AbstractBlackboxIdeal ParamPunPam.enable_progressbar(false) -using ModelingToolkit - # defining a model export ODE, @ODEmodel, mtk_to_si @@ -170,64 +168,19 @@ function _assess_identifiability( return result end -""" - assess_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=[], prob_threshold = 0.99, loglevel=Logging.Info) - -Input: -- `ode` - the ModelingToolkit.ODESystem object that defines the model -- `measured_quantities` - the output functions of the model -- `funcs_to_check` - functions of parameters for which to check the identifiability -- `prob_threshold` - probability of correctness. -- `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) - -Assesses identifiability (both local and global) of a given ODE model (parameters detected automatically). The result is guaranteed to be correct with the probability -at least `prob_threshold`. -""" -function assess_identifiability( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = [], - prob_threshold = 0.99, - loglevel = Logging.Info, -) - restart_logging(loglevel = loglevel) - with_logger(_si_logger[]) do - return _assess_identifiability( - ode, - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = prob_threshold, - ) - end +@static if !isdefined(Base, :get_extension) + using Requires end -function _assess_identifiability( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = [], - prob_threshold = 0.99, -) - if isempty(measured_quantities) - measured_quantities = get_measured_quantities(ode) - end - - 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.x_vars..., ode.parameters...]] +@static if !isdefined(Base,:get_extension) + function __init__() + @require ModelingToolkit="961ee093-0014-501f-94e3-6117800e7a78" begin + include("../ext/ModelingToolkitExt.jl") + end 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_, - prob_threshold = prob_threshold, - ) - nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) - return out_dict end + using PrecompileTools include("precompile.jl") diff --git a/src/discrete.jl b/src/discrete.jl index 193d4dcb3..7cb92ea31 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -286,105 +286,3 @@ function _assess_local_identifiability_discrete_aux( end # ------------------------------------------------------------------------------ - -""" - function assess_local_identifiability( - dds::ModelingToolkit.DiscreteSystem; - measured_quantities=Array{ModelingToolkit.Equation}[], - funcs_to_check=Array{}[], - known_ic=Array{}[], - prob_threshold::Float64=0.99) - -Input: -- `dds` - the DiscreteSystem object from ModelingToolkit (with **difference** operator in the right-hand side) -- `measured_quantities` - the measurable outputs of the model -- `funcs_to_check` - functions of parameters for which to check identifiability (all parameters and states if not specified) -- `known_ic` - functions (of states and parameter) whose initial conditions are assumed to be known -- `prob_threshold` - probability of correctness - -Output: -- the result is an (ordered) dictionary from each function to to boolean; - -The result is correct with probability at least `prob_threshold`. -""" -function assess_local_identifiability( - dds::ModelingToolkit.DiscreteSystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = Array{}[], - known_ic = Array{}[], - prob_threshold::Float64 = 0.99, - loglevel = Logging.Info, -) - restart_logging(loglevel = loglevel) - with_logger(_si_logger[]) do - return _assess_local_identifiability( - dds, - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - known_ic = known_ic, - prob_threshold = prob_threshold, - ) - end -end - -function _assess_local_identifiability( - dds::ModelingToolkit.DiscreteSystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = Array{}[], - known_ic = Array{}[], - prob_threshold::Float64 = 0.99, -) - if length(measured_quantities) == 0 - if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(dds)) - @info "Measured quantities are not provided, trying to find the outputs in input dynamical system." - measured_quantities = filter( - eq -> (ModelingToolkit.isoutput(eq.lhs)), - ModelingToolkit.equations(dds), - ) - else - throw( - error( - "Measured quantities (output functions) were not provided and no outputs were found.", - ), - ) - end - end - - # Converting the finite difference operator in the right-hand side to - # the corresponding shift operator - eqs = filter(eq -> !(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(dds)) - deltas = [Symbolics.operation(e.lhs).dt for e in eqs] - @assert length(Set(deltas)) == 1 - eqs_shift = [e.lhs ~ e.rhs + first(Symbolics.arguments(e.lhs)) for e in eqs] - dds_shift = DiscreteSystem(eqs_shift, name = gensym()) - @debug "System transformed from difference to shift: $dds_shift" - - dds_aux, conversion = mtk_to_si(dds_shift, measured_quantities) - if length(funcs_to_check) == 0 - params = parameters(dds) - params_from_measured_quantities = union( - [filter(s -> !istree(s), get_variables(y)) for y in measured_quantities]..., - ) - funcs_to_check = vcat( - [x for x in states(dds) if conversion[x] in dds_aux.x_vars], - union(params, params_from_measured_quantities), - ) - end - funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] - known_ic_ = [eval_at_nemo(x, conversion) for x in known_ic] - - result = _assess_local_identifiability_discrete_aux( - dds_aux, - funcs_to_check_, - known_ic_, - prob_threshold, - ) - nemo2mtk = Dict(funcs_to_check_ .=> 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 !" - out_dict = OrderedDict(substitute(k, Dict(t => 0)) => v for (k, v) in out_dict) - end - return out_dict -end -# ------------------------------------------------------------------------------ diff --git a/src/identifiable_functions.jl b/src/identifiable_functions.jl index 79db0ffbd..d84e0f14e 100644 --- a/src/identifiable_functions.jl +++ b/src/identifiable_functions.jl @@ -117,93 +117,3 @@ function _find_identifiable_functions( return id_funcs_fracs end - -""" - find_identifiable_functions(ode::ModelingToolkit.ODESystem; measured_quantities=[], options...) - -Finds all functions of parameters/states that are identifiable in the given ODE -system. - -## Options - -This functions takes the following optional arguments: -- `measured_quantities` - the output functions of the model. -- `loglevel` - the verbosity of the logging - (can be Logging.Error, Logging.Warn, Logging.Info, Logging.Debug) - -## Example - -```jldoctest -using StructuralIdentifiability -using ModelingToolkit - -@parameters a01 a21 a12 -@variables t x0(t) x1(t) y1(t) [output = true] -D = Differential(t) - -eqs = [ - D(x0) ~ -(a01 + a21) * x0 + a12 * x1, - D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0 -] -de = ODESystem(eqs, t, name = :Test) - -find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) - -# prints -2-element Vector{Num}: - a01*a12 - a01 + a12 + a21 -``` -""" -function find_identifiable_functions( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - prob_threshold::Float64 = 0.99, - seed = 42, - with_states = false, - simplify = :standard, - rational_interpolator = :VanDerHoevenLecerf, - loglevel = Logging.Info, -) - restart_logging(loglevel = loglevel) - reset_timings() - with_logger(_si_logger[]) do - return _find_identifiable_functions( - ode, - measured_quantities = measured_quantities, - prob_threshold = prob_threshold, - seed = seed, - with_states = with_states, - simplify = simplify, - rational_interpolator = rational_interpolator, - ) - end -end - -function _find_identifiable_functions( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - prob_threshold::Float64 = 0.99, - seed = 42, - with_states = false, - simplify = :standard, - rational_interpolator = :VanDerHoevenLecerf, -) - Random.seed!(seed) - if isempty(measured_quantities) - measured_quantities = get_measured_quantities(ode) - end - ode, conversion = mtk_to_si(ode, measured_quantities) - result = _find_identifiable_functions( - ode, - simplify = simplify, - prob_threshold = prob_threshold, - seed = seed, - with_states = with_states, - rational_interpolator = rational_interpolator, - ) - result = [parent_ring_change(f, ode.poly_ring) for f in result] - nemo2mtk = Dict(v => Num(k) for (k, v) in conversion) - out_funcs = [eval_at_dict(func, nemo2mtk) for func in result] - return out_funcs -end diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index ae2a3cbba..d39800c2d 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -141,104 +141,6 @@ function get_degree_and_coeffsize(f::Generic.Frac{<:MPolyElem{Nemo.fmpq}}) return (max(num_deg, den_deg), max(num_coef, den_coef)) end -# ------------------------------------------------------------------------------ -""" - function assess_local_identifiability(ode::ModelingToolkit.ODESystem; measured_quantities=Array{ModelingToolkit.Equation}[], funcs_to_check=Array{}[], prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) - -Input: -- `ode` - the ODESystem object from ModelingToolkit -- `measured_quantities` - the measurable outputs of the model -- `funcs_to_check` - functions of parameters for which to check identifiability -- `prob_threshold` - probability of correctness -- `type` - identifiability type (`:SE` for single-experiment, `:ME` for multi-experiment) -- `loglevel` - the minimal level of log messages to display (`Logging.Info` by default) - -Output: -- 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 - -The result is correct with probability at least `prob_threshold`. - -`type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability). -The return value is a tuple consisting of the array of bools and the number of experiments to be performed. -""" -function assess_local_identifiability( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = Array{}[], - prob_threshold::Float64 = 0.99, - type = :SE, - loglevel = Logging.Info, -) - restart_logging(loglevel = loglevel) - with_logger(_si_logger[]) do - return _assess_local_identifiability( - ode, - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = prob_threshold, - type = type, - ) - end -end - -@timeit _to function _assess_local_identifiability( - ode::ModelingToolkit.ODESystem; - measured_quantities = Array{ModelingToolkit.Equation}[], - funcs_to_check = Array{}[], - prob_threshold::Float64 = 0.99, - type = :SE, -) - if length(measured_quantities) == 0 - if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(ode)) - @info "Measured quantities are not provided, trying to find the outputs in input ODE." - measured_quantities = filter( - eq -> (ModelingToolkit.isoutput(eq.lhs)), - ModelingToolkit.equations(ode), - ) - else - throw( - error( - "Measured quantities (output functions) were not provided and no outputs were found.", - ), - ) - end - end - if length(funcs_to_check) == 0 - funcs_to_check = vcat( - [e for e in ModelingToolkit.states(ode) if !ModelingToolkit.isoutput(e)], - ModelingToolkit.parameters(ode), - ) - end - ode, conversion = mtk_to_si(ode, measured_quantities) - funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] - - if isequal(type, :SE) - result = _assess_local_identifiability( - ode, - funcs_to_check = funcs_to_check_, - prob_threshold = prob_threshold, - type = type, - ) - nemo2mtk = Dict(funcs_to_check_ .=> 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( - ode, - funcs_to_check = funcs_to_check_, - prob_threshold = prob_threshold, - type = type, - ) - nemo2mtk = Dict(funcs_to_check_ .=> funcs_to_check) - out_dict = - OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) - return (out_dict, bd) - end -end # ------------------------------------------------------------------------------ """ diff --git a/src/precompile.jl b/src/precompile.jl index fb2c2094f..f40e111da 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -3,12 +3,12 @@ # Putting some things in `@setup_workload` instead of `@compile_workload` can reduce the size of the # precompile file and potentially make loading faster. using Logging - using ModelingToolkit - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y(t) - D = Differential(t) - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] - de = ODESystem(eqs, t, name = :Test) + #using ModelingToolkit + #@parameters a01 a21 a12 + #@variables t x0(t) x1(t) y(t) + #D = Differential(t) + #eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] + #de = ODESystem(eqs, t, name = :Test) @compile_workload begin restart_logging(loglevel = Logging.Warn) with_logger(_si_logger[]) do @@ -21,12 +21,12 @@ y(t) = x2(t) ) assess_identifiability(ode, loglevel = Logging.Warn) - assess_identifiability(de; measured_quantities = [x0], loglevel = Logging.Warn) - assess_identifiability( - de; - measured_quantities = [y ~ x0], - loglevel = Logging.Warn, - ) + #assess_identifiability(de; measured_quantities = [x0], loglevel = Logging.Warn) + #assess_identifiability( + # de; + # measured_quantities = [y ~ x0], + # loglevel = Logging.Warn, + #) find_identifiable_functions(ode, with_states = true, loglevel = Logging.Warn) end restart_logging(loglevel = Logging.Info) diff --git a/src/util.jl b/src/util.jl index a55d5571c..b6cb29d83 100644 --- a/src/util.jl +++ b/src/util.jl @@ -478,55 +478,6 @@ end # ------------------------------------------------------------------------------ -function eval_at_nemo(e::Num, vals::Dict) - e = Symbolics.value(e) - return eval_at_nemo(e, vals) -end - -function eval_at_nemo(e::SymbolicUtils.BasicSymbolic, vals::Dict) - if Symbolics.istree(e) - # checking if it is a function of the form x(t), a bit dirty - if length(Symbolics.arguments(e)) == 1 && "$(first(Symbolics.arguments(e)))" == "t" - return vals[e] - end - # checking if this is a vector entry like x(t)[1] - if Symbolics.operation(e) == getindex - return vals[e] - end - # otherwise, this is a term - args = map(a -> eval_at_nemo(a, vals), Symbolics.arguments(e)) - if Symbolics.operation(e) in (+, -, *) - return Symbolics.operation(e)(args...) - elseif isequal(Symbolics.operation(e), /) - return //(args...) - elseif isequal(Symbolics.operation(e), ^) - if args[2] >= 0 - return args[1]^args[2] - end - return 1 // args[1]^(-args[2]) - end - throw(Base.ArgumentError("Function $(Symbolics.operation(e)) is not supported")) - elseif e isa Symbolics.Symbolic - return get(vals, e, e) - end -end - -function eval_at_nemo(e::Union{Integer, Rational}, vals::Dict) - return e -end - -function eval_at_nemo(e::Union{Float16, Float32, Float64}, vals::Dict) - if isequal(e % 1, 0) - out = Int(e) - else - out = rationalize(e) - end - @warn "Floating point value $e will be converted to $(out)." - return out -end - -# ----------------------------------------------------------------------------- - """ decompose_derivative(varname, prefixes) @@ -563,19 +514,3 @@ function difforder(diffpoly::MPolyElem, prefix::String) end return max(orders...) end - -function get_measured_quantities(ode::ModelingToolkit.ODESystem) - if any(ModelingToolkit.isoutput(eq.lhs) for eq in ModelingToolkit.equations(ode)) - @info "Measured quantities are not provided, trying to find the outputs in input ODE." - return filter( - eq -> (ModelingToolkit.isoutput(eq.lhs)), - ModelingToolkit.equations(ode), - ) - else - throw( - error( - "Measured quantities (output functions) were not provided and no outputs were found.", - ), - ) - end -end From ff1daf3a77ece62e810f07c8f60fdecd8f1f2f6a Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 19:53:12 +0100 Subject: [PATCH 30/82] simplifying precompilation --- src/precompile.jl | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/precompile.jl b/src/precompile.jl index f40e111da..8930129e9 100644 --- a/src/precompile.jl +++ b/src/precompile.jl @@ -3,12 +3,6 @@ # Putting some things in `@setup_workload` instead of `@compile_workload` can reduce the size of the # precompile file and potentially make loading faster. using Logging - #using ModelingToolkit - #@parameters a01 a21 a12 - #@variables t x0(t) x1(t) y(t) - #D = Differential(t) - #eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] - #de = ODESystem(eqs, t, name = :Test) @compile_workload begin restart_logging(loglevel = Logging.Warn) with_logger(_si_logger[]) do @@ -21,12 +15,6 @@ y(t) = x2(t) ) assess_identifiability(ode, loglevel = Logging.Warn) - #assess_identifiability(de; measured_quantities = [x0], loglevel = Logging.Warn) - #assess_identifiability( - # de; - # measured_quantities = [y ~ x0], - # loglevel = Logging.Warn, - #) find_identifiable_functions(ode, with_states = true, loglevel = Logging.Warn) end restart_logging(loglevel = Logging.Info) From e60889bd2b5532a2a6e756bd94aa90b519b8631d Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 22:04:11 +0100 Subject: [PATCH 31/82] testing + GF --- Project.toml | 14 +- src/ODE.jl | 6 +- .../RationalFunctionField.jl | 2 +- src/RationalFunctionFields/normalforms.jl | 2 +- src/local_identifiability.jl | 2 +- src/wronskian.jl | 2 +- test/differentiate_output.jl | 2 +- .../modelingtoolkit.jl} | 250 ++++++++++++++++++ test/local_identifiability_discrete.jl | 243 ----------------- test/ode_ps_solution.jl | 2 +- test/parent_ring_change.jl | 2 +- test/ps_inverse.jl | 2 +- test/ps_matrix_homlinear.jl | 4 +- test/ps_matrix_linear.jl | 2 +- test/runtests.jl | 3 +- 15 files changed, 273 insertions(+), 265 deletions(-) rename test/{mtk_compat.jl => extensions/modelingtoolkit.jl} (67%) delete mode 100644 test/local_identifiability_discrete.jl diff --git a/Project.toml b/Project.toml index 2ae345461..3bc8b888d 100644 --- a/Project.toml +++ b/Project.toml @@ -30,23 +30,23 @@ ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" -[weakdeps] -ModelingToolkitExt = "ModelingToolkit,SymbolicUtils,Symbolics" +[extensions] +ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils" ,"Symbolics"] [compat] -AbstractAlgebra = "0.27, 0.28, 0.29, 0.30, 0.31, 0.32, 0.33, 0.34" +AbstractAlgebra = "0.34.5, 0.35" BenchmarkTools = "1" Combinatorics = "1" DataStructures = "0.18" Dates = "1.6, 1.7" -Groebner = "0.4, 0.5" +Groebner = "0.6.3" IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" ModelingToolkit = "8.51" -Nemo = "0.32, 0.33, 0.34, 0.35, 0.36, 0.37, 0.38" -ParamPunPam = "0.2" +Nemo = "0.38.3, 0.39" +ParamPunPam = "0.3.1" PrecompileTools = "1.1, 1.2" Primes = "0.5" Random = "1.6, 1.7" @@ -66,4 +66,4 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [targets] -test = ["CPUSummary", "Test", "TestSetExtensions"] +test = ["CPUSummary", "Test", "TestSetExtensions", "ModelingToolkit"] diff --git a/src/ODE.jl b/src/ODE.jl index 8481f8d64..1113a204f 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -216,10 +216,10 @@ Reduces a polynomial/rational function over Q modulo p function _reduce_mod_p(poly::fmpq_mpoly, p::Int) den = denominator(poly) num = change_base_ring(Nemo.ZZ, den * poly) - if Nemo.GF(p)(den) == 0 + if Nemo.Native.GF(p)(den) == 0 throw(Base.ArgumentError("Prime $p divides the denominator of $poly")) end - return change_base_ring(Nemo.GF(p), num) * (1 // Nemo.GF(p)(den)) + return change_base_ring(Nemo.Native.GF(p), num) * (1 // Nemo.Native.GF(p)(den)) end function _reduce_mod_p(rat::Generic.Frac{fmpq_mpoly}, p::Int) @@ -240,7 +240,7 @@ Output: the reduction mod p, throws an exception if p divides one of the denomin """ function reduce_ode_mod_p(ode::ODE{<:MPolyElem{Nemo.fmpq}}, p::Int) new_ring, new_vars = - Nemo.PolynomialRing(Nemo.GF(p), map(var_to_str, gens(ode.poly_ring))) + Nemo.PolynomialRing(Nemo.Native.GF(p), map(var_to_str, gens(ode.poly_ring))) new_type = typeof(new_vars[1]) new_inputs = map(u -> switch_ring(u, new_ring), ode.u_vars) new_x = map(x -> switch_ring(x, new_ring), ode.x_vars) diff --git a/src/RationalFunctionFields/RationalFunctionField.jl b/src/RationalFunctionFields/RationalFunctionField.jl index cf38a9da4..100252e17 100644 --- a/src/RationalFunctionFields/RationalFunctionField.jl +++ b/src/RationalFunctionFields/RationalFunctionField.jl @@ -194,7 +194,7 @@ end ) where {T} mqs_generators = generators.mqs mqs_tobereduced = tobereduced.mqs - ff = Nemo.GF(2^31 - 1) + ff = Nemo.Native.GF(2^31 - 1) reduce_mod_p!(mqs_generators, ff) reduce_mod_p!(mqs_tobereduced, ff) param_ring = ParamPunPam.parent_params(mqs_generators) diff --git a/src/RationalFunctionFields/normalforms.jl b/src/RationalFunctionFields/normalforms.jl index 80c6329c0..530518487 100644 --- a/src/RationalFunctionFields/normalforms.jl +++ b/src/RationalFunctionFields/normalforms.jl @@ -301,7 +301,7 @@ is not specified but is assumed to be close to 1. ring_param = ParamPunPam.parent_params(mqs) xs_param = gens(ring_param) nparams = nvars(ring_param) - finite_field = Nemo.GF(2^30 + 3) + finite_field = Nemo.Native.GF(2^30 + 3) ParamPunPam.reduce_mod_p!(mqs, finite_field) @info "Computing normal forms of degree $up_to_degree in $nparams variables" @debug """Variables ($nparams in total): $xs_param diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index d39800c2d..da6909777 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -228,7 +228,7 @@ function _assess_local_identifiability( Dprime = max(Dprime, 1.0) prime = Primes.nextprime(Int(ceil(2 * mu * Dprime))) @debug "The prime is $prime" - F = Nemo.GF(prime) + F = Nemo.Native.GF(prime) @debug "Extending the model" ode_ext = diff --git a/src/wronskian.jl b/src/wronskian.jl index ac5750a12..d796f4758 100644 --- a/src/wronskian.jl +++ b/src/wronskian.jl @@ -207,7 +207,7 @@ Computes the Wronskians of io_equations # reducing everything modulo prime PRIME = 2^31 - 1 - F = Nemo.GF(PRIME) + F = Nemo.Native.GF(PRIME) polyring_red, gens_red = Nemo.PolynomialRing(F, map(var_to_str, gens(parent(termlists[1][1])))) termlists = diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index 75d45dc0a..db9e6b99a 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -161,7 +161,7 @@ end ), ) - F = Nemo.GF(2^31 - 1) + F = Nemo.Native.GF(2^31 - 1) P = gfp_mpoly DType = Union{P, Generic.Frac{P}} diff --git a/test/mtk_compat.jl b/test/extensions/modelingtoolkit.jl similarity index 67% rename from test/mtk_compat.jl rename to test/extensions/modelingtoolkit.jl index ccbdaf287..3441e33f9 100644 --- a/test/mtk_compat.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,3 +1,7 @@ +if GROUP == "All" || GROUP == "ModelingToolkit" + +using ModelingToolkit + @testset "Check identifiability of `ODESystem` object" begin @parameters a01 a21 a12 @variables t x0(t) x1(t) y1(t) [output = true] @@ -395,3 +399,249 @@ 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 + +@testset "Discrete local identifiability, internal function" begin + cases = [] + + @parameters α β + @variables t S(t) I(t) R(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] + @named sir = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => sir, + :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), + :y => [y ~ I], + :y2 => [I], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ + @variables t x(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x) ~ θ * x^3] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x => true, θ => true), + :y => [y ~ x], + :y2 => [x], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ β + @variables t x1(t) x2(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b c d + @variables t x1(t) x2(t) u(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] + + @named lv = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + x1 => true, + x2 => false, + a => true, + b => false, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict(b * x2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => [b * x2], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :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{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + substitute(x2, Dict(t => 0)) => true, + a => true, + b => true, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => [x2], + :to_check => Array{}[], + ), + ) + + # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 + @variables t x1(t) x2(t) u(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] + + @named abmd1 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd1, + :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 theta3 + @variables t x1(t) x2(t) u(t) y(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] + + @named abmd2 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd2, + :res => OrderedDict( + x1 => true, + x2 => false, + theta1 => true, + theta2 => false, + theta3 => false, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => abmd2, + :res => Dict( + x1 => true, + x2 => true, + theta1 => true, + theta2 => true, + theta3 => true, + ), + :y => [y ~ x1, y2 ~ x2], + :y2 => [x1, x2], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b + @variables t x1(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a] + + @named kic = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict(x1 => false, a => true, b => false), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => [x1], + :to_check => Array{}[], + ), + ) + + for c in cases + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y2], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + end +end + +end diff --git a/test/local_identifiability_discrete.jl b/test/local_identifiability_discrete.jl deleted file mode 100644 index fd56b77e4..000000000 --- a/test/local_identifiability_discrete.jl +++ /dev/null @@ -1,243 +0,0 @@ -@testset "Discrete local identifiability, internal function" begin - cases = [] - - @parameters α β - @variables t S(t) I(t) R(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] - @named sir = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => sir, - :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), - :y => [y ~ I], - :y2 => [I], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ - @variables t x(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x) ~ θ * x^3] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x => true, θ => true), - :y => [y ~ x], - :y2 => [x], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ β - @variables t x1(t) x2(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b c d - @variables t x1(t) x2(t) u(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] - - @named lv = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => false, - a => true, - b => false, - c => true, - d => true, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict(b * x2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => [b * x2], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :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{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - substitute(x2, Dict(t => 0)) => true, - a => true, - b => true, - c => true, - d => true, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => [x2], - :to_check => Array{}[], - ), - ) - - # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 - @variables t x1(t) x2(t) u(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] - - @named abmd1 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd1, - :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 theta3 - @variables t x1(t) x2(t) u(t) y(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] - - @named abmd2 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd2, - :res => OrderedDict( - x1 => true, - x2 => false, - theta1 => true, - theta2 => false, - theta3 => false, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => abmd2, - :res => Dict( - x1 => true, - x2 => true, - theta1 => true, - theta2 => true, - theta3 => true, - ), - :y => [y ~ x1, y2 ~ x2], - :y2 => [x1, x2], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b - @variables t x1(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a] - - @named kic = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(x1 => false, a => true, b => false), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => [x1], - :to_check => Array{}[], - ), - ) - - for c in cases - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y2], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - end -end diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index 177bec543..654ad485c 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -39,7 +39,7 @@ end sol = ps_ode_solution(eqs, Dict(x => 0, y => -2), Dict(u => u_coeff), prec) @test map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) == [prec - 1, prec - 1] - F = Nemo.GF(2^31 - 1) + F = Nemo.Native.GF(2^31 - 1) prec = 100 # Testing the function ps_ode_solution by itself diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index fcb952c75..96b78ff9f 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -1,5 +1,5 @@ @testset "Parent ring change" begin - for field in [Nemo.QQ, Nemo.GF(2^31 - 1)] + for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] R, (z, x, y) = PolynomialRing(QQ, ["z", "x", "y"], ordering = :degrevlex) R_, (y_, x_) = PolynomialRing(QQ, ["y", "x"], ordering = :lex) R__, (x__, t__, y__, z__) = diff --git a/test/ps_inverse.jl b/test/ps_inverse.jl index c5a95d124..63acafad9 100644 --- a/test/ps_inverse.jl +++ b/test/ps_inverse.jl @@ -1,5 +1,5 @@ @testset "Power series matrix inverse" begin - T, t = Nemo.PowerSeriesRing(Nemo.GF(2^31 - 1), 50, "t"; model = :capped_absolute) + T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 50, "t"; model = :capped_absolute) for d in 1:5 S = Nemo.MatrixSpace(T, d, d) diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index aa8ab8230..aad8f6970 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,11 +1,11 @@ @testset "Homogeneous linear differential equations" begin - T, t = Nemo.PowerSeriesRing(Nemo.GF(2^31 - 1), 300, "t"; model = :capped_absolute) + T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 S = Nemo.MatrixSpace(T, d, d) A = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.GF(2^31 - 1), d, d) + Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index e681c54d6..2b38e3ac5 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,5 +1,5 @@ @testset "Linear differential equations" begin - T, t = Nemo.PowerSeriesRing(Nemo.GF(2^31 - 1), 300, "t"; model = :capped_absolute) + T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 diff --git a/test/runtests.jl b/test/runtests.jl index 82e6b0dd7..001feacec 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,7 +5,6 @@ using TestSetExtensions using StructuralIdentifiability.DataStructures using StructuralIdentifiability.Nemo -using StructuralIdentifiability.ModelingToolkit using StructuralIdentifiability: field_contains, check_identifiability, @@ -77,6 +76,8 @@ function random_ps_matrix(ps_ring, matrix_space) return result end +const GROUP = get(ENV, "GROUP", "All") + @info "Testing started" @test isempty(Test.detect_ambiguities(StructuralIdentifiability)) From 9acd1edaf1ecc3facbdc691541bbfcbd016a6cba Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 22:56:40 +0100 Subject: [PATCH 32/82] adding groups to ci workflow --- .github/workflows/CI.yml | 1 + test/extensions/modelingtoolkit.jl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4955b36c8..6394401a1 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,6 +13,7 @@ jobs: matrix: group: - Core + - ModelingToolkitExt version: - '1' - '1.6' diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index 3441e33f9..d428214c3 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,4 +1,4 @@ -if GROUP == "All" || GROUP == "ModelingToolkit" +if GROUP == "All" || GROUP == "ModelingToolkitExt" using ModelingToolkit From 3a3d0b48a9d9dedbd0f7360604f9cad5a7f962b9 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 17 Jan 2024 23:01:57 +0100 Subject: [PATCH 33/82] formatting --- src/StructuralIdentifiability.jl | 5 +- test/extensions/modelingtoolkit.jl | 1260 ++++++++++++++-------------- test/ps_matrix_homlinear.jl | 3 +- test/ps_matrix_linear.jl | 3 +- 4 files changed, 641 insertions(+), 630 deletions(-) diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 601375d1d..c3a5fd5ee 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -172,15 +172,14 @@ end using Requires end -@static if !isdefined(Base,:get_extension) +@static if !isdefined(Base, :get_extension) function __init__() - @require ModelingToolkit="961ee093-0014-501f-94e3-6117800e7a78" begin + @require ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" begin include("../ext/ModelingToolkitExt.jl") end end end - using PrecompileTools include("precompile.jl") diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index d428214c3..cc580e21c 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,647 +1,657 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" + using ModelingToolkit -using ModelingToolkit - -@testset "Check identifiability of `ODESystem` object" begin - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal(correct, assess_identifiability(de; measured_quantities = [y1 ~ x0])) - @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) - @test isequal( - correct, - assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), - ) - - # check identifiabile functions - correct = [a01 * a12, a01 + a12 + a21] - result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - - # check identifiabile functions - @parameters V_m k_m k01 c - @variables t x(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] - de = ODESystem(eqs, t, name = :Test) - - correct = [k01, c * k_m, V_m * c] - result = find_identifiable_functions(de) - @test isequal(Set(correct), Set(result)) - - correct = [k01, c * x, k_m * c, V_m * c] - result = find_identifiable_functions(de, with_states = true) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal(correct, assess_identifiability(de)) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - 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 = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal(correct, assess_identifiability(de; funcs_to_check = funcs_to_check)) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] - measured_quantities = [y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # -------------------------------------------------------------------------- - @parameters μ bi bw a χ γ k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, - D(W) ~ χ * (I - W), - D(R) ~ γ * I - (μ + a) * R, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - measured_quantities = [y ~ k * I] - # check all parameters (default) - @test isequal( - true, - all( - values( - assess_local_identifiability(de; measured_quantities = measured_quantities), - ), - ), - ) - - # check specific parameters - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # checking identifiabile functions - correct = [a, bw, χ, bi, k, γ, μ] - result = find_identifiable_functions(de, measured_quantities = measured_quantities) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) [output = true] - - eqs = [ - D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - W), - D(R) ~ gm * I - (mu + a) * R, - y ~ k * I, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - # check all parameters (default) - @test isequal(true, all(values(assess_local_identifiability(de)))) - - @test isequal( - true, - all(values(assess_local_identifiability(de; measured_quantities = [y ~ k * I]))), - ) - - # check specific parameters - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability(de; funcs_to_check = funcs_to_check), - ) - - # checking ME identifiability - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - 0.6 * W), - D(R) ~ gm * I - (mu + a) * R, - ] - 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 = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # ---------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - using SpecialFunctions - - eqs = [ - D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, - D(x1) ~ a21 * x0 - a12 * x1, - ] - - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y1 ~ x0] - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = Dict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test_throws ArgumentError assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ) - # ---------- - @parameters a b c - @variables t x1(t) x2(t) y(t) - D = Differential(t) - - eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y ~ x2] - correct = Dict(a => :globally, b => :globally, c => :locally) - to_check = [a, b, c] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - # check identifiabile functions - result = find_identifiable_functions(de, measured_quantities = measured_quantities) - correct = [b, a, c^2] - @test isequal(Set(result), Set(correct)) - - # ---------- - @parameters a b - @variables t c(t) x1(t) x2(t) y1(t) y2(t) - D = Differential(t) - - eqs = - [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 = OrderedDict(a => :globally, b => :globally) - to_check = [a, b] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - #---------------------------------- - # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) - @variables t - function rabbits_creator(; name) - ps = @parameters α = 1.5 - vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] + @testset "Check identifiability of `ODESystem` object" begin + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - equs = [D(x) ~ α^2 * x + z] - - ODESystem(equs, t, vars, ps; name = name) - end - function wolves_creator(; name) - ps = @parameters δ = 3.0 - vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) + + @test isequal(correct, assess_identifiability(de; measured_quantities = [y1 ~ x0])) + @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) + @test isequal( + correct, + assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), + ) + + # check identifiabile functions + correct = [a01 * a12, a01 + a12 + a21] + result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + + # check identifiabile functions + @parameters V_m k_m k01 c + @variables t x(t) y1(t) [output = true] D = Differential(t) - equs = [D(y) ~ -δ * y + q] - ODESystem(equs, t, vars, ps; name = name) - end + eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] + de = ODESystem(eqs, t, name = :Test) + + correct = [k01, c * k_m, V_m * c] + result = find_identifiable_functions(de) + @test isequal(Set(correct), Set(result)) - function lotka_volterra_creator(; name) - @named wolves = wolves_creator() - @named rabbits = rabbits_creator() + correct = [k01, c * x, k_m * c, V_m * c] + result = find_identifiable_functions(de, with_states = true) + @test isequal(Set(correct), Set(result)) - ps = @parameters β = 1.0 γ = 1.0 + # -------------------------------------------------------------------------- + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - eqs = [rabbits.z ~ -β * wolves.y * rabbits.x, wolves.q ~ γ * wolves.y * rabbits.x] + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) - ModelingToolkit.compose(ODESystem(eqs, t, [], ps; name = name), wolves, rabbits) - end + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) - function getbyname(sys, name) - println(name) - return first([ - v for v in vcat(states(sys), parameters(sys)) if - replace(string(v), "(t)" => "") == name - ]) - end + @test isequal(correct, assess_identifiability(de)) - @named ltk_mtk = lotka_volterra_creator() - simp_ltk_mtk = structural_simplify(ltk_mtk) - wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") - rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") - β = getbyname(simp_ltk_mtk, "β") - γ = getbyname(simp_ltk_mtk, "γ") - wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") - rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") - @variables y(t) - measured_quantities = [y ~ wolves₊y] - result = assess_identifiability(simp_ltk_mtk, measured_quantities = measured_quantities) - correct = Dict( - rabbits₊α => :locally, - γ => :nonidentifiable, - β => :globally, - wolves₊δ => :globally, - rabbits₊x => :nonidentifiable, - wolves₊y => :globally, - ) - @test Dict(result) == correct - - #---------------------------------- - - @variables t, x(t), y(t), z(t), w(t) - @parameters a - @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) - measured_quantities = [w ~ x] - result = assess_identifiability(sys, measured_quantities = measured_quantities) - @test result[a] == :globally - - result = find_identifiable_functions(sys, measured_quantities = measured_quantities) - @test isequal(result, [a]) - - #---------------------------------- - - # Tensor definition case as reported in - # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 - @variables t, x(t)[1:2], y(t)[1:2] - @parameters k1, k2 - - eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] - - sys = ODESystem(eqs, t, name = :example_vector) - 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 + # -------------------------------------------------------------------------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] + D = Differential(t) + + 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 = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal(correct, assess_identifiability(de; funcs_to_check = funcs_to_check)) + + # -------------------------------------------------------------------------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) -@testset "Discrete local identifiability, internal function" begin - cases = [] - - @parameters α β - @variables t S(t) I(t) R(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] - @named sir = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => sir, - :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), - :y => [y ~ I], - :y2 => [I], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ - @variables t x(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x) ~ θ * x^3] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x => true, θ => true), - :y => [y ~ x], - :y2 => [x], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ β - @variables t x1(t) x2(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b c d - @variables t x1(t) x2(t) u(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] - - @named lv = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => false, - a => true, - b => false, - c => true, - d => true, + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] + measured_quantities = [y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # -------------------------------------------------------------------------- + @parameters μ bi bw a χ γ k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, + D(W) ~ χ * (I - W), + D(R) ~ γ * I - (μ + a) * R, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + measured_quantities = [y ~ k * I] + # check all parameters (default) + @test isequal( + true, + all( + values( + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + ), + ), + ), + ) + + # check specific parameters + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # checking ME identifiability + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict(b * x2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => [b * x2], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => true, - a => true, - b => true, - c => true, - d => true, + ) + + # checking identifiabile functions + correct = [a, bw, χ, bi, k, γ, μ] + result = find_identifiable_functions(de, measured_quantities = measured_quantities) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) [output = true] + + eqs = [ + D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - W), + D(R) ~ gm * I - (mu + a) * R, + y ~ k * I, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + # check all parameters (default) + @test isequal(true, all(values(assess_local_identifiability(de)))) + + @test isequal( + true, + all( + values(assess_local_identifiability(de; measured_quantities = [y ~ k * I])), ), - :y => [y ~ x1, y2 ~ x1 / x2], - :y2 => [x1, x1 / x2], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - substitute(x2, Dict(t => 0)) => true, - a => true, - b => true, - c => true, - d => true, + ) + + # check specific parameters + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability(de; funcs_to_check = funcs_to_check), + ) + + # checking ME identifiability + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => [x2], - :to_check => Array{}[], - ), - ) - - # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 - @variables t x1(t) x2(t) u(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] - - @named abmd1 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd1, - :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 theta3 - @variables t x1(t) x2(t) u(t) y(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] - - @named abmd2 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd2, - :res => OrderedDict( - x1 => true, - x2 => false, - theta1 => true, - theta2 => false, - theta3 => false, + ) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - 0.6 * W), + D(R) ~ gm * I - (mu + a) * R, + ] + 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 = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # checking ME identifiability + funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # ---------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) + using SpecialFunctions + + eqs = [ + D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, + D(x1) ~ a21 * x0 - a12 * x1, + ] + + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y1 ~ x0] + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = Dict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test_throws ArgumentError assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ) + # ---------- + @parameters a b c + @variables t x1(t) x2(t) y(t) + D = Differential(t) + + eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y ~ x2] + correct = Dict(a => :globally, b => :globally, c => :locally) + to_check = [a, b, c] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => abmd2, - :res => Dict( - x1 => true, - x2 => true, - theta1 => true, - theta2 => true, - theta3 => true, + ) + + # check identifiabile functions + result = find_identifiable_functions(de, measured_quantities = measured_quantities) + correct = [b, a, c^2] + @test isequal(Set(result), Set(correct)) + + # ---------- + @parameters a b + @variables t c(t) x1(t) x2(t) y1(t) y2(t) + D = Differential(t) + + eqs = [ + 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 = OrderedDict(a => :globally, b => :globally) + to_check = [a, b] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, ), - :y => [y ~ x1, y2 ~ x2], - :y2 => [x1, x2], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b - @variables t x1(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a] - - @named kic = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(x1 => false, a => true, b => false), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => [x1], - :to_check => Array{}[], - ), - ) - - for c in cases - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y2], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] + ) + + #---------------------------------- + # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) + @variables t + function rabbits_creator(; name) + ps = @parameters α = 1.5 + vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] + D = Differential(t) + equs = [D(x) ~ α^2 * x + z] + + ODESystem(equs, t, vars, ps; name = name) + end + + function wolves_creator(; name) + ps = @parameters δ = 3.0 + vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] + D = Differential(t) + equs = [D(y) ~ -δ * y + q] + + ODESystem(equs, t, vars, ps; name = name) + end + + function lotka_volterra_creator(; name) + @named wolves = wolves_creator() + @named rabbits = rabbits_creator() + + ps = @parameters β = 1.0 γ = 1.0 + D = Differential(t) + + eqs = + [rabbits.z ~ -β * wolves.y * rabbits.x, wolves.q ~ γ * wolves.y * rabbits.x] + + ModelingToolkit.compose(ODESystem(eqs, t, [], ps; name = name), wolves, rabbits) + end + + function getbyname(sys, name) + println(name) + return first([ + v for v in vcat(states(sys), parameters(sys)) if + replace(string(v), "(t)" => "") == name + ]) + end + + @named ltk_mtk = lotka_volterra_creator() + simp_ltk_mtk = structural_simplify(ltk_mtk) + wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") + rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") + β = getbyname(simp_ltk_mtk, "β") + γ = getbyname(simp_ltk_mtk, "γ") + wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") + rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") + @variables y(t) + measured_quantities = [y ~ wolves₊y] + result = + assess_identifiability(simp_ltk_mtk, measured_quantities = measured_quantities) + correct = Dict( + rabbits₊α => :locally, + γ => :nonidentifiable, + β => :globally, + wolves₊δ => :globally, + rabbits₊x => :nonidentifiable, + wolves₊y => :globally, + ) + @test Dict(result) == correct + + #---------------------------------- + + @variables t, x(t), y(t), z(t), w(t) + @parameters a + @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) + measured_quantities = [w ~ x] + result = assess_identifiability(sys, measured_quantities = measured_quantities) + @test result[a] == :globally + + result = find_identifiable_functions(sys, measured_quantities = measured_quantities) + @test isequal(result, [a]) + + #---------------------------------- + + # Tensor definition case as reported in + # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 + @variables t, x(t)[1:2], y(t)[1:2] + @parameters k1, k2 + + eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] + + sys = ODESystem(eqs, t, name = :example_vector) + 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 -end + @testset "Discrete local identifiability, internal function" begin + cases = [] + + @parameters α β + @variables t S(t) I(t) R(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] + @named sir = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => sir, + :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), + :y => [y ~ I], + :y2 => [I], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ + @variables t x(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x) ~ θ * x^3] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x => true, θ => true), + :y => [y ~ x], + :y2 => [x], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ β + @variables t x1(t) x2(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b c d + @variables t x1(t) x2(t) u(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] + + @named lv = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + x1 => true, + x2 => false, + a => true, + b => false, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict(b * x2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => [b * x2], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :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{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + substitute(x2, Dict(t => 0)) => true, + a => true, + b => true, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => [x2], + :to_check => Array{}[], + ), + ) + + # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 + @variables t x1(t) x2(t) u(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] + + @named abmd1 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd1, + :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 theta3 + @variables t x1(t) x2(t) u(t) y(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] + + @named abmd2 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd2, + :res => OrderedDict( + x1 => true, + x2 => false, + theta1 => true, + theta2 => false, + theta3 => false, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => abmd2, + :res => Dict( + x1 => true, + x2 => true, + theta1 => true, + theta2 => true, + theta3 => true, + ), + :y => [y ~ x1, y2 ~ x2], + :y2 => [x1, x2], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b + @variables t x1(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a] + + @named kic = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict(x1 => false, a => true, b => false), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => kic, + :res => + OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => [x1], + :to_check => Array{}[], + ), + ) + + for c in cases + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y2], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + end + end end diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index aad8f6970..c9925fef2 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,5 +1,6 @@ @testset "Homogeneous linear differential equations" begin - T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) + T, t = + Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index 2b38e3ac5..617aeb226 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,5 +1,6 @@ @testset "Linear differential equations" begin - T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) + T, t = + Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 From 52dd8c3ae67fd4fcff86b992ccab778100c11569 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Thu, 18 Jan 2024 09:51:16 +0100 Subject: [PATCH 34/82] optionalizing MTK in the tests --- Project.toml | 4 +- test/extensions/modelingtoolkit.jl | 1301 ++++++++++++++-------------- test/ps_matrix_linear.jl | 2 +- 3 files changed, 669 insertions(+), 638 deletions(-) diff --git a/Project.toml b/Project.toml index 3bc8b888d..49cbd2968 100644 --- a/Project.toml +++ b/Project.toml @@ -51,7 +51,7 @@ PrecompileTools = "1.1, 1.2" Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "1, 2" -SymbolicUtils = "1" +SymbolicUtils = "1.2, 1.3, 1.4, 1.5" Symbolics = "5.5" TestSetExtensions = "2" TimerOutputs = "0.5" @@ -66,4 +66,4 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [targets] -test = ["CPUSummary", "Test", "TestSetExtensions", "ModelingToolkit"] +test = ["CPUSummary", "Test", "TestSetExtensions"] diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index cc580e21c..d02b4959c 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,657 +1,688 @@ -if GROUP == "All" || GROUP == "ModelingToolkitExt" - using ModelingToolkit - - @testset "Check identifiability of `ODESystem` object" begin - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal(correct, assess_identifiability(de; measured_quantities = [y1 ~ x0])) - @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) - @test isequal( - correct, - assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), - ) - - # check identifiabile functions - correct = [a01 * a12, a01 + a12 + a21] - result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - - # check identifiabile functions - @parameters V_m k_m k01 c - @variables t x(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] - de = ODESystem(eqs, t, name = :Test) - - correct = [k01, c * k_m, V_m * c] - result = find_identifiable_functions(de) - @test isequal(Set(correct), Set(result)) - - correct = [k01, c * x, k_m * c, V_m * c] - result = find_identifiable_functions(de, with_states = true) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal(correct, assess_identifiability(de)) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - 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 = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal(correct, assess_identifiability(de; funcs_to_check = funcs_to_check)) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] - measured_quantities = [y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # -------------------------------------------------------------------------- - @parameters μ bi bw a χ γ k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, - D(W) ~ χ * (I - W), - D(R) ~ γ * I - (μ + a) * R, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - measured_quantities = [y ~ k * I] - # check all parameters (default) - @test isequal( - true, - all( - values( - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - ), - ), - ), - ) - - # check specific parameters - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # checking identifiabile functions - correct = [a, bw, χ, bi, k, γ, μ] - result = find_identifiable_functions(de, measured_quantities = measured_quantities) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) [output = true] - - eqs = [ - D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - W), - D(R) ~ gm * I - (mu + a) * R, - y ~ k * I, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - # check all parameters (default) - @test isequal(true, all(values(assess_local_identifiability(de)))) - - @test isequal( - true, - all( - values(assess_local_identifiability(de; measured_quantities = [y ~ k * I])), - ), - ) - - # check specific parameters - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability(de; funcs_to_check = funcs_to_check), - ) - - # checking ME identifiability - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - 0.6 * W), - D(R) ~ gm * I - (mu + a) * R, - ] - 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 = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # ---------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - using SpecialFunctions - - eqs = [ - D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, - D(x1) ~ a21 * x0 - a12 * x1, - ] - - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y1 ~ x0] - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = Dict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test_throws ArgumentError assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ) - # ---------- - @parameters a b c - @variables t x1(t) x2(t) y(t) - D = Differential(t) - - eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y ~ x2] - correct = Dict(a => :globally, b => :globally, c => :locally) - to_check = [a, b, c] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - # check identifiabile functions - result = find_identifiable_functions(de, measured_quantities = measured_quantities) - correct = [b, a, c^2] - @test isequal(Set(result), Set(correct)) - - # ---------- - @parameters a b - @variables t c(t) x1(t) x2(t) y1(t) y2(t) - D = Differential(t) - - eqs = [ - 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 = OrderedDict(a => :globally, b => :globally) - to_check = [a, b] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - #---------------------------------- - # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) - @variables t - function rabbits_creator(; name) - ps = @parameters α = 1.5 - vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] +@static if VERSION >= v"1.10.0" + if GROUP == "All" || GROUP == "ModelingToolkitExt" + using Pkg + Pkg.add("ModelingToolkit") + using ModelingToolkit + + @testset "Check identifiability of `ODESystem` object" begin + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - equs = [D(x) ~ α^2 * x + z] - ODESystem(equs, t, vars, ps; name = name) - end + eqs = + [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) + + @test isequal( + correct, + assess_identifiability(de; measured_quantities = [y1 ~ x0]), + ) + @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) + @test isequal( + correct, + assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), + ) + + # check identifiabile functions + correct = [a01 * a12, a01 + a12 + a21] + result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + + # check identifiabile functions + @parameters V_m k_m k01 c + @variables t x(t) y1(t) [output = true] + D = Differential(t) + + eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] + de = ODESystem(eqs, t, name = :Test) + + correct = [k01, c * k_m, V_m * c] + result = find_identifiable_functions(de) + @test isequal(Set(correct), Set(result)) - function wolves_creator(; name) - ps = @parameters δ = 3.0 - vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] + correct = [k01, c * x, k_m * c, V_m * c] + result = find_identifiable_functions(de, with_states = true) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - equs = [D(y) ~ -δ * y + q] - ODESystem(equs, t, vars, ps; name = name) - end + eqs = + [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) + + @test isequal(correct, assess_identifiability(de)) - function lotka_volterra_creator(; name) - @named wolves = wolves_creator() - @named rabbits = rabbits_creator() + # -------------------------------------------------------------------------- - ps = @parameters β = 1.0 γ = 1.0 + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) eqs = - [rabbits.z ~ -β * wolves.y * rabbits.x, wolves.q ~ γ * wolves.y * rabbits.x] + [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 = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal( + correct, + assess_identifiability(de; funcs_to_check = funcs_to_check), + ) + + # -------------------------------------------------------------------------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) - ModelingToolkit.compose(ODESystem(eqs, t, [], ps; name = name), wolves, rabbits) - end + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] + measured_quantities = [y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # -------------------------------------------------------------------------- + @parameters μ bi bw a χ γ k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, + D(W) ~ χ * (I - W), + D(R) ~ γ * I - (μ + a) * R, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + measured_quantities = [y ~ k * I] + # check all parameters (default) + @test isequal( + true, + all( + values( + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + ), + ), + ), + ) + + # check specific parameters + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # checking ME identifiability + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # checking identifiabile functions + correct = [a, bw, χ, bi, k, γ, μ] + result = + find_identifiable_functions(de, measured_quantities = measured_quantities) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) [output = true] + + eqs = [ + D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - W), + D(R) ~ gm * I - (mu + a) * R, + y ~ k * I, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + # check all parameters (default) + @test isequal(true, all(values(assess_local_identifiability(de)))) + + @test isequal( + true, + all( + values( + assess_local_identifiability(de; measured_quantities = [y ~ k * I]), + ), + ), + ) + + # check specific parameters + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability(de; funcs_to_check = funcs_to_check), + ) + + # checking ME identifiability + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - 0.6 * W), + D(R) ~ gm * I - (mu + a) * R, + ] + 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 = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # checking ME identifiability + funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) - function getbyname(sys, name) - println(name) - return first([ - v for v in vcat(states(sys), parameters(sys)) if - replace(string(v), "(t)" => "") == name - ]) - end + # ---------- - @named ltk_mtk = lotka_volterra_creator() - simp_ltk_mtk = structural_simplify(ltk_mtk) - wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") - rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") - β = getbyname(simp_ltk_mtk, "β") - γ = getbyname(simp_ltk_mtk, "γ") - wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") - rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") - @variables y(t) - measured_quantities = [y ~ wolves₊y] - result = - assess_identifiability(simp_ltk_mtk, measured_quantities = measured_quantities) - correct = Dict( - rabbits₊α => :locally, - γ => :nonidentifiable, - β => :globally, - wolves₊δ => :globally, - rabbits₊x => :nonidentifiable, - wolves₊y => :globally, - ) - @test Dict(result) == correct - - #---------------------------------- - - @variables t, x(t), y(t), z(t), w(t) - @parameters a - @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) - measured_quantities = [w ~ x] - result = assess_identifiability(sys, measured_quantities = measured_quantities) - @test result[a] == :globally - - result = find_identifiable_functions(sys, measured_quantities = measured_quantities) - @test isequal(result, [a]) - - #---------------------------------- - - # Tensor definition case as reported in - # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 - @variables t, x(t)[1:2], y(t)[1:2] - @parameters k1, k2 - - eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] - - sys = ODESystem(eqs, t, name = :example_vector) - 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 + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) + using SpecialFunctions + + eqs = [ + D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, + D(x1) ~ a21 * x0 - a12 * x1, + ] + + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y1 ~ x0] + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = Dict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test_throws ArgumentError assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ) + # ---------- + @parameters a b c + @variables t x1(t) x2(t) y(t) + D = Differential(t) - @testset "Discrete local identifiability, internal function" begin - cases = [] - - @parameters α β - @variables t S(t) I(t) R(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] - @named sir = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => sir, - :res => OrderedDict(S => true, I => true, R => false, α => true, β => true), - :y => [y ~ I], - :y2 => [I], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ - @variables t x(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x) ~ θ * x^3] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x => true, θ => true), - :y => [y ~ x], - :y2 => [x], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters θ β - @variables t x1(t) x2(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b c d - @variables t x1(t) x2(t) u(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] - - @named lv = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => false, - a => true, - b => false, - c => true, - d => true, + eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y ~ x2] + correct = Dict(a => :globally, b => :globally, c => :locally) + to_check = [a, b, c] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict(b * x2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => [b * x2], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => true, - a => true, - b => true, - c => true, - d => true, + ) + + # check identifiabile functions + result = + find_identifiable_functions(de, measured_quantities = measured_quantities) + correct = [b, a, c^2] + @test isequal(Set(result), Set(correct)) + + # ---------- + @parameters a b + @variables t c(t) x1(t) x2(t) y1(t) y2(t) + D = Differential(t) + + eqs = [ + 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 = OrderedDict(a => :globally, b => :globally) + to_check = [a, b] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, ), - :y => [y ~ x1, y2 ~ x1 / x2], - :y2 => [x1, x1 / x2], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - substitute(x2, Dict(t => 0)) => true, - a => true, - b => true, - c => true, - d => true, + ) + + #---------------------------------- + # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) + @variables t + function rabbits_creator(; name) + ps = @parameters α = 1.5 + vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] + D = Differential(t) + equs = [D(x) ~ α^2 * x + z] + + ODESystem(equs, t, vars, ps; name = name) + end + + function wolves_creator(; name) + ps = @parameters δ = 3.0 + vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] + D = Differential(t) + equs = [D(y) ~ -δ * y + q] + + ODESystem(equs, t, vars, ps; name = name) + end + + function lotka_volterra_creator(; name) + @named wolves = wolves_creator() + @named rabbits = rabbits_creator() + + ps = @parameters β = 1.0 γ = 1.0 + D = Differential(t) + + eqs = [ + rabbits.z ~ -β * wolves.y * rabbits.x, + wolves.q ~ γ * wolves.y * rabbits.x, + ] + + ModelingToolkit.compose( + ODESystem(eqs, t, [], ps; name = name), + wolves, + rabbits, + ) + end + + function getbyname(sys, name) + println(name) + return first([ + v for v in vcat(states(sys), parameters(sys)) if + replace(string(v), "(t)" => "") == name + ]) + end + + @named ltk_mtk = lotka_volterra_creator() + simp_ltk_mtk = structural_simplify(ltk_mtk) + wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") + rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") + β = getbyname(simp_ltk_mtk, "β") + γ = getbyname(simp_ltk_mtk, "γ") + wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") + rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") + @variables y(t) + measured_quantities = [y ~ wolves₊y] + result = assess_identifiability( + simp_ltk_mtk, + measured_quantities = measured_quantities, + ) + correct = Dict( + rabbits₊α => :locally, + γ => :nonidentifiable, + β => :globally, + wolves₊δ => :globally, + rabbits₊x => :nonidentifiable, + wolves₊y => :globally, + ) + @test Dict(result) == correct + + #---------------------------------- + + @variables t, x(t), y(t), z(t), w(t) + @parameters a + @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) + measured_quantities = [w ~ x] + result = assess_identifiability(sys, measured_quantities = measured_quantities) + @test result[a] == :globally + + result = + find_identifiable_functions(sys, measured_quantities = measured_quantities) + @test isequal(result, [a]) + + #---------------------------------- + + # Tensor definition case as reported in + # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 + @variables t, x(t)[1:2], y(t)[1:2] + @parameters k1, k2 + + eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] + + sys = ODESystem(eqs, t, name = :example_vector) + 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 + + @testset "Discrete local identifiability, internal function" begin + cases = [] + + @parameters α β + @variables t S(t) I(t) R(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] + @named sir = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => sir, + :res => + OrderedDict(S => true, I => true, R => false, α => true, β => true), + :y => [y ~ I], + :y2 => [I], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ + @variables t x(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x) ~ θ * x^3] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x => true, θ => true), + :y => [y ~ x], + :y2 => [x], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ β + @variables t x1(t) x2(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b c d + @variables t x1(t) x2(t) u(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] + + @named lv = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + x1 => true, + x2 => false, + a => true, + b => false, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict(b * x2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => [b * x2], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :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{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + substitute(x2, Dict(t => 0)) => true, + a => true, + b => true, + c => true, + d => true, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => [x2], + :to_check => Array{}[], ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => [x2], - :to_check => Array{}[], - ), - ) - - # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 - @variables t x1(t) x2(t) u(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] - - @named abmd1 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd1, - :res => OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 theta3 - @variables t x1(t) x2(t) u(t) y(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] - - @named abmd2 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd2, - :res => OrderedDict( - x1 => true, - x2 => false, - theta1 => true, - theta2 => false, - theta3 => false, + ) + + # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 + @variables t x1(t) x2(t) u(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] + + @named abmd1 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd1, + :res => + OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 theta3 + @variables t x1(t) x2(t) u(t) y(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] + + @named abmd2 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd2, + :res => OrderedDict( + x1 => true, + x2 => false, + theta1 => true, + theta2 => false, + theta3 => false, + ), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => abmd2, - :res => Dict( - x1 => true, - x2 => true, - theta1 => true, - theta2 => true, - theta3 => true, + ) + push!( + cases, + Dict( + :dds => abmd2, + :res => Dict( + x1 => true, + x2 => true, + theta1 => true, + theta2 => true, + theta3 => true, + ), + :y => [y ~ x1, y2 ~ x2], + :y2 => [x1, x2], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b + @variables t x1(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a] + + @named kic = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict(x1 => false, a => true, b => false), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + a => true, + b => true, + ), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => [x1], + :to_check => Array{}[], ), - :y => [y ~ x1, y2 ~ x2], - :y2 => [x1, x2], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - - @parameters a b - @variables t x1(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a] - - @named kic = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(x1 => false, a => true, b => false), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) - push!( - cases, - Dict( - :dds => kic, - :res => - OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => [x1], - :to_check => Array{}[], - ), - ) - - for c in cases - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y2], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] + ) + + for c in cases + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y2], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + end end end end diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index 617aeb226..c18fedf44 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -7,7 +7,7 @@ S = Nemo.MatrixSpace(T, d, d) A = random_ps_matrix(T, S) B = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.GF(2^31 - 1), d, d) + Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) @time sol = ps_matrix_linear_de(A, B, Y0) to_be_zero = map(ps_diff, sol) - A * sol - B From 67049757a2552e0fc8a1dffac5245bc58d992f7f Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Thu, 18 Jan 2024 10:00:12 +0100 Subject: [PATCH 35/82] cleaning deps? --- Project.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Project.toml b/Project.toml index 49cbd2968..5214b6ffc 100644 --- a/Project.toml +++ b/Project.toml @@ -14,15 +14,12 @@ IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] From c5ef895bc24891fc6b3e1b0b7aefcdf4e9677f5e Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Thu, 18 Jan 2024 10:06:05 +0100 Subject: [PATCH 36/82] adding Requires --- Project.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 5214b6ffc..12a9cdcfc 100644 --- a/Project.toml +++ b/Project.toml @@ -19,6 +19,7 @@ ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Requires = "ae029012-a4dd-5104-9daa-d747884805df" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" @@ -28,7 +29,7 @@ SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [extensions] -ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils" ,"Symbolics"] +ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] [compat] AbstractAlgebra = "0.34.5, 0.35" @@ -56,11 +57,11 @@ julia = "1.6, 1.7" [extras] CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] test = ["CPUSummary", "Test", "TestSetExtensions"] From b8b770d14547cca8f41dfa2e7f24b72fbec84e0a Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Thu, 18 Jan 2024 10:41:16 +0100 Subject: [PATCH 37/82] grouping tests better --- .../RationalFunctionField.jl | 62 +- test/RationalFunctionFields/normalforms.jl | 146 +- test/check_field_membership.jl | 50 +- test/check_primality_zerodim.jl | 14 +- test/common_ring.jl | 77 +- test/constructive_membership.jl | 228 +- test/decompose_derivative.jl | 22 +- test/det_minor_expansion.jl | 14 +- test/diff_sequence_solution.jl | 200 +- test/differentiate_output.jl | 444 ++-- test/diffreduction.jl | 320 +-- test/exp_vec_trie.jl | 52 +- test/extract_coefficients.jl | 105 +- test/find_leader.jl | 46 +- test/identifiability.jl | 468 ++-- test/identifiable_functions.jl | 1975 +++++++++-------- test/io_cases.jl | 280 +-- test/io_projections.jl | 111 +- test/lc_univariate.jl | 16 +- test/lie_derivative.jl | 43 +- test/linear_compartment.jl | 312 +-- test/local_identifiability.jl | 234 +- test/local_identifiability_discrete_aux.jl | 136 +- test/local_identifiability_me.jl | 401 ++-- test/logging.jl | 34 +- test/monomial_compress.jl | 30 +- test/ode.jl | 34 +- test/ode_ps_solution.jl | 205 +- test/paradigm_shift.jl | 464 ++-- test/parent_ring_change.jl | 88 +- test/pb_representation.jl | 37 +- test/ps_diff.jl | 17 +- test/ps_integrate.jl | 20 +- test/ps_inverse.jl | 37 +- test/ps_matrix_homlinear.jl | 32 +- test/ps_matrix_linear.jl | 32 +- test/ps_matrix_log.jl | 76 +- test/pseudodivision.jl | 10 +- test/reduce_ode_mod_p.jl | 24 +- test/sequence_solution.jl | 46 +- test/set_parameter_values.jl | 28 +- test/state_generators.jl | 58 +- test/submodels.jl | 274 +-- 43 files changed, 3741 insertions(+), 3561 deletions(-) diff --git a/test/RationalFunctionFields/RationalFunctionField.jl b/test/RationalFunctionFields/RationalFunctionField.jl index 350887b77..9a9f88ba1 100644 --- a/test/RationalFunctionFields/RationalFunctionField.jl +++ b/test/RationalFunctionFields/RationalFunctionField.jl @@ -1,32 +1,38 @@ -@testset "RationalFunctionField" begin - p = 0.99 - R, (a, b, c) = QQ["a", "b", "c"] +if GROUP == "All" || GROUP == "Core" + @testset "RationalFunctionField" begin + p = 0.99 + R, (a, b, c) = QQ["a", "b", "c"] - f1 = [a, b, a + b + c] - f2 = [2c, 3b, 5a] - rff1 = StructuralIdentifiability.RationalFunctionField(f1) - rff2 = StructuralIdentifiability.RationalFunctionField(f2) - @test StructuralIdentifiability.fields_equal(rff1, rff2, p) - @test all(StructuralIdentifiability.field_contains(rff1, [zero(R), one(R)], p)) - @test all(StructuralIdentifiability.field_contains(rff1, [7a, 9b, 11c], p)) - @test all(StructuralIdentifiability.field_contains(rff2, [7a, 9b, 11c], p)) + f1 = [a, b, a + b + c] + f2 = [2c, 3b, 5a] + rff1 = StructuralIdentifiability.RationalFunctionField(f1) + rff2 = StructuralIdentifiability.RationalFunctionField(f2) + @test StructuralIdentifiability.fields_equal(rff1, rff2, p) + @test all(StructuralIdentifiability.field_contains(rff1, [zero(R), one(R)], p)) + @test all(StructuralIdentifiability.field_contains(rff1, [7a, 9b, 11c], p)) + @test all(StructuralIdentifiability.field_contains(rff2, [7a, 9b, 11c], p)) - s1, s2 = a + b + c, a^2 + b^2 + c^2 - f1 = [s1, s2] - rff1 = StructuralIdentifiability.RationalFunctionField(f1) - @test !any(StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p)) - @test all(StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p)) - @test all(StructuralIdentifiability.field_contains(rff1, [(s1)^8 - (s2)^9 + 89], p)) + s1, s2 = a + b + c, a^2 + b^2 + c^2 + f1 = [s1, s2] + rff1 = StructuralIdentifiability.RationalFunctionField(f1) + @test !any( + StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p), + ) + @test all( + StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p), + ) + @test all(StructuralIdentifiability.field_contains(rff1, [(s1)^8 - (s2)^9 + 89], p)) - # Example in Section 5 from - # https://mediatum.ub.tum.de/doc/685465/685465.pdf - R, (x1, x2) = QQ["x1", "x2"] - g1 = (x1^3 + x1 * x2 - 2) // (x1^2 - x2 - 1) - g2 = (x1^2 + x1^2 * x2 + 7) // (x1 - x1^2 * x2^2) - g3 = x1^2 + 3x1 * x2 - g4 = x1 * x2^2 + 5x1 * x2 - g5 = x1^3 * x2 - x2 - rff1 = StructuralIdentifiability.RationalFunctionField([g1, g2, g3, g4, g5]) - rff2 = StructuralIdentifiability.RationalFunctionField([x1, x2]) - @test StructuralIdentifiability.fields_equal(rff1, rff2, p) + # Example in Section 5 from + # https://mediatum.ub.tum.de/doc/685465/685465.pdf + R, (x1, x2) = QQ["x1", "x2"] + g1 = (x1^3 + x1 * x2 - 2) // (x1^2 - x2 - 1) + g2 = (x1^2 + x1^2 * x2 + 7) // (x1 - x1^2 * x2^2) + g3 = x1^2 + 3x1 * x2 + g4 = x1 * x2^2 + 5x1 * x2 + g5 = x1^3 * x2 - x2 + rff1 = StructuralIdentifiability.RationalFunctionField([g1, g2, g3, g4, g5]) + rff2 = StructuralIdentifiability.RationalFunctionField([x1, x2]) + @test StructuralIdentifiability.fields_equal(rff1, rff2, p) + end end diff --git a/test/RationalFunctionFields/normalforms.jl b/test/RationalFunctionFields/normalforms.jl index abce0d95f..b8c503de7 100644 --- a/test/RationalFunctionFields/normalforms.jl +++ b/test/RationalFunctionFields/normalforms.jl @@ -1,81 +1,83 @@ -eq_up_to_the_order(a, b) = issubset(a, b) && issubset(b, a) +if GROUP == "All" || GROUP == "Core" + eq_up_to_the_order(a, b) = issubset(a, b) && issubset(b, a) -@testset "Linear relations over the rationals" begin - R, (a, b, c) = QQ["a", "b", "c"] + @testset "Linear relations over the rationals" begin + R, (a, b, c) = QQ["a", "b", "c"] - f = [a + 9] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a]) + f = [a + 9] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a]) - f = [a * b // R(1), (b * c + a * b) // (a * b)] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a * b // R(1), b * c // R(1)]) + f = [a * b // R(1), (b * c + a * b) // (a * b)] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a * b // R(1), b * c // R(1)]) - R, (a, b, c) = QQ["a", "b", "c"] - f = [a^2 + b^2, a^3 + b^3, a^4 + b^4] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) - @test eq_up_to_the_order(relations, [a + b]) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a + b, a * b, a^2 + b^2]) + R, (a, b, c) = QQ["a", "b", "c"] + f = [a^2 + b^2, a^3 + b^3, a^4 + b^4] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) + @test eq_up_to_the_order(relations, [a + b]) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a + b, a * b, a^2 + b^2]) - f = [9a^7 + 10b^6, b^10 - 5b^2] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) - @test eq_up_to_the_order(relations, empty(f)) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 7) - @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6]) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 12) - @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6, b^10 - 5b^2]) + f = [9a^7 + 10b^6, b^10 - 5b^2] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) + @test eq_up_to_the_order(relations, empty(f)) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 7) + @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6]) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 12) + @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6, b^10 - 5b^2]) - # Regression tests - ### - # LV model. - R, (x1, p2, p4, y1, x2, x3, u, p1, p3) = - QQ["x1", "p2", "p4", "y1", "x2", "x3", "u", "p1", "p3"] - f = [ - x3 // one(R), - x2 * x1 // one(R), - p1 * p3 // one(R), - p2 * p4 // one(R), - p1 + p3 // one(R), - (p2 * x2 + p4 * x1) // (x2 * x1), - (p2 * x2 - p4 * x1) // (p1 - p3), - ] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree( - rff, - 2, - strategy = :monte_carlo, - ) - @test (x1 * p4 + p2 * x2) // one(R) in relations + # Regression tests + ### + # LV model. + R, (x1, p2, p4, y1, x2, x3, u, p1, p3) = + QQ["x1", "p2", "p4", "y1", "x2", "x3", "u", "p1", "p3"] + f = [ + x3 // one(R), + x2 * x1 // one(R), + p1 * p3 // one(R), + p2 * p4 // one(R), + p1 + p3 // one(R), + (p2 * x2 + p4 * x1) // (x2 * x1), + (p2 * x2 - p4 * x1) // (p1 - p3), + ] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree( + rff, + 2, + strategy = :monte_carlo, + ) + @test (x1 * p4 + p2 * x2) // one(R) in relations - ### - R, (a, b, c) = QQ["a", "b", "c"] - f = [a, a * b + b * c] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a, a * b + b * c]) + ### + R, (a, b, c) = QQ["a", "b", "c"] + f = [a, a * b + b * c] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a, a * b + b * c]) - ### - # Some arbitrary generators for the SLIQR model - R, (b, e, In, S, Ninv, s, Q, g, u, a, y, L) = - PolynomialRing(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) - f = [ - In // one(R), - s // one(R), - Ninv // one(R), - b // one(R), - (g + a) // one(R), - (e * s * g - s * g + g * a) // one(R), - (e * S - S) // (e * Q), - (e * S * s - S * s + S * a) // e, - (s * Q^2 - Q^2 * a) // (e * g - g), - (e * In + e * L - In - Q - L) // (e * Q), - ] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test s * Q - Q * a in relations + ### + # Some arbitrary generators for the SLIQR model + R, (b, e, In, S, Ninv, s, Q, g, u, a, y, L) = + PolynomialRing(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) + f = [ + In // one(R), + s // one(R), + Ninv // one(R), + b // one(R), + (g + a) // one(R), + (e * s * g - s * g + g * a) // one(R), + (e * S - S) // (e * Q), + (e * S * s - S * s + S * a) // e, + (s * Q^2 - Q^2 * a) // (e * g - g), + (e * In + e * L - In - Q - L) // (e * Q), + ] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test s * Q - Q * a in relations + end end diff --git a/test/check_field_membership.jl b/test/check_field_membership.jl index 6521db4d7..3c42a3ed0 100644 --- a/test/check_field_membership.jl +++ b/test/check_field_membership.jl @@ -1,26 +1,32 @@ -@testset "Check field membership" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) +if GROUP == "All" || GROUP == "Core" + @testset "Check field membership" begin + R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) - @test field_contains( - RationalFunctionField([[R(1), x + y], [R(1), x * y], [z, (x + y)^2]]), - [(x^2 + y^2) // R(1), (x^3 + y^3) // (z - x * y), R(1) // (z + x + y), z // x], - 0.99, - ) == [true, true, true, false] + @test field_contains( + RationalFunctionField([[R(1), x + y], [R(1), x * y], [z, (x + y)^2]]), + [(x^2 + y^2) // R(1), (x^3 + y^3) // (z - x * y), R(1) // (z + x + y), z // x], + 0.99, + ) == [true, true, true, false] - @test field_contains( - RationalFunctionField([[ - x + y + z, - x^2 + y^2 + z^2, - (x + y + z)^2, - x^3 + y^3 + z^3, - ]]), - [x + y + z // 1, x * y * z // 1, x + y + 2 * z // 1, x // (y + z)], - 0.99, - ) == [true, true, false, false] + @test field_contains( + RationalFunctionField([[ + x + y + z, + x^2 + y^2 + z^2, + (x + y + z)^2, + x^3 + y^3 + z^3, + ]]), + [x + y + z // 1, x * y * z // 1, x + y + 2 * z // 1, x // (y + z)], + 0.99, + ) == [true, true, false, false] - @test field_contains( - RationalFunctionField([x + y + z // 1, x * y + y * z + z * x // 1, x * y * z // 1]), - [x^2 + y^2 + z^2, x^6 + y^6 + z^6, x - y + z, x^2 - y^2 + z^2], - 0.99, - ) == [true, true, false, false] + @test field_contains( + RationalFunctionField([ + x + y + z // 1, + x * y + y * z + z * x // 1, + x * y * z // 1, + ]), + [x^2 + y^2 + z^2, x^6 + y^6 + z^6, x - y + z, x^2 - y^2 + z^2], + 0.99, + ) == [true, true, false, false] + end end diff --git a/test/check_primality_zerodim.jl b/test/check_primality_zerodim.jl index 6178efa89..2ac4005d5 100644 --- a/test/check_primality_zerodim.jl +++ b/test/check_primality_zerodim.jl @@ -1,11 +1,13 @@ -@testset "Primality check (zerodim subroutine)" begin - R, (x, y) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y"]) +if GROUP == "All" || GROUP == "Core" + @testset "Primality check (zerodim subroutine)" begin + R, (x, y) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y"]) - @test check_primality_zerodim([x^2 - 1, y^2 - 4]) == false + @test check_primality_zerodim([x^2 - 1, y^2 - 4]) == false - @test check_primality_zerodim([(x + 5) * (x^3 - 7), y - 3]) == false + @test check_primality_zerodim([(x + 5) * (x^3 - 7), y - 3]) == false - @test check_primality_zerodim([x^3 - 5, y - 1]) == true + @test check_primality_zerodim([x^3 - 5, y - 1]) == true - @test check_primality_zerodim([x^2 + 1, y^3 - 3 * x + x + 5]) == true + @test check_primality_zerodim([x^2 + 1, y^3 - 3 * x + x + 5]) == true + end end diff --git a/test/common_ring.jl b/test/common_ring.jl index 91512b439..68c3e5bde 100644 --- a/test/common_ring.jl +++ b/test/common_ring.jl @@ -1,39 +1,42 @@ -@testset "Computing common ring for the PB-reduction" begin - ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - R, (y_2, y_5, c) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_2", "y(t)_5", "c"]) - p = y_2^2 + c * y_5 - (r, der) = common_ring(p, pbr) - @test Set(map(var_to_str, gens(r))) == - Set(["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "y(t)_5", "c", "a"]) +if GROUP == "All" || GROUP == "Core" + @testset "Computing common ring for the PB-reduction" begin + ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + R, (y_2, y_5, c) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_2", "y(t)_5", "c"]) + p = y_2^2 + c * y_5 + (r, der) = common_ring(p, pbr) + @test Set(map(var_to_str, gens(r))) == + Set(["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "y(t)_5", "c", "a"]) - ode = @ODEmodel( - x1'(t) = x3(t), - x2'(t) = a * x2(t), - x3'(t) = x1(t), - y1(t) = x1(t), - y2(t) = x2(t) + u(t) - ) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - R, (y1_0, y2_3, u_3) = Nemo.PolynomialRing(Nemo.QQ, ["y1(t)_0", "y2(t)_3", "u(t)_3"]) - p = y1_0 + y2_3 + u_3 - (r, der) = common_ring(p, pbr) - @test Set([var_to_str(v) for v in gens(r)]) == Set([ - "y1(t)_0", - "y1(t)_1", - "y1(t)_2", - "y1(t)_3", - "y1(t)_4", - "y2(t)_0", - "y2(t)_1", - "y2(t)_2", - "y2(t)_3", - "u(t)_0", - "u(t)_1", - "u(t)_2", - "u(t)_3", - "a", - ]) + ode = @ODEmodel( + x1'(t) = x3(t), + x2'(t) = a * x2(t), + x3'(t) = x1(t), + y1(t) = x1(t), + y2(t) = x2(t) + u(t) + ) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + R, (y1_0, y2_3, u_3) = + Nemo.PolynomialRing(Nemo.QQ, ["y1(t)_0", "y2(t)_3", "u(t)_3"]) + p = y1_0 + y2_3 + u_3 + (r, der) = common_ring(p, pbr) + @test Set([var_to_str(v) for v in gens(r)]) == Set([ + "y1(t)_0", + "y1(t)_1", + "y1(t)_2", + "y1(t)_3", + "y1(t)_4", + "y2(t)_0", + "y2(t)_1", + "y2(t)_2", + "y2(t)_3", + "u(t)_0", + "u(t)_1", + "u(t)_2", + "u(t)_3", + "a", + ]) + end end diff --git a/test/constructive_membership.jl b/test/constructive_membership.jl index d80aca25f..d568a52b4 100644 --- a/test/constructive_membership.jl +++ b/test/constructive_membership.jl @@ -1,125 +1,131 @@ -@testset "Constructive field membership" begin - R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) +if GROUP == "All" || GROUP == "Core" + @testset "Constructive field membership" begin + R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) - generators = [x^2, x^3] - to_be_reduced = [x^2, x, 3one(R), zero(R)] + generators = [x^2, x^3] + to_be_reduced = [x^2, x, 3one(R), zero(R)] - memberships, remainders, relations_between_tags, tag_to_gen = - StructuralIdentifiability.check_constructive_field_membership( - StructuralIdentifiability.RationalFunctionField(generators), - map(f -> f // one(f), to_be_reduced), - tag_names = ["T1", "T2"], - ) - tags = gens(base_ring(parent(first(remainders)))) - - @test length(tags) == 2 - @test all(memberships) - @test map(string, remainders) == ["T1", "T1^2//T2", "3", "0"] - @test tag_to_gen == Dict(tags[1] => x^2, tags[2] => x^3) - @test length(relations_between_tags) == 1 - @test string(relations_between_tags[1]) == "T1^3 - T2^2" + memberships, remainders, relations_between_tags, tag_to_gen = + StructuralIdentifiability.check_constructive_field_membership( + StructuralIdentifiability.RationalFunctionField(generators), + map(f -> f // one(f), to_be_reduced), + tag_names = ["T1", "T2"], + ) + tags = gens(base_ring(parent(first(remainders)))) - cases = [] + @test length(tags) == 2 + @test all(memberships) + @test map(string, remainders) == ["T1", "T1^2//T2", "3", "0"] + @test tag_to_gen == Dict(tags[1] => x^2, tags[2] => x^3) + @test length(relations_between_tags) == 1 + @test string(relations_between_tags[1]) == "T1^3 - T2^2" - R, (T1,) = PolynomialRing(Nemo.QQ, ["T1"]) - append!( - cases, - [(generators = [T1^2], to_be_reduced = [T1, T1^2], memberships = Bool[0, 1])], - ) + cases = [] - R, (T1, t, _t) = PolynomialRing(Nemo.QQ, ["T1", "t", "_t"]) - append!( - cases, - [( - generators = [T1, t, _t], - to_be_reduced = [_t, t, T1 * t * _t], - memberships = Bool[1, 1, 1], - )], - ) + R, (T1,) = PolynomialRing(Nemo.QQ, ["T1"]) + append!( + cases, + [(generators = [T1^2], to_be_reduced = [T1, T1^2], memberships = Bool[0, 1])], + ) - R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) - append!( - cases, - [ - ( - generators = [(x - 1) // R(1), R(1) // (x^5 - 1), x // R(1)], - to_be_reduced = [ - (x^4 + x^3 + x^2 + x + 1) // one(R), - x // R(1), - R(33) // x^2, - ], + R, (T1, t, _t) = PolynomialRing(Nemo.QQ, ["T1", "t", "_t"]) + append!( + cases, + [( + generators = [T1, t, _t], + to_be_reduced = [_t, t, T1 * t * _t], memberships = Bool[1, 1, 1], - ), - ( - generators = [(x^10 + x^9 + x^2 + 1) // (x^7 - x^6 - x^3 + 1)], - to_be_reduced = [x // one(R), 2x // one(R), -3x // one(R)], - memberships = Bool[0, 0, 0], - ), - (generators = [x^2], to_be_reduced = [x, x^88], memberships = Bool[0, 1]), - ], - ) + )], + ) - R, (x, y, z) = PolynomialRing(Nemo.QQ, ["x", "y", "z"]) - append!( - cases, - [ - (generators = [x, y], to_be_reduced = [x^2 + y^2, z], memberships = Bool[1, 0]), - ( - generators = [x^2 + y^2, x^3 + y^3, x^4 + y^4], - to_be_reduced = [x * y, x + y, x + y + 1, x + y + z], - memberships = Bool[1, 1, 1, 0], - ), - ( - generators = [(x + y + z)^2, (x + y + z)^3, (x + y + z)^4], - to_be_reduced = [(x + y + z)^18, x + 1, y + 2, z + 3], - memberships = Bool[1, 0, 0, 0], - ), - ], - ) + R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) + append!( + cases, + [ + ( + generators = [(x - 1) // R(1), R(1) // (x^5 - 1), x // R(1)], + to_be_reduced = [ + (x^4 + x^3 + x^2 + x + 1) // one(R), + x // R(1), + R(33) // x^2, + ], + memberships = Bool[1, 1, 1], + ), + ( + generators = [(x^10 + x^9 + x^2 + 1) // (x^7 - x^6 - x^3 + 1)], + to_be_reduced = [x // one(R), 2x // one(R), -3x // one(R)], + memberships = Bool[0, 0, 0], + ), + (generators = [x^2], to_be_reduced = [x, x^88], memberships = Bool[0, 1]), + ], + ) - # NOTE: in this case it actually matter to cancel out the gcd after - # computing the normal forms - R, (a, b, y, x2, c, x1) = PolynomialRing(Nemo.QQ, ["a", "b", "y", "x2", "c", "x1"]) - append!( - cases, - [ - ( - generators = [ - x1 // one(R), - a // one(R), - (a * c + c^2) // one(R), - c // x2, - x2 // (a + b), - ], - to_be_reduced = [ - (a * c + c^2 + x1) // (a * c + c^2), - (a * c + c^2 + x1) // (a^2 + a * b + a * c + b * c), - (a * x2 + a * x1 + b * x1) // x2, - ], - memberships = Bool[1, 1, 1], - ), - ], - ) + R, (x, y, z) = PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + append!( + cases, + [ + ( + generators = [x, y], + to_be_reduced = [x^2 + y^2, z], + memberships = Bool[1, 0], + ), + ( + generators = [x^2 + y^2, x^3 + y^3, x^4 + y^4], + to_be_reduced = [x * y, x + y, x + y + 1, x + y + z], + memberships = Bool[1, 1, 1, 0], + ), + ( + generators = [(x + y + z)^2, (x + y + z)^3, (x + y + z)^4], + to_be_reduced = [(x + y + z)^18, x + 1, y + 2, z + 3], + memberships = Bool[1, 0, 0, 0], + ), + ], + ) - for case in cases - generators = case.generators - to_be_reduced = case.to_be_reduced - memberships, remainders, relations_between_tags, tag_to_gen = - StructuralIdentifiability.check_constructive_field_membership( - StructuralIdentifiability.RationalFunctionField(generators), - map(f -> f // one(f), to_be_reduced), - ) - @test memberships == case.memberships - tags = gens(base_ring(parent(first(remainders)))) - evaluate_tags = poly -> evaluate(poly, [tag_to_gen[tag] for tag in tags]) - for i in 1:length(relations_between_tags) - @test iszero(evaluate_tags(relations_between_tags[i])) - end - for i in 1:length(remainders) - if !memberships[i] - continue + # NOTE: in this case it actually matter to cancel out the gcd after + # computing the normal forms + R, (a, b, y, x2, c, x1) = PolynomialRing(Nemo.QQ, ["a", "b", "y", "x2", "c", "x1"]) + append!( + cases, + [ + ( + generators = [ + x1 // one(R), + a // one(R), + (a * c + c^2) // one(R), + c // x2, + x2 // (a + b), + ], + to_be_reduced = [ + (a * c + c^2 + x1) // (a * c + c^2), + (a * c + c^2 + x1) // (a^2 + a * b + a * c + b * c), + (a * x2 + a * x1 + b * x1) // x2, + ], + memberships = Bool[1, 1, 1], + ), + ], + ) + + for case in cases + generators = case.generators + to_be_reduced = case.to_be_reduced + memberships, remainders, relations_between_tags, tag_to_gen = + StructuralIdentifiability.check_constructive_field_membership( + StructuralIdentifiability.RationalFunctionField(generators), + map(f -> f // one(f), to_be_reduced), + ) + @test memberships == case.memberships + tags = gens(base_ring(parent(first(remainders)))) + evaluate_tags = poly -> evaluate(poly, [tag_to_gen[tag] for tag in tags]) + for i in 1:length(relations_between_tags) + @test iszero(evaluate_tags(relations_between_tags[i])) + end + for i in 1:length(remainders) + if !memberships[i] + continue + end + @test iszero(evaluate_tags(remainders[i]) - to_be_reduced[i]) end - @test iszero(evaluate_tags(remainders[i]) - to_be_reduced[i]) end end end diff --git a/test/decompose_derivative.jl b/test/decompose_derivative.jl index d44311707..7f2c8c060 100644 --- a/test/decompose_derivative.jl +++ b/test/decompose_derivative.jl @@ -1,12 +1,14 @@ -@testset "Decomposing derivative" begin - cases = [ - ["yy_11", ["y", "yy", "yy_"], ("yy", 11)], - ["xx_xx_xx_0", ["xx", "x", "xx_xx_xx"], ("xx_xx_xx", 0)], - ["abc154f", ["ab", "abc"], nothing], - ["c_1542673", ["a", "b", "c"], ("c", 1542673)], - ["a", ["a"], nothing], - ] - for c in cases - @test decompose_derivative(c[1], c[2]) == c[3] +if GROUP == "All" || GROUP == "Core" + @testset "Decomposing derivative" begin + cases = [ + ["yy_11", ["y", "yy", "yy_"], ("yy", 11)], + ["xx_xx_xx_0", ["xx", "x", "xx_xx_xx"], ("xx_xx_xx", 0)], + ["abc154f", ["ab", "abc"], nothing], + ["c_1542673", ["a", "b", "c"], ("c", 1542673)], + ["a", ["a"], nothing], + ] + for c in cases + @test decompose_derivative(c[1], c[2]) == c[3] + end end end diff --git a/test/det_minor_expansion.jl b/test/det_minor_expansion.jl index 4240feae7..b21509f59 100644 --- a/test/det_minor_expansion.jl +++ b/test/det_minor_expansion.jl @@ -1,9 +1,11 @@ -@testset "Determinant by minor expansion" begin - for d in 1:5 - for testcase in 1:10 - mat_space = Nemo.MatrixSpace(Nemo.QQ, d, d) - rnd_matrix = mat_space([mod(rand(Int), 1000) for i in 1:d, j in 1:d]) - @test det(rnd_matrix) == det_minor_expansion(rnd_matrix) +if GROUP == "All" || GROUP == "Core" + @testset "Determinant by minor expansion" begin + for d in 1:5 + for testcase in 1:10 + mat_space = Nemo.MatrixSpace(Nemo.QQ, d, d) + rnd_matrix = mat_space([mod(rand(Int), 1000) for i in 1:d, j in 1:d]) + @test det(rnd_matrix) == det_minor_expansion(rnd_matrix) + end end end end diff --git a/test/diff_sequence_solution.jl b/test/diff_sequence_solution.jl index 1df86d8f7..c23475807 100644 --- a/test/diff_sequence_solution.jl +++ b/test/diff_sequence_solution.jl @@ -1,103 +1,113 @@ -@testset "Computing variations around a sequence solution" begin - # Computing sensitivities directly be explicitly writing down Lie derivatives - function use_lie_derivatives( - dds::ODE{P}, - params::Dict{P, T}, - ic::Dict{P, T}, - inputs::Dict{P, Array{T, 1}}, - num_terms::Int, - ) where {T <: Generic.FieldElem, P <: MPolyElem{T}} - newvars = [var_to_str(v) for v in gens(dds.poly_ring)] - append!(newvars, [var_to_str(v) * "$i" for v in dds.u_vars for i in 1:num_terms]) - R, _ = - StructuralIdentifiability.Nemo.PolynomialRing(base_ring(dds.poly_ring), newvars) - explicit_sol = merge( - Dict( - parent_ring_change(x, R) => Vector{Any}([parent_ring_change(x, R)]) for - (x, eq) in dds.x_equations - ), - Dict( - parent_ring_change(y, R) => Vector{Any}([parent_ring_change(eq, R)]) for - (y, eq) in dds.y_equations - ), - ) - time_step = merge( - Dict( - parent_ring_change(x, R) => parent_ring_change(eq, R) for - (x, eq) in dds.x_equations - ), - Dict( - parent_ring_change(p, R) => parent_ring_change(p, R) for - p in dds.parameters - ), - Dict( - parent_ring_change(u, R) => str_to_var(var_to_str(u) * "1", R) for - u in dds.u_vars - ), - Dict( - str_to_var(s * "$i", R) => str_to_var(s * "$(i + 1)", R) for - s in map(var_to_str, dds.u_vars) for i in 1:(num_terms - 1) - ), - ) - eval_dict = merge( - Dict(parent_ring_change(p, R) => v for (p, v) in params), - Dict(parent_ring_change(x, R) => v for (x, v) in ic), - Dict(parent_ring_change(u, R) => val[1] for (u, val) in inputs), - Dict( - str_to_var(var_to_str(u) * "$i", R) => inputs[u][i + 1] for - u in dds.u_vars for i in 1:(num_terms - 1) - ), - ) - generalized_parameters = - [parent_ring_change(p, R) for p in vcat(dds.x_vars, dds.parameters)] - for i in 2:num_terms - for (k, v) in explicit_sol - push!(explicit_sol[k], eval_at_dict(v[end], time_step)) +if GROUP == "All" || GROUP == "Core" + @testset "Computing variations around a sequence solution" begin + # Computing sensitivities directly be explicitly writing down Lie derivatives + function use_lie_derivatives( + dds::ODE{P}, + params::Dict{P, T}, + ic::Dict{P, T}, + inputs::Dict{P, Array{T, 1}}, + num_terms::Int, + ) where {T <: Generic.FieldElem, P <: MPolyElem{T}} + newvars = [var_to_str(v) for v in gens(dds.poly_ring)] + append!( + newvars, + [var_to_str(v) * "$i" for v in dds.u_vars for i in 1:num_terms], + ) + R, _ = StructuralIdentifiability.Nemo.PolynomialRing( + base_ring(dds.poly_ring), + newvars, + ) + explicit_sol = merge( + Dict( + parent_ring_change(x, R) => Vector{Any}([parent_ring_change(x, R)]) + for (x, eq) in dds.x_equations + ), + Dict( + parent_ring_change(y, R) => + Vector{Any}([parent_ring_change(eq, R)]) for + (y, eq) in dds.y_equations + ), + ) + time_step = merge( + Dict( + parent_ring_change(x, R) => parent_ring_change(eq, R) for + (x, eq) in dds.x_equations + ), + Dict( + parent_ring_change(p, R) => parent_ring_change(p, R) for + p in dds.parameters + ), + Dict( + parent_ring_change(u, R) => str_to_var(var_to_str(u) * "1", R) for + u in dds.u_vars + ), + Dict( + str_to_var(s * "$i", R) => str_to_var(s * "$(i + 1)", R) for + s in map(var_to_str, dds.u_vars) for i in 1:(num_terms - 1) + ), + ) + eval_dict = merge( + Dict(parent_ring_change(p, R) => v for (p, v) in params), + Dict(parent_ring_change(x, R) => v for (x, v) in ic), + Dict(parent_ring_change(u, R) => val[1] for (u, val) in inputs), + Dict( + str_to_var(var_to_str(u) * "$i", R) => inputs[u][i + 1] for + u in dds.u_vars for i in 1:(num_terms - 1) + ), + ) + generalized_parameters = + [parent_ring_change(p, R) for p in vcat(dds.x_vars, dds.parameters)] + for i in 2:num_terms + for (k, v) in explicit_sol + push!(explicit_sol[k], eval_at_dict(v[end], time_step)) + end end - end - part_diffs = - Dict((f, p) => [] for f in keys(explicit_sol) for p in generalized_parameters) - for i in 1:num_terms - for (f, ders) in explicit_sol - for p in generalized_parameters - push!( - part_diffs[(f, p)], - eval_at_dict(derivative(ders[i], p), eval_dict), - ) + part_diffs = Dict( + (f, p) => [] for f in keys(explicit_sol) for p in generalized_parameters + ) + for i in 1:num_terms + for (f, ders) in explicit_sol + for p in generalized_parameters + push!( + part_diffs[(f, p)], + eval_at_dict(derivative(ders[i], p), eval_dict), + ) + end end end + return Dict( + ( + parent_ring_change(k[1], dds.poly_ring), + parent_ring_change(k[2], dds.poly_ring), + ) => res for (k, res) in part_diffs + ) end - return Dict( - ( - parent_ring_change(k[1], dds.poly_ring), - parent_ring_change(k[2], dds.poly_ring), - ) => res for (k, res) in part_diffs - ) - end - locQQ = StructuralIdentifiability.Nemo.QQ + locQQ = StructuralIdentifiability.Nemo.QQ - ode = @ODEmodel(a'(t) = a(t)^2 + b, y(t) = 1 / (a(t) * c(t))) - params = Dict(b => locQQ(1)) - ic = Dict(a => locQQ(2)) - inputs = Dict(c => [locQQ(1), locQQ(-2), locQQ(3), locQQ(-4), locQQ(5)]) - seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 4) - diff_y = differentiate_sequence_output(ode, params, ic, inputs, 4) - lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 4) - merged = merge(diff_sol, diff_y) - @test merged == lie_ders_sol + ode = @ODEmodel(a'(t) = a(t)^2 + b, y(t) = 1 / (a(t) * c(t))) + params = Dict(b => locQQ(1)) + ic = Dict(a => locQQ(2)) + inputs = Dict(c => [locQQ(1), locQQ(-2), locQQ(3), locQQ(-4), locQQ(5)]) + seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 4) + diff_y = differentiate_sequence_output(ode, params, ic, inputs, 4) + lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 4) + merged = merge(diff_sol, diff_y) + @test merged == lie_ders_sol - ode = @ODEmodel( - a'(t) = (23 * k1 * a(t) - 7 * b(t)^3) // (a(t)^2 + b(t)^2) - c(t)^3 * k1 * b(t), - b'(t) = a(t) + 17 * (b(t) - c(t))^2 + 1 // (a(t) + b(t) - k2), - y(t) = (a(t) + b(t) - c(t)) // (a(t)^2 + k2) - ) - params = Dict(k1 => locQQ(1), k2 => locQQ(2)) - ic = Dict(a => locQQ(3), b => locQQ(-4)) - inputs = Dict(c => [locQQ(5), locQQ(-6), locQQ(7), locQQ(-8)]) - seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 2) - diff_y = differentiate_sequence_output(ode, params, ic, inputs, 2) - lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 2) - merged = merge(diff_sol, diff_y) - @test merged == lie_ders_sol + ode = @ODEmodel( + a'(t) = + (23 * k1 * a(t) - 7 * b(t)^3) // (a(t)^2 + b(t)^2) - c(t)^3 * k1 * b(t), + b'(t) = a(t) + 17 * (b(t) - c(t))^2 + 1 // (a(t) + b(t) - k2), + y(t) = (a(t) + b(t) - c(t)) // (a(t)^2 + k2) + ) + params = Dict(k1 => locQQ(1), k2 => locQQ(2)) + ic = Dict(a => locQQ(3), b => locQQ(-4)) + inputs = Dict(c => [locQQ(5), locQQ(-6), locQQ(7), locQQ(-8)]) + seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 2) + diff_y = differentiate_sequence_output(ode, params, ic, inputs, 2) + lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 2) + merged = merge(diff_sol, diff_y) + @test merged == lie_ders_sol + end end diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index db9e6b99a..f9a191bae 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -1,245 +1,255 @@ -#------- Auxiliary functions -------------------------------------------------- - -function diff_sol_Lie_derivatives(ode::ODE, params, ic, inputs, prec::Int) - # creating a new ring with variables for the derivatives of u - new_varnames = map(var_to_str, gens(ode.poly_ring)) - if length(ode.u_vars) > 0 - append!( - new_varnames, - [var_to_str(u) * "_$i" for u in ode.u_vars for i in 0:(prec - 1)], - ) - end - new_ring, vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_varnames) - - # mapping everything to the new ring - eval_point = Dict(v => switch_ring(v, new_ring) for v in gens(ode.poly_ring)) - for u in ode.u_vars - eval_point[u] = str_to_var(var_to_str(u) * "_0", new_ring) - end +if GROUP == "All" || GROUP == "Core" + #------- Auxiliary functions -------------------------------------------------- - new_eqs = Dict() - for (x, f) in ode.x_equations - new_eqs[str_to_var(var_to_str(x), new_ring)] = eval_at_dict(f, eval_point) - end - params, ic = - map(d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), [params, ic]) - - # computing Lie derivatives - derivation = copy(new_eqs) - for u in ode.u_vars - for i in 0:(prec - 2) - derivation[str_to_var(var_to_str(u) * "_$i", new_ring)] = - str_to_var(var_to_str(u) * "_$(i + 1)", new_ring) - end - end - Lie_derivatives = Dict() - for (y, g) in ode.y_equations - Lie_derivatives[y] = Array{Any, 1}([eval_at_dict(g, eval_point)]) - for i in 1:prec - push!( - Lie_derivatives[y], - sum( - derivative(Lie_derivatives[y][end], v) * get(derivation, v, 0) for - v in gens(new_ring) - ), + function diff_sol_Lie_derivatives(ode::ODE, params, ic, inputs, prec::Int) + # creating a new ring with variables for the derivatives of u + new_varnames = map(var_to_str, gens(ode.poly_ring)) + if length(ode.u_vars) > 0 + append!( + new_varnames, + [var_to_str(u) * "_$i" for u in ode.u_vars for i in 0:(prec - 1)], ) end - end + new_ring, vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_varnames) - # producing the result - eval_point = merge(params, ic) - for u in ode.u_vars - for i in 1:prec - eval_point[str_to_var(var_to_str(u) * "_$(i - 1)", new_ring)] = - inputs[u][i] * factorial(i - 1) + # mapping everything to the new ring + eval_point = Dict(v => switch_ring(v, new_ring) for v in gens(ode.poly_ring)) + for u in ode.u_vars + eval_point[u] = str_to_var(var_to_str(u) * "_0", new_ring) end - end - result = Dict() - for y in ode.y_vars - result[y] = Dict() - for v in vcat(ode.x_vars, ode.parameters) - result[y][v] = [] - for j in 1:prec + new_eqs = Dict() + for (x, f) in ode.x_equations + new_eqs[str_to_var(var_to_str(x), new_ring)] = eval_at_dict(f, eval_point) + end + params, ic = map( + d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), + [params, ic], + ) + + # computing Lie derivatives + derivation = copy(new_eqs) + for u in ode.u_vars + for i in 0:(prec - 2) + derivation[str_to_var(var_to_str(u) * "_$i", new_ring)] = + str_to_var(var_to_str(u) * "_$(i + 1)", new_ring) + end + end + Lie_derivatives = Dict() + for (y, g) in ode.y_equations + Lie_derivatives[y] = Array{Any, 1}([eval_at_dict(g, eval_point)]) + for i in 1:prec push!( - result[y][v], - eval_at_dict( - derivative(Lie_derivatives[y][j], str_to_var("$v", new_ring)), - eval_point, + Lie_derivatives[y], + sum( + derivative(Lie_derivatives[y][end], v) * get(derivation, v, 0) for + v in gens(new_ring) ), ) end end - end - return result -end + # producing the result + eval_point = merge(params, ic) + for u in ode.u_vars + for i in 1:prec + eval_point[str_to_var(var_to_str(u) * "_$(i - 1)", new_ring)] = + inputs[u][i] * factorial(i - 1) + end + end -#------------------------------------------------------------------------------ + result = Dict() + for y in ode.y_vars + result[y] = Dict() + for v in vcat(ode.x_vars, ode.parameters) + result[y][v] = [] + for j in 1:prec + push!( + result[y][v], + eval_at_dict( + derivative(Lie_derivatives[y][j], str_to_var("$v", new_ring)), + eval_point, + ), + ) + end + end + end -function rand_poly(deg, vars) - if deg == 0 - return parent(vars[1])(1) + return result end - result = 0 - indices = collect(1:length(vars)) - monomials = [] - for d in 0:deg - for subs in StructuralIdentifiability.IterTools.subsets(indices, d) - push!(monomials, subs) + + #------------------------------------------------------------------------------ + + function rand_poly(deg, vars) + if deg == 0 + return parent(vars[1])(1) + end + result = 0 + indices = collect(1:length(vars)) + monomials = [] + for d in 0:deg + for subs in StructuralIdentifiability.IterTools.subsets(indices, d) + push!(monomials, subs) + end end - end - for subs in monomials - monom = rand(-50:50) - for v_ind in subs - monom *= vars[v_ind] + for subs in monomials + monom = rand(-50:50) + for v_ind in subs + monom *= vars[v_ind] + end + result += monom end - result += monom + + return result end - return result -end + #------------------------------------------------------------------------------ + + @testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin + test_cases = [] + P = fmpq_mpoly + DType = Union{P, Generic.Frac{P}} + + ode = @ODEmodel(x'(t) = x(t) + a, y(t) = x(t)^2) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{fmpq, 1}}(), + :prec => 20, + ), + ) + + ode = @ODEmodel(x'(t) = x(t)^2 + a, y1(t) = x(t) + a^2, y2(t) = x(t)^3) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{fmpq, 1}}(), + :prec => 20, + ), + ) + + ode = @ODEmodel( + x'(t) = x(t)^2 + 2 * x(t) * y(t) - 3 * a * y(t), + y'(t) = x(t)^2 + a * b - b^2 + 4 * b * x(t), + y1(t) = a * x(t), + y2(t) = b * y(t)^2 - y(t) + ) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10)), y => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10)), b => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{fmpq, 1}}(), + :prec => 8, + ), + ) + + ode = @ODEmodel(x'(t) = u(t) + a, y(t) = x(t)) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict(u => [Nemo.QQ(rand(-3:3)) for i in 1:20]), + :prec => 20, + ), + ) -#------------------------------------------------------------------------------ - -@testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin - test_cases = [] - P = fmpq_mpoly - DType = Union{P, Generic.Frac{P}} - - ode = @ODEmodel(x'(t) = x(t) + a, y(t) = x(t)^2) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), - :prec => 20, - ), - ) - - ode = @ODEmodel(x'(t) = x(t)^2 + a, y1(t) = x(t) + a^2, y2(t) = x(t)^3) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), - :prec => 20, - ), - ) - - ode = @ODEmodel( - x'(t) = x(t)^2 + 2 * x(t) * y(t) - 3 * a * y(t), - y'(t) = x(t)^2 + a * b - b^2 + 4 * b * x(t), - y1(t) = a * x(t), - y2(t) = b * y(t)^2 - y(t) - ) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10)), y => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10)), b => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), - :prec => 8, - ), - ) - - ode = @ODEmodel(x'(t) = u(t) + a, y(t) = x(t)) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict(u => [Nemo.QQ(rand(-3:3)) for i in 1:20]), - :prec => 20, - ), - ) - - F = Nemo.Native.GF(2^31 - 1) - P = gfp_mpoly - DType = Union{P, Generic.Frac{P}} - - varnames = vcat( - ["x_$i" for i in 1:3], - ["p_$i" for i in 1:3], - ["u_$i" for i in 1:2], - ["y_$i" for i in 1:3], - ) - R, vars = Nemo.PolynomialRing(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}(vars[i] => rand_poly(1, vars[1:8]) for i in 1:3), - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), - vars[7:8], + F = Nemo.Native.GF(2^31 - 1) + P = gfp_mpoly + DType = Union{P, Generic.Frac{P}} + + varnames = vcat( + ["x_$i" for i in 1:3], + ["p_$i" for i in 1:3], + ["u_$i" for i in 1:2], + ["y_$i" for i in 1:3], + ) + R, vars = Nemo.PolynomialRing(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}(vars[i] => rand_poly(1, vars[1:8]) for i in 1:3), + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), + vars[7:8], + ), + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), + :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), + :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), + :prec => 6, ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), - :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), - :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), - :prec => 6, - ), - ) - - varnames = vcat( - ["x_$i" for i in 1:3], - ["p_$i" for i in 1:3], - ["u_$i" for i in 1:2], - ["y_$i" for i in 1:3], - ) - R, vars = Nemo.PolynomialRing(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 1:3), - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), - vars[7:8], + ) + + varnames = vcat( + ["x_$i" for i in 1:3], + ["p_$i" for i in 1:3], + ["u_$i" for i in 1:2], + ["y_$i" for i in 1:3], + ) + R, vars = Nemo.PolynomialRing(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 1:3), + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), + vars[7:8], + ), + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), + :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), + :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), + :prec => 6, ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), - :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), - :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), - :prec => 6, - ), - ) - - varnames = vcat(["x_$i" for i in 1:2], ["p_$i" for i in 1:2], "u", ["y_1", "y_2"]) - R, vars = Nemo.PolynomialRing(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}( - vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for i in 1:2 + ) + + varnames = vcat(["x_$i" for i in 1:2], ["p_$i" for i in 1:2], "u", ["y_1", "y_2"]) + R, vars = Nemo.PolynomialRing(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}( + vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for + i in 1:2 + ), + Dict{P, DType}(vars[i] => rand_poly(1, vars[1:5]) for i in 6:7), + [vars[5]], ), - Dict{P, DType}(vars[i] => rand_poly(1, vars[1:5]) for i in 6:7), - [vars[5]], + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:2), + :param_vals => Dict(vars[i + 2] => F(rand(1:50)) for i in 1:2), + :inputs => Dict(vars[5] => [F(rand(-30:30)) for i in 1:4]), + :prec => 4, ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:2), - :param_vals => Dict(vars[i + 2] => F(rand(1:50)) for i in 1:2), - :inputs => Dict(vars[5] => [F(rand(-30:30)) for i in 1:4]), - :prec => 4, - ), - ) - - for case in test_cases - ode, prec = case[:ODE], case[:prec] - @time sol1 = - differentiate_output(ode, case[:param_vals], case[:ic], case[:inputs], prec) - sol2 = - diff_sol_Lie_derivatives(ode, case[:param_vals], case[:ic], case[:inputs], prec) - for y in ode.y_vars - for v in vcat(ode.x_vars, ode.parameters) - @test sol2[y][v] == [ - base_ring(ode.poly_ring)(coeff(sol1[y][v], j) * factorial(j)) for - j in 0:(prec - 1) - ] + ) + + for case in test_cases + ode, prec = case[:ODE], case[:prec] + @time sol1 = + differentiate_output(ode, case[:param_vals], case[:ic], case[:inputs], prec) + sol2 = diff_sol_Lie_derivatives( + ode, + case[:param_vals], + case[:ic], + case[:inputs], + prec, + ) + for y in ode.y_vars + for v in vcat(ode.x_vars, ode.parameters) + @test sol2[y][v] == [ + base_ring(ode.poly_ring)(coeff(sol1[y][v], j) * factorial(j)) for + j in 0:(prec - 1) + ] + end end end end diff --git a/test/diffreduction.jl b/test/diffreduction.jl index b624438b2..338a29be8 100644 --- a/test/diffreduction.jl +++ b/test/diffreduction.jl @@ -1,162 +1,166 @@ -@testset "Differential reduction" begin - ode = @ODEmodel(x'(t) = a * x(t), y(t) = x(t)) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4"]) - res = diffreduce(y_5, pbr) - @test res == str_to_var("a", parent(res))^5 * str_to_var("y(t)_0", parent(res)) - res = diffreduce(y_4, pbr) - @test res == str_to_var("a", parent(res))^4 * str_to_var("y(t)_0", parent(res)) - res = diffreduce(y_4^2, pbr) - @test res == str_to_var("a", parent(res))^8 * str_to_var("y(t)_0", parent(res))^2 +if GROUP == "All" || GROUP == "Core" + @testset "Differential reduction" begin + ode = @ODEmodel(x'(t) = a * x(t), y(t) = x(t)) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_5, y_4) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4"]) + res = diffreduce(y_5, pbr) + @test res == str_to_var("a", parent(res))^5 * str_to_var("y(t)_0", parent(res)) + res = diffreduce(y_4, pbr) + @test res == str_to_var("a", parent(res))^4 * str_to_var("y(t)_0", parent(res)) + res = diffreduce(y_4^2, pbr) + @test res == str_to_var("a", parent(res))^8 * str_to_var("y(t)_0", parent(res))^2 - ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = -x1(t), y(t) = x1(t) + u(t)) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4, u_10) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) - res = diffreduce(y_4 + u_10, pbr) - @test res == - str_to_var("u(t)_10", parent(res)) + str_to_var("y(t)_0", parent(res)) - - str_to_var("u(t)_0", parent(res)) + str_to_var("u(t)_4", parent(res)) + ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = -x1(t), y(t) = x1(t) + u(t)) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_5, y_4, u_10) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) + res = diffreduce(y_4 + u_10, pbr) + @test res == + str_to_var("u(t)_10", parent(res)) + str_to_var("y(t)_0", parent(res)) - + str_to_var("u(t)_0", parent(res)) + str_to_var("u(t)_4", parent(res)) - # Next two verified with Maple - # Mizuka's example - R, (y_0, y_1, y_2, y_3, u_0, u_1, u_2) = - Nemo.PolynomialRing(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) - pbr = PBRepresentation( - ["y"], - ["u"], - Array{String, 1}(), - Dict("y" => 2), - Dict( - "y" => - 27 * y_0^9 + 27 * y_0^6 * y_1^3 - 54 * u_0 * y_0^6 + - 54 * y_0^7 + - 54 * y_0^6 * y_1 - 27 * y_0^5 * y_1^2 - 27 * y_0^4 * u_1 * y_1^2 + - 27 * y_0^4 * y_1^3 + - 27 * y_0^4 * y_1^2 * y_2 + - 27 * u_0^2 * y_0^3 - 54 * u_0 * y_0^4 - 54 * u_0 * y_0^3 * y_1 + - 27 * y_0^5 + - 54 * y_0^4 * y_1 + - 18 * y_0^3 * u_1 * y_1 + - 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + 9 * y_0^2 * u_1^2 * y_1 - - 18 * y_0^2 * u_1 * y_1^2 - 18 * y_0^2 * u_1 * y_1 * y_2 + - 9 * y_0^2 * y_1^3 + - 18 * y_0^2 * y_1^2 * y_2 + - 9 * y_0^2 * y_1 * y_2^2 + - 4 * y_0^3 - 3 * y_0 * u_1^2 + - 6 * y_0 * u_1 * y_1 + - 6 * y_0 * u_1 * y_2 - 3 * y_0 * y_1^2 - 6 * y_0 * y_1 * y_2 - - 3 * y_0 * y_2^2 - u_1^3 + - 3 * u_1^2 * y_1 + - 3 * u_1^2 * y_2 - 3 * u_1 * y_1^2 - 6 * u_1 * y_1 * y_2 - - 3 * u_1 * y_2^2 + - y_1^3 + - 3 * y_1^2 * y_2 + - 3 * y_1 * y_2^2 + - y_2^3, - ), - ) - p = - 100 * y_0^4 + 10 * y_0^3 * y_1 + 410 // 3 * y_0 * y_2 + 10 * y_0 * y_3 - - 110 * y_1^2 - 10 * y_1 * y_2 - @time res = diffreduce(p, pbr) - expected = parent_ring_change( - -100 * y_2^2 * y_1^2 + 70 * y_2^2 * y_0^4 + 380 // 3 * y_2^2 * y_0^2 - - 200 * y_2 * y_1^3 - 180 * y_2 * y_0^7 - 380 * y_2 * y_0^5 - - 270 * y_1^4 * y_0^6 - 1080 * y_1^4 * y_0^4 - 630 * y_1^4 * y_0^2 + - 810 * y_1^3 * y_0^9 - 2520 * y_1^3 * y_0^7 - 2910 * y_1^3 * y_0^5 - - 440 * y_1^3 * y_0^3 + - 220 // 3 * y_1^3 * y_0 + - 90 * y_1^2 * y_0^8 + - 3690 * y_1^2 * y_0^6 - 1330 * y_1^2 * y_0^4 + - 380 * y_1^2 * y_0^2 + - 1080 * y_1 * y_0^9 - 6540 * y_1 * y_0^7 - 7220 * y_1 * y_0^5 + - 810 * u_0^2 * y_0^6 - 3420 * u_0^2 * y_0^4 - 1620 * u_0 * y_0^9 + - 5220 * u_0 * y_0^7 + - 6840 * u_0 * y_0^5 - 10 * u_1^3 * y_1 - 30 * u_1^3 * y_0^3 + - 380 // 3 * u_1^3 * y_0 - 80 * u_1^2 * y_1^2 + - 10 * u_1^2 * y_0^4 + - 380 * u_1^2 * y_0^2 + - 190 * u_1 * y_1^3 + - 180 * u_1 * y_0^7 + - 380 * u_1 * y_0^5 - 3300 * y_0^6 - 1520 // 3 * y_0^4 - - 20 * u_2 * y_2 * u_1 * y_0 + - 60 * u_2 * y_2 * y_1 * y_0^3 + - 20 * u_2 * y_2 * y_1 * y_0 - 60 * u_2 * u_1 * y_1 * y_0^3 - - 20 * u_2 * u_1 * y_1 * y_0 - 360 * y_2 * u_1 * y_1 * y_0^5 + - 1380 * y_2 * u_1 * y_1 * y_0^3 + - 1580 // 3 * y_2 * u_1 * y_1 * y_0 - 30 * y_2^2 * y_1^2 * y_0^2 + - 90 * y_2^2 * y_1 * y_0^5 - 340 * y_2^2 * y_1 * y_0^3 - - 380 // 3 * y_2^2 * y_1 * y_0 - 180 * y_2 * y_1^3 * y_0^4 - - 660 * y_2 * y_1^3 * y_0^2 + 540 * y_2 * y_1^2 * y_0^7 - - 1860 * y_2 * y_1^2 * y_0^5 - 1380 * y_2 * y_1^2 * y_0^3 - - 160 // 3 * y_2 * y_1^2 * y_0 + - 240 * y_2 * y_1 * y_0^6 + - 1400 * y_2 * y_1 * y_0^4 + - 1520 // 3 * y_2 * y_1 * y_0^2 + - 10 * u_2 * y_2^2 * y_0 - 20 * u_2 * y_2 * y_0^2 + - 10 * u_2 * u_1^2 * y_0 + - 20 * u_2 * u_1 * y_0^2 + - 90 * u_2 * y_1^2 * y_0^5 + - 60 * u_2 * y_1^2 * y_0^3 + - 10 * u_2 * y_1^2 * y_0 - 60 * u_2 * y_1 * y_0^4 - 20 * u_2 * y_1 * y_0^2 - - 10 * y_2^2 * u_1 * y_1 - 30 * y_2^2 * u_1 * y_0^3 + - 380 // 3 * y_2^2 * u_1 * y_0 + - 20 * y_2 * u_1^2 * y_1 + - 60 * y_2 * u_1^2 * y_0^3 - 760 // 3 * y_2 * u_1^2 * y_0 + - 180 * y_2 * u_1 * y_1^2 - 80 * y_2 * u_1 * y_0^4 - - 1520 // 3 * y_2 * u_1 * y_0^2 + - 180 * y_2 * u_0 * y_0^4 + - 30 * u_1^2 * y_1^2 * y_0^2 + - 270 * u_1^2 * y_1 * y_0^5 - 1040 * u_1^2 * y_1 * y_0^3 - - 400 * u_1^2 * y_1 * y_0 + - 90 * u_1 * y_1^3 * y_0^4 + - 600 * u_1 * y_1^3 * y_0^2 - 810 * u_1 * y_1^2 * y_0^7 + - 2820 * u_1 * y_1^2 * y_0^5 + - 2170 * u_1 * y_1^2 * y_0^3 + - 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - 2100 * u_1 * y_1 * y_0^4 - - 760 * u_1 * y_1 * y_0^2 - 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + - 7020 * y_1 * u_0 * y_0^4 - 6030 * y_0^8 + 810 * y_0^12 - 1800 * y_0^10 - - 100 * y_1^4, - parent(res), - ) - @test total_degree(divexact(res, expected)) == 0 + # Next two verified with Maple + # Mizuka's example + R, (y_0, y_1, y_2, y_3, u_0, u_1, u_2) = + Nemo.PolynomialRing(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) + pbr = PBRepresentation( + ["y"], + ["u"], + Array{String, 1}(), + Dict("y" => 2), + Dict( + "y" => + 27 * y_0^9 + 27 * y_0^6 * y_1^3 - 54 * u_0 * y_0^6 + + 54 * y_0^7 + + 54 * y_0^6 * y_1 - 27 * y_0^5 * y_1^2 - 27 * y_0^4 * u_1 * y_1^2 + + 27 * y_0^4 * y_1^3 + + 27 * y_0^4 * y_1^2 * y_2 + + 27 * u_0^2 * y_0^3 - 54 * u_0 * y_0^4 - 54 * u_0 * y_0^3 * y_1 + + 27 * y_0^5 + + 54 * y_0^4 * y_1 + + 18 * y_0^3 * u_1 * y_1 + + 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + + 9 * y_0^2 * u_1^2 * y_1 - 18 * y_0^2 * u_1 * y_1^2 - + 18 * y_0^2 * u_1 * y_1 * y_2 + + 9 * y_0^2 * y_1^3 + + 18 * y_0^2 * y_1^2 * y_2 + + 9 * y_0^2 * y_1 * y_2^2 + + 4 * y_0^3 - 3 * y_0 * u_1^2 + + 6 * y_0 * u_1 * y_1 + + 6 * y_0 * u_1 * y_2 - 3 * y_0 * y_1^2 - 6 * y_0 * y_1 * y_2 - + 3 * y_0 * y_2^2 - u_1^3 + + 3 * u_1^2 * y_1 + + 3 * u_1^2 * y_2 - 3 * u_1 * y_1^2 - 6 * u_1 * y_1 * y_2 - + 3 * u_1 * y_2^2 + + y_1^3 + + 3 * y_1^2 * y_2 + + 3 * y_1 * y_2^2 + + y_2^3, + ), + ) + p = + 100 * y_0^4 + 10 * y_0^3 * y_1 + 410 // 3 * y_0 * y_2 + 10 * y_0 * y_3 - + 110 * y_1^2 - 10 * y_1 * y_2 + @time res = diffreduce(p, pbr) + expected = parent_ring_change( + -100 * y_2^2 * y_1^2 + 70 * y_2^2 * y_0^4 + 380 // 3 * y_2^2 * y_0^2 - + 200 * y_2 * y_1^3 - 180 * y_2 * y_0^7 - 380 * y_2 * y_0^5 - + 270 * y_1^4 * y_0^6 - 1080 * y_1^4 * y_0^4 - 630 * y_1^4 * y_0^2 + + 810 * y_1^3 * y_0^9 - 2520 * y_1^3 * y_0^7 - 2910 * y_1^3 * y_0^5 - + 440 * y_1^3 * y_0^3 + + 220 // 3 * y_1^3 * y_0 + + 90 * y_1^2 * y_0^8 + + 3690 * y_1^2 * y_0^6 - 1330 * y_1^2 * y_0^4 + + 380 * y_1^2 * y_0^2 + + 1080 * y_1 * y_0^9 - 6540 * y_1 * y_0^7 - 7220 * y_1 * y_0^5 + + 810 * u_0^2 * y_0^6 - 3420 * u_0^2 * y_0^4 - 1620 * u_0 * y_0^9 + + 5220 * u_0 * y_0^7 + + 6840 * u_0 * y_0^5 - 10 * u_1^3 * y_1 - 30 * u_1^3 * y_0^3 + + 380 // 3 * u_1^3 * y_0 - 80 * u_1^2 * y_1^2 + + 10 * u_1^2 * y_0^4 + + 380 * u_1^2 * y_0^2 + + 190 * u_1 * y_1^3 + + 180 * u_1 * y_0^7 + + 380 * u_1 * y_0^5 - 3300 * y_0^6 - 1520 // 3 * y_0^4 - + 20 * u_2 * y_2 * u_1 * y_0 + + 60 * u_2 * y_2 * y_1 * y_0^3 + + 20 * u_2 * y_2 * y_1 * y_0 - 60 * u_2 * u_1 * y_1 * y_0^3 - + 20 * u_2 * u_1 * y_1 * y_0 - 360 * y_2 * u_1 * y_1 * y_0^5 + + 1380 * y_2 * u_1 * y_1 * y_0^3 + + 1580 // 3 * y_2 * u_1 * y_1 * y_0 - 30 * y_2^2 * y_1^2 * y_0^2 + + 90 * y_2^2 * y_1 * y_0^5 - 340 * y_2^2 * y_1 * y_0^3 - + 380 // 3 * y_2^2 * y_1 * y_0 - 180 * y_2 * y_1^3 * y_0^4 - + 660 * y_2 * y_1^3 * y_0^2 + 540 * y_2 * y_1^2 * y_0^7 - + 1860 * y_2 * y_1^2 * y_0^5 - 1380 * y_2 * y_1^2 * y_0^3 - + 160 // 3 * y_2 * y_1^2 * y_0 + + 240 * y_2 * y_1 * y_0^6 + + 1400 * y_2 * y_1 * y_0^4 + + 1520 // 3 * y_2 * y_1 * y_0^2 + + 10 * u_2 * y_2^2 * y_0 - 20 * u_2 * y_2 * y_0^2 + + 10 * u_2 * u_1^2 * y_0 + + 20 * u_2 * u_1 * y_0^2 + + 90 * u_2 * y_1^2 * y_0^5 + + 60 * u_2 * y_1^2 * y_0^3 + + 10 * u_2 * y_1^2 * y_0 - 60 * u_2 * y_1 * y_0^4 - 20 * u_2 * y_1 * y_0^2 - + 10 * y_2^2 * u_1 * y_1 - 30 * y_2^2 * u_1 * y_0^3 + + 380 // 3 * y_2^2 * u_1 * y_0 + + 20 * y_2 * u_1^2 * y_1 + + 60 * y_2 * u_1^2 * y_0^3 - 760 // 3 * y_2 * u_1^2 * y_0 + + 180 * y_2 * u_1 * y_1^2 - 80 * y_2 * u_1 * y_0^4 - + 1520 // 3 * y_2 * u_1 * y_0^2 + + 180 * y_2 * u_0 * y_0^4 + + 30 * u_1^2 * y_1^2 * y_0^2 + + 270 * u_1^2 * y_1 * y_0^5 - 1040 * u_1^2 * y_1 * y_0^3 - + 400 * u_1^2 * y_1 * y_0 + + 90 * u_1 * y_1^3 * y_0^4 + + 600 * u_1 * y_1^3 * y_0^2 - 810 * u_1 * y_1^2 * y_0^7 + + 2820 * u_1 * y_1^2 * y_0^5 + + 2170 * u_1 * y_1^2 * y_0^3 + + 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - + 2100 * u_1 * y_1 * y_0^4 - 760 * u_1 * y_1 * y_0^2 - + 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + + 7020 * y_1 * u_0 * y_0^4 - 6030 * y_0^8 + 810 * y_0^12 - 1800 * y_0^10 - + 100 * y_1^4, + parent(res), + ) + @test total_degree(divexact(res, expected)) == 0 - ode = @ODEmodel( - x1'(t) = x1(t) + 2 * x1(t) * x2(t) + u(t), - x2'(t) = x2(t) + 3 * x1(t) * x2(t), - y(t) = x1(t) - ) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.PolynomialRing( - Nemo.QQ, - ["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "u(t)_0", "u(t)_3"], - ) - io_switch!(pbr) - @time res = diffreduce(u_3, pbr) - expected = parent_ring_change( - y_0 * ( - 27 * u_0 * y_0^4 + 27 * y_0^5 - 27 * y_0^4 * y_1 + - 27 * u_0 * y_0^3 + - 54 * u_0 * y_0^2 * y_1 + - 27 * y_0^4 + - 27 * y_0^3 * y_1 - 54 * y_0^2 * y_1^2 + - 9 * u_0 * y_0^2 + - 27 * u_0 * y_0 * y_1 + - 12 * u_0 * y_0 * y_2 + - 9 * u_0 * y_1^2 + - 9 * y_0^3 + - 18 * y_0^2 * y_1 + - 12 * y_0^2 * y_2 - 18 * y_0 * y_1^2 - 12 * y_0 * y_1 * y_2 - 9 * y_1^3 + - u_0 * y_0 + - 3 * u_0 * y_1 + - 3 * u_0 * y_2 + - u_0 * y_3 + - y_0^2 + - 2 * y_0 * y_1 + - 3 * y_0 * y_2 + - y_0 * y_4 - 3 * y_1^2 - 3 * y_1 * y_2 - y_1 * y_3 - ), - parent(res), - ) - @test total_degree(divexact(res, expected)) == 0 + ode = @ODEmodel( + x1'(t) = x1(t) + 2 * x1(t) * x2(t) + u(t), + x2'(t) = x2(t) + 3 * x1(t) * x2(t), + y(t) = x1(t) + ) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.PolynomialRing( + Nemo.QQ, + ["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "u(t)_0", "u(t)_3"], + ) + io_switch!(pbr) + @time res = diffreduce(u_3, pbr) + expected = parent_ring_change( + y_0 * ( + 27 * u_0 * y_0^4 + 27 * y_0^5 - 27 * y_0^4 * y_1 + + 27 * u_0 * y_0^3 + + 54 * u_0 * y_0^2 * y_1 + + 27 * y_0^4 + + 27 * y_0^3 * y_1 - 54 * y_0^2 * y_1^2 + + 9 * u_0 * y_0^2 + + 27 * u_0 * y_0 * y_1 + + 12 * u_0 * y_0 * y_2 + + 9 * u_0 * y_1^2 + + 9 * y_0^3 + + 18 * y_0^2 * y_1 + + 12 * y_0^2 * y_2 - 18 * y_0 * y_1^2 - 12 * y_0 * y_1 * y_2 - 9 * y_1^3 + + u_0 * y_0 + + 3 * u_0 * y_1 + + 3 * u_0 * y_2 + + u_0 * y_3 + + y_0^2 + + 2 * y_0 * y_1 + + 3 * y_0 * y_2 + + y_0 * y_4 - 3 * y_1^2 - 3 * y_1 * y_2 - y_1 * y_3 + ), + parent(res), + ) + @test total_degree(divexact(res, expected)) == 0 + end end diff --git a/test/exp_vec_trie.jl b/test/exp_vec_trie.jl index 11420ecde..ec09c5425 100644 --- a/test/exp_vec_trie.jl +++ b/test/exp_vec_trie.jl @@ -1,30 +1,32 @@ -@testset "Trie for exponents vectors" begin - for _ in 1:10 - d = rand([i + 10 for i in 1:10]) - t = ExpVectTrie(d) - push!(t, [0 for _ in 1:d]) - vects = [rand([i for i in 1:10], d) for _ in 1:20] - for v in vects - push!(t, v) - end - for v in vects - diff, best = get_max_below(t, v) - @test diff == 0 - @test best == v - end +if GROUP == "All" || GROUP == "Core" + @testset "Trie for exponents vectors" begin + for _ in 1:10 + d = rand([i + 10 for i in 1:10]) + t = ExpVectTrie(d) + push!(t, [0 for _ in 1:d]) + vects = [rand([i for i in 1:10], d) for _ in 1:20] + for v in vects + push!(t, v) + end + for v in vects + diff, best = get_max_below(t, v) + @test diff == 0 + @test best == v + end - svects = Set(vects) - push!(svects, [0 for _ in 1:d]) + svects = Set(vects) + push!(svects, [0 for _ in 1:d]) - for _ in 1:20 - v = rand([i for i in 1:10], d) - diff, best = get_max_below(t, v) - if v in svects - @test diff == 0 - @test v == best - else - @test (best in svects) - @test diff == sum(v .- best) + for _ in 1:20 + v = rand([i for i in 1:10], d) + diff, best = get_max_below(t, v) + if v in svects + @test diff == 0 + @test v == best + else + @test (best in svects) + @test diff == sum(v .- best) + end end end end diff --git a/test/extract_coefficients.jl b/test/extract_coefficients.jl index da3bae87f..7237ac142 100644 --- a/test/extract_coefficients.jl +++ b/test/extract_coefficients.jl @@ -1,58 +1,61 @@ -@testset "Coefficient extraction for rational fucntions" begin - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) - C = extract_coefficients_ratfunc( - (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), - [z], - ) +if GROUP == "All" || GROUP == "Core" + @testset "Coefficient extraction for rational fucntions" begin + R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + C = extract_coefficients_ratfunc( + (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), + [z], + ) - @test Set(C) == Set([ - one(R) // 1, - (3 * x - y^2) // 1, - y // 1, - x^2 // 1, - (x + y) // 1, - (x^2 + 1) // 1, - ]) + @test Set(C) == Set([ + one(R) // 1, + (3 * x - y^2) // 1, + y // 1, + x^2 // 1, + (x + y) // 1, + (x^2 + 1) // 1, + ]) - R, (x, y) = PolynomialRing(QQ, ["x", "y"]) - f = (x^2 + y^2) // (1 - x - 3 * y) - @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.fmpq_mpoly}())) == - Set([f, one(R) // 1]) + R, (x, y) = PolynomialRing(QQ, ["x", "y"]) + f = (x^2 + y^2) // (1 - x - 3 * y) + @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.fmpq_mpoly}())) == + Set([f, one(R) // 1]) - R, (x, y, u, v) = PolynomialRing(QQ, ["x", "y", "u", "v"]) - C = extract_coefficients_ratfunc( - (x + (y + 3) * u * v + y^2 * v^3) // (u + 3 * v - (x^2 + y^2) * u^2), - [u, v], - ) - @test Set(C) == Set([ - x // 1, - (y + 3) // 1, - y^2 // 1, - one(R) // 1, - 3 * one(R) // 1, - -(x^2 + y^2) // 1, - ]) -end + R, (x, y, u, v) = PolynomialRing(QQ, ["x", "y", "u", "v"]) + C = extract_coefficients_ratfunc( + (x + (y + 3) * u * v + y^2 * v^3) // (u + 3 * v - (x^2 + y^2) * u^2), + [u, v], + ) + @test Set(C) == Set([ + x // 1, + (y + 3) // 1, + y^2 // 1, + one(R) // 1, + 3 * one(R) // 1, + -(x^2 + y^2) // 1, + ]) + end -@testset "Coefficient extraction for polynomials" begin - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) - C = extract_coefficients((y + z + 8), [x]) - R_coef = parent(first(values(C))) - y, z = gens(R_coef) - @test symbols(R_coef) == [:y, :z] - @test C == Dict([0] => y + z + 8) + @testset "Coefficient extraction for polynomials" begin + R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + C = extract_coefficients((y + z + 8), [x]) + R_coef = parent(first(values(C))) + y, z = gens(R_coef) + @test symbols(R_coef) == [:y, :z] + @test C == Dict([0] => y + z + 8) - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) - C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [z]) - R_coef = parent(first(values(C))) - x, y = gens(R_coef) - @test symbols(R_coef) == [:x, :y] - @test C == Dict([3] => 3x - y^2, [1] => y, [0] => x^2) + R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [z]) + R_coef = parent(first(values(C))) + x, y = gens(R_coef) + @test symbols(R_coef) == [:x, :y] + @test C == Dict([3] => 3x - y^2, [1] => y, [0] => x^2) - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) - C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [x, z]) - R_coef = parent(first(values(C))) - y = gens(R_coef)[1] - @test symbols(R_coef) == [:y] - @test C == Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) + R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [x, z]) + R_coef = parent(first(values(C))) + y = gens(R_coef)[1] + @test symbols(R_coef) == [:y] + @test C == + Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) + end end diff --git a/test/find_leader.jl b/test/find_leader.jl index 1994e82d2..36dd41c40 100644 --- a/test/find_leader.jl +++ b/test/find_leader.jl @@ -1,24 +1,26 @@ -@testset "Finding leader" begin - ode = @ODEmodel( - x1'(t) = x3(t), - x2'(t) = a * x2(t), - x3'(t) = x1(t), - y1(t) = x1(t), - y2(t) = x2(t) + u(t) - ) - ioeqs = find_ioequations(ode) - println("IOEQS: ", ioeqs) - pbr = PBRepresentation(ode, ioeqs) +if GROUP == "All" || GROUP == "Core" + @testset "Finding leader" begin + ode = @ODEmodel( + x1'(t) = x3(t), + x2'(t) = a * x2(t), + x3'(t) = x1(t), + y1(t) = x1(t), + y2(t) = x2(t) + u(t) + ) + ioeqs = find_ioequations(ode) + println("IOEQS: ", ioeqs) + pbr = PBRepresentation(ode, ioeqs) - R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.PolynomialRing( - Nemo.QQ, - ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y2(t)_0", "y2(t)_1", "y2(t)_2"], - ) - @test find_leader([a, x1, x2, u], pbr) == nothing - @test find_leader([a, x1, y1_0, x2], pbr) == y1_0 - @test find_leader([y1_0, y1_1, y2_0, y2_1], pbr) == y2_1 - l = find_leader([y1_2, y2_1], pbr) - @test (pbr.y_names[1] == "y1(t)" && l == y2_1) || - (pbr.y_names[1] == "y2(t)" && l == y1_2) - @test find_leader([y1_2, y2_0], pbr) == y1_2 + R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.PolynomialRing( + Nemo.QQ, + ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y2(t)_0", "y2(t)_1", "y2(t)_2"], + ) + @test find_leader([a, x1, x2, u], pbr) == nothing + @test find_leader([a, x1, y1_0, x2], pbr) == y1_0 + @test find_leader([y1_0, y1_1, y2_0, y2_1], pbr) == y2_1 + l = find_leader([y1_2, y2_1], pbr) + @test (pbr.y_names[1] == "y1(t)" && l == y2_1) || + (pbr.y_names[1] == "y2(t)" && l == y1_2) + @test find_leader([y1_2, y2_0], pbr) == y1_2 + end end diff --git a/test/identifiability.jl b/test/identifiability.jl index cec848d14..1b51ed5a9 100644 --- a/test/identifiability.jl +++ b/test/identifiability.jl @@ -1,235 +1,237 @@ -@testset "Assessing identifiability" begin - test_cases = [] - - # 2-compartiment model - - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = - [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] - correct = [ - :nonidentifiable, - :nonidentifiable, - :nonidentifiable, - :globally, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - # No parameters no worry - - ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) - funcs_to_test = [x1, x2, x1 + x2] - correct = [:nonidentifiable, :nonidentifiable, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - # Also test when `funcs_to_test` is empty! - funcs_to_test = Vector{typeof(x1)}() - correct = OrderedDict(x1 => :nonidentifiable, x2 => :nonidentifiable) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), - x1'(t) = c * x1(t) + d * x0(t) * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [a, b, c, d, b * x1, x0, x1] - correct = [ - :globally, - :nonidentifiable, - :globally, - :globally, - :globally, - :globally, - :nonidentifiable, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), - I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), - W'(t) = xi * (I(t) - W(t)), - R'(t) = gam * I(t) - (mu + a) * R(t), - y(t) = k * I(t) - ) - funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k] - correct = [:globally for _ in funcs_to_test] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) - ) - funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] - correct = [ - :globally, - :globally, - :nonidentifiable, - :locally, - :locally, - :nonidentifiable, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = (1 + x1(t)^2) // 2, - x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), - y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), - y2(t) = x2(t) - ) - funcs_to_test = [b, x1, x2] - correct = [:globally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -a1 * x1(t) + a21 * x2(t), - x2'(t) = -a2 * x2(t) - a21 * x2(t), - y1(t) = x1(t) - ) - funcs_to_test = [a1, a2, a21] - correct = [:locally, :nonidentifiable, :nonidentifiable] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -a1 * x1(t) + a21 * x2(t), - x2'(t) = -a2 * x2(t) - a21 * x2(t) + u(t), - y1(t) = x1(t) - ) - funcs_to_test = [a1, a2, a21, a2 + a1, a2 * (a1 - a21)] - correct = [:locally, :locally, :globally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -(a21 + a31 + a01) * x1(t) + a12 * x2(t) + a13 * x3(t) + u(t), - x2'(t) = a21 * x1(t) - a12 * x2(t), - x3'(t) = a31 * x1(t) - a13 * x3(t), - y(t) = x1(t) - ) - funcs_to_test = [x1, x2, x3, a31 + a21, (a21 - a31) // (a12 - a13)] - correct = [:globally, :locally, :locally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - S'(t) = -b * S(t) * In(t) / N, - E'(t) = b * S(t) * In(t) / N - nu * E(t), - In'(t) = nu * E(t) - a * In(t), - y1(t) = In(t), - y2(t) = N - ) - funcs_to_test = [b, N, In, a, nu, S, E, a * nu, a + nu] - correct = [ - :globally, - :globally, - :globally, - :locally, - :locally, - :locally, - :locally, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - for case in test_cases - result = assess_identifiability(case[:ode], funcs_to_check = case[:funcs]) - @test result == case[:correct] +if GROUP == "All" || GROUP == "Core" + @testset "Assessing identifiability" begin + test_cases = [] + + # 2-compartiment model + + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = + [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] + correct = [ + :nonidentifiable, + :nonidentifiable, + :nonidentifiable, + :globally, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + # No parameters no worry + + ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) + funcs_to_test = [x1, x2, x1 + x2] + correct = [:nonidentifiable, :nonidentifiable, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + # Also test when `funcs_to_test` is empty! + funcs_to_test = Vector{typeof(x1)}() + correct = OrderedDict(x1 => :nonidentifiable, x2 => :nonidentifiable) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), + x1'(t) = c * x1(t) + d * x0(t) * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [a, b, c, d, b * x1, x0, x1] + correct = [ + :globally, + :nonidentifiable, + :globally, + :globally, + :globally, + :globally, + :nonidentifiable, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), + I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), + W'(t) = xi * (I(t) - W(t)), + R'(t) = gam * I(t) - (mu + a) * R(t), + y(t) = k * I(t) + ) + funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k] + correct = [:globally for _ in funcs_to_test] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) + ) + funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] + correct = [ + :globally, + :globally, + :nonidentifiable, + :locally, + :locally, + :nonidentifiable, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = (1 + x1(t)^2) // 2, + x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), + y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), + y2(t) = x2(t) + ) + funcs_to_test = [b, x1, x2] + correct = [:globally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -a1 * x1(t) + a21 * x2(t), + x2'(t) = -a2 * x2(t) - a21 * x2(t), + y1(t) = x1(t) + ) + funcs_to_test = [a1, a2, a21] + correct = [:locally, :nonidentifiable, :nonidentifiable] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -a1 * x1(t) + a21 * x2(t), + x2'(t) = -a2 * x2(t) - a21 * x2(t) + u(t), + y1(t) = x1(t) + ) + funcs_to_test = [a1, a2, a21, a2 + a1, a2 * (a1 - a21)] + correct = [:locally, :locally, :globally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -(a21 + a31 + a01) * x1(t) + a12 * x2(t) + a13 * x3(t) + u(t), + x2'(t) = a21 * x1(t) - a12 * x2(t), + x3'(t) = a31 * x1(t) - a13 * x3(t), + y(t) = x1(t) + ) + funcs_to_test = [x1, x2, x3, a31 + a21, (a21 - a31) // (a12 - a13)] + correct = [:globally, :locally, :locally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + S'(t) = -b * S(t) * In(t) / N, + E'(t) = b * S(t) * In(t) / N - nu * E(t), + In'(t) = nu * E(t) - a * In(t), + y1(t) = In(t), + y2(t) = N + ) + funcs_to_test = [b, N, In, a, nu, S, E, a * nu, a + nu] + correct = [ + :globally, + :globally, + :globally, + :locally, + :locally, + :locally, + :locally, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + for case in test_cases + result = assess_identifiability(case[:ode], funcs_to_check = case[:funcs]) + @test result == case[:correct] + end end end diff --git a/test/identifiable_functions.jl b/test/identifiable_functions.jl index 6adcd51ca..04376531a 100644 --- a/test/identifiable_functions.jl +++ b/test/identifiable_functions.jl @@ -1,984 +1,1007 @@ -# For each ODE system we check the equality (in terms of fields of rational -# functions) of the true set of identifiable functions and the obtained -# simplified set -test_cases = [] - -### - -ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) -ident_funcs = [a] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel(x1'(t) = a, x2'(t) = -a, y(t) = x1 + x2) -ident_funcs = [] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Parameter a is not identifiable, and neither are any combinations thereof. -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x2(t) - a, - x2'(t) = x1(t) + a, - y(t) = x1(t) + x2(t) -) -ident_funcs = Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() - -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Example 2 from -# "On Global Identifiability for Arbitrary Model Parametrizations", -# DOI: 10.1016/0005-1098(94)90029-9 -ode = StructuralIdentifiability.@ODEmodel(x1'(t) = Θ * x2(t)^2, x2'(t) = u(t), y(t) = x1(t)) -ident_funcs = [Θ] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Example 4 from -# "On Global Identifiability for Arbitrary Model Parametrizations", -# DOI: 10.1016/0005-1098(94)90029-9 -ode = StructuralIdentifiability.@ODEmodel( - x'(t) = (-V_m * x(t)) / (k_m + x(t)) + k01 * x(t), - y(t) = c * x(t) -) -ident_funcs = [k01, k_m // V_m, V_m * c] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Parameters b and c enter the io equations only as the product b * c. -ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) -ident_funcs = [b * c, a] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# 2-compartiment model -ode = StructuralIdentifiability.@ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) -) -ident_funcs = [(a01 * a12), (a01 + a12 + a21)] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# TODO: uncomment when identifiability can handle models with no states -# ode = StructuralIdentifiability.@ODEmodel( -# y(t) = a*u(t) -# ) -# ident_funcs = [(a01 * a12), (a01 + a12 + a21)] -# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Example 2 from -# "On Structural Identifiability", -# DOI: https://doi.org/10.1016/0025-5564(70)90132-X -# -# More or less the same 2-compartmental model as the one given above -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -(k1 + k2) * x1(t) + k3 * x2(t) + u(t), - x2'(t) = k2 * x1(t) - (k3 + k4) * x2(t), - y(t) = x1(t) -) -ident_funcs = [(k1 + k2), (k1 + k2 + k3 + k4), ((k1 + k2) * (k3 + k4) - k2 * k3)] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Diagonal with simple spectrum and observable states -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = λ1 * x1(t) + β1 * u1(t), - x2'(t) = λ2 * x2(t) + β2 * u2(t), - x3'(t) = λ3 * x3(t) + β3 * u3(t), - y(t) = x1(t) + x2(t) + x3(t) -) -ident_funcs = [λ1, λ2, λ3, β1, β2, β3] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# 3 compartments: -# x1 <--> x2 <--> x3 -# If we observe x1 and control x1, then all parameters are identifiable -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -a1 * x1(t) + b1 * x2(t) + u(t), - x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), - x3'(t) = -b2 * x3(t) + a2 * x2(t), - y(t) = x1(t) -) -ident_funcs = [a1, a2, b1, b2] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Example 3 from -# "On Structural Identifiability", -# DOI: https://doi.org/10.1016/0025-5564(70)90132-X -# -# 3 compartments: -# x1 <--> x2 <--> x3 -# If we observe x1 and control x3, then only some functions of parameters -# are identifiable -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -a1 * x1(t) + b1 * x2(t), - x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), - x3'(t) = -b2 * x3(t) + a2 * x2(t) + u(t), - y(t) = x1(t) -) -ident_funcs = [b1 * b2, a1 + a2 + b1 + b2, a1 * a2 + a1 * b2] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Example 3 from -# "On the identifiability and distinguishability of nonlinear parametric -# models", -# DOI: https://doi.org/10.1016/0378-4754(95)00123-9 -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = p1 * x1^2 + p2 * x1 * x2, - x2'(t) = p3 * x1^2 + p4 * x1 * x2, - y(t) = x1 -) -ident_funcs = [p1 + p4, p2 * p3 - p4 * p1] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Goowdin oscillator -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) -) -ident_funcs = [sigma, delta + beta, c, b, delta * beta] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# SIRS forced -ode = StructuralIdentifiability.@ODEmodel( - s'(t) = mu - mu * s(t) - b0 * (1 + b1 * x1(t)) * i(t) * s(t) + g * r(t), - i'(t) = b0 * (1 + b1 * x1(t)) * i(t) * s(t) - (nu + mu) * i(t), - r'(t) = nu * i(t) - (mu + g) * r(t), - x1'(t) = -M * x2(t), - x2'(t) = M * x1(t), - y1(t) = i(t), - y2(t) = r(t) -) -ident_funcs = [g, mu, b0, nu, M^2] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# SEIR_1_io -ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -beta * S(t) * I(t), - E'(t) = beta * S(t) * I(t) - v * E(t), - I'(t) = v * E(t) - psi * I(t) - (1 - psi) * gamma * I(t), - R'(t) = gamma * Q(t) + (1 - psi) * gamma * I(t), - Q'(t) = -gamma * Q(t) + psi * I(t), - y1(t) = Q(t) -) -ident_funcs = [gamma, beta // psi, gamma * psi - v - psi, gamma * psi - v * psi] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Bilirubin2_io. -# Regression test: failed before, as the total degrees were being estimated -# incorrectly in the interpolation -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = - -(k21 + k31 + k41 + k01) * x1(t) + k12 * x2(t) + k13 * x3(t) + k14 * x4(t) + u(t), - x2'(t) = k21 * x1(t) - k12 * x2(t), - x3'(t) = k31 * x1(t) - k13 * x3(t), - x4'(t) = k41 * x1(t) - k14 * x4(t), - y1(t) = x1(t) -) -ident_funcs = [ - k01 // one(k01), - k12 * k13 * k14 // one(k01), - k31 * k21 * k41 // one(k01), - k12 + k13 + k14 // one(k01), - k31 + k21 + k41 // one(k01), - k12 * k13 + k12 * k14 + k13 * k14 // one(k01), - k31 * k21 + k31 * k41 + k21 * k41 // one(k01), - k31 * k12 - 2 * k31 * k13 + k31 * k14 - 2 * k21 * k12 + - k21 * k13 + - k21 * k14 + - k12 * k41 + - k13 * k41 - 2 * k14 * k41 // one(k01), -] -# Too slow with hybrid strategy :( -# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# Biohydrogenation_io -ode = StructuralIdentifiability.@ODEmodel( - x5'(t) = +if GROUP == "All" || GROUP == "Core" + # For each ODE system we check the equality (in terms of fields of rational + # functions) of the true set of identifiable functions and the obtained + # simplified set + test_cases = [] + + ### + + ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) + ident_funcs = [a] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel(x1'(t) = a, x2'(t) = -a, y(t) = x1 + x2) + ident_funcs = [] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Parameter a is not identifiable, and neither are any combinations thereof. + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x2(t) - a, + x2'(t) = x1(t) + a, + y(t) = x1(t) + x2(t) + ) + ident_funcs = + Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() + + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Example 2 from + # "On Global Identifiability for Arbitrary Model Parametrizations", + # DOI: 10.1016/0005-1098(94)90029-9 + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = Θ * x2(t)^2, + x2'(t) = u(t), + y(t) = x1(t) + ) + ident_funcs = [Θ] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Example 4 from + # "On Global Identifiability for Arbitrary Model Parametrizations", + # DOI: 10.1016/0005-1098(94)90029-9 + ode = StructuralIdentifiability.@ODEmodel( + x'(t) = (-V_m * x(t)) / (k_m + x(t)) + k01 * x(t), + y(t) = c * x(t) + ) + ident_funcs = [k01, k_m // V_m, V_m * c] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Parameters b and c enter the io equations only as the product b * c. + ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) + ident_funcs = [b * c, a] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # 2-compartiment model + ode = StructuralIdentifiability.@ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + ident_funcs = [(a01 * a12), (a01 + a12 + a21)] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # TODO: uncomment when identifiability can handle models with no states + # ode = StructuralIdentifiability.@ODEmodel( + # y(t) = a*u(t) + # ) + # ident_funcs = [(a01 * a12), (a01 + a12 + a21)] + # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Example 2 from + # "On Structural Identifiability", + # DOI: https://doi.org/10.1016/0025-5564(70)90132-X + # + # More or less the same 2-compartmental model as the one given above + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -(k1 + k2) * x1(t) + k3 * x2(t) + u(t), + x2'(t) = k2 * x1(t) - (k3 + k4) * x2(t), + y(t) = x1(t) + ) + ident_funcs = [(k1 + k2), (k1 + k2 + k3 + k4), ((k1 + k2) * (k3 + k4) - k2 * k3)] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Diagonal with simple spectrum and observable states + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = λ1 * x1(t) + β1 * u1(t), + x2'(t) = λ2 * x2(t) + β2 * u2(t), + x3'(t) = λ3 * x3(t) + β3 * u3(t), + y(t) = x1(t) + x2(t) + x3(t) + ) + ident_funcs = [λ1, λ2, λ3, β1, β2, β3] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # 3 compartments: + # x1 <--> x2 <--> x3 + # If we observe x1 and control x1, then all parameters are identifiable + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -a1 * x1(t) + b1 * x2(t) + u(t), + x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), + x3'(t) = -b2 * x3(t) + a2 * x2(t), + y(t) = x1(t) + ) + ident_funcs = [a1, a2, b1, b2] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Example 3 from + # "On Structural Identifiability", + # DOI: https://doi.org/10.1016/0025-5564(70)90132-X + # + # 3 compartments: + # x1 <--> x2 <--> x3 + # If we observe x1 and control x3, then only some functions of parameters + # are identifiable + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -a1 * x1(t) + b1 * x2(t), + x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), + x3'(t) = -b2 * x3(t) + a2 * x2(t) + u(t), + y(t) = x1(t) + ) + ident_funcs = [b1 * b2, a1 + a2 + b1 + b2, a1 * a2 + a1 * b2] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Example 3 from + # "On the identifiability and distinguishability of nonlinear parametric + # models", + # DOI: https://doi.org/10.1016/0378-4754(95)00123-9 + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = p1 * x1^2 + p2 * x1 * x2, + x2'(t) = p3 * x1^2 + p4 * x1 * x2, + y(t) = x1 + ) + ident_funcs = [p1 + p4, p2 * p3 - p4 * p1] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Goowdin oscillator + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) + ) + ident_funcs = [sigma, delta + beta, c, b, delta * beta] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # SIRS forced + ode = StructuralIdentifiability.@ODEmodel( + s'(t) = mu - mu * s(t) - b0 * (1 + b1 * x1(t)) * i(t) * s(t) + g * r(t), + i'(t) = b0 * (1 + b1 * x1(t)) * i(t) * s(t) - (nu + mu) * i(t), + r'(t) = nu * i(t) - (mu + g) * r(t), + x1'(t) = -M * x2(t), + x2'(t) = M * x1(t), + y1(t) = i(t), + y2(t) = r(t) + ) + ident_funcs = [g, mu, b0, nu, M^2] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # SEIR_1_io + ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -beta * S(t) * I(t), + E'(t) = beta * S(t) * I(t) - v * E(t), + I'(t) = v * E(t) - psi * I(t) - (1 - psi) * gamma * I(t), + R'(t) = gamma * Q(t) + (1 - psi) * gamma * I(t), + Q'(t) = -gamma * Q(t) + psi * I(t), + y1(t) = Q(t) + ) + ident_funcs = [gamma, beta // psi, gamma * psi - v - psi, gamma * psi - v * psi] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Bilirubin2_io. + # Regression test: failed before, as the total degrees were being estimated + # incorrectly in the interpolation + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = + -(k21 + k31 + k41 + k01) * x1(t) + + k12 * x2(t) + + k13 * x3(t) + + k14 * x4(t) + + u(t), + x2'(t) = k21 * x1(t) - k12 * x2(t), + x3'(t) = k31 * x1(t) - k13 * x3(t), + x4'(t) = k41 * x1(t) - k14 * x4(t), + y1(t) = x1(t) + ) + ident_funcs = [ + k01 // one(k01), + k12 * k13 * k14 // one(k01), + k31 * k21 * k41 // one(k01), + k12 + k13 + k14 // one(k01), + k31 + k21 + k41 // one(k01), + k12 * k13 + k12 * k14 + k13 * k14 // one(k01), + k31 * k21 + k31 * k41 + k21 * k41 // one(k01), + k31 * k12 - 2 * k31 * k13 + k31 * k14 - 2 * k21 * k12 + + k21 * k13 + + k21 * k14 + + k12 * k41 + + k13 * k41 - 2 * k14 * k41 // one(k01), + ] + # Too slow with hybrid strategy :( + # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # Biohydrogenation_io + ode = StructuralIdentifiability.@ODEmodel( + x5'(t) = + ( + k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - + k6 * x5(t) * k7 - x5(t) * k7 * x4(t) + ) // ( + k8 * k6 + + k8 * x4(t) + + k6 * x6(t) + + k6 * x5(t) + + x6(t) * x4(t) + + x5(t) * x4(t) + ), + x7'(t) = (k9 * k10 * x6(t) - k9 * x6(t)^2) // k10, + x4'(t) = (-k5 * x4(t)) // (k6 + x4(t)), + x6'(t) = + ( + -k8 * k9 * k10 * x6(t) + k8 * k9 * x6(t)^2 - k9 * k10 * x6(t)^2 - + k9 * k10 * x6(t) * x5(t) + + k9 * x6(t)^3 + + k9 * x6(t)^2 * x5(t) + + k10 * x5(t) * k7 + ) // (k8 * k10 + k10 * x6(t) + k10 * x5(t)), + y1(t) = x4(t), + y2(t) = x5(t) + ) + ident_funcs = [k7, k6, k5, k10^2, k9 * k10, k8 + 1 // 2 * k10] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # SLIQR + ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -b * In(t) * S(t) * Ninv - S(t) * Ninv * u(t), + In'(t) = -In(t) * g + s * Q(t) + a * L(t), + L'(t) = b * In(t) * S(t) * Ninv - a * L(t), + Q'(t) = -e * In(t) * g + In(t) * g - s * Q(t), + y(t) = In(t) * Ninv + ) + ident_funcs = [ + g + a, + s + g + a, + s, + Ninv, + b, + (e * s - s + a) // (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), + e * s * g + s * a + g * a, + (e * s^2 + e * s * g - s^2 - s * g + g * a + a^2) // + (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), + e * s * g * a, + 2 * e * Ninv * s * g + 2 * Ninv * s * a + 2 * Ninv * g * a, + ] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # St. + # Regression test: + # Failed before, as the degrees of Groebner basis were too large + ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -e * S(t) - S(t) * d * W(t) + S(t) * r - S(t) * a * W(t) + R(t) * g, + R'(t) = e * S(t) + rR * R(t) + S(t) * a * W(t) - dr * R(t) * W(t) - R(t) * g, + W'(t) = T * Dd - W(t) * Dd, + y1(t) = S(t) + R(t), + y2(t) = T + ) + ident_funcs = [ + T, + Dd, + e - rR + dr * T + d * T + g - r + a * T, + (2 * rR * d - 2 * dr * r) // (dr - d), + (dr^2 + d^2 + 2 * d * a + a^2) // (dr * d + dr * a), + (e * dr - e * d + rR * a + dr * g - d * g - r * a) // (dr - d), + (e * dr^2 - e * dr * d + rR * dr * a + dr * d * g - dr * r * a - d^2 * g) // + (dr^2 + dr * a - d^2 - d * a), + ] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + # QY system. + # (this is a big one) + ode = StructuralIdentifiability.@ODEmodel( + P3'(t) = P4(t), + P0'(t) = P1(t), + P5'(t) = + ( + -P0(t) * beta_SI * phi * Mar * Ks * siga2 + + P0(t) * beta_SI * Mar * Ks * siga2 - + P0(t) * phi * M * Mar * Ks * beta_SA + + P0(t) * phi * M * Ks * siga2 * beta_SA + + P0(t) * M * Mar * Ks * beta_SA - P1(t) * beta_SI * phi * Mar * siga2 - + P1(t) * beta_SI * phi * Ks * siga2 + + P1(t) * beta_SI * Mar * siga2 + + P1(t) * beta_SI * Ks * siga2 - P1(t) * phi * M * Mar * beta_SA + + P1(t) * phi * M * siga2 * beta_SA - P1(t) * phi * Mar * Ks * beta_SA + + P1(t) * phi * Ks * siga2 * beta_SA + + P1(t) * M * Mar * beta_SA + + P1(t) * M * Ks * beta_SA + + P1(t) * Mar * Ks * beta_SA - beta_SI * phi * P2(t) * siga2 + + beta_SI * P2(t) * siga2 + + P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - + phi * M * beta * siga2 - phi * P2(t) * Mar * beta_SA + + phi * P2(t) * siga2 * beta_SA + + M * P2(t) * beta_SA + + M * Mar * P5(t) * siga2 + + M * beta * siga2 + + P2(t) * Mar * beta_SA + + P2(t) * Ks * beta_SA + ) // (phi * M * siga2 - M * siga2), + P4'(t) = + ( + -siga1 * P0(t)^2 * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + + siga1 * P0(t)^2 * beta_SI * M * Mar * Ks^2 * siga2^2 - + siga1 * P0(t)^2 * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + + siga1 * P0(t)^2 * phi * M^2 * Ks^2 * siga2^2 * beta_SA + + siga1 * P0(t)^2 * M^2 * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2 - + 2 * siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks * siga2^2 - + siga1 * P0(t) * P1(t) * beta_SI * phi * M * Ks^2 * siga2^2 - + siga1 * P0(t) * P1(t) * beta_SI * phi * Mar * Ks^2 * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2 + + 2 * siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * M * Ks^2 * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * Mar * Ks^2 * siga2^2 - + siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * beta_SA - + 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Ks * siga2^2 * beta_SA - + 2 * siga1 * P0(t) * P1(t) * phi * M * Mar * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * phi * M * Ks^2 * siga2^2 * beta_SA + + siga1 * P0(t) * P1(t) * M^2 * Mar * Ks^2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * M^2 * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * P1(t) * M^2 * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * M * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 + + siga1 * P0(t) * beta_SI * P3(t) * Mar * Ks * siga2 - + siga1 * P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - + siga1 * P0(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - + siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2 - + siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 + + siga1 * P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + + siga1 * P0(t) * beta_SI * M * P2(t) * Ks * siga2^2 + + siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2 + + siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 - + siga1 * P0(t) * P3(t) * phi * M * Mar * Ks * beta_SA + + siga1 * P0(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + + siga1 * P0(t) * P3(t) * M * Mar * Ks * beta_SA + + siga1 * P0(t) * P3(t) * M * Ks * siga2 * beta_SA - + siga1 * P0(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA + + siga1 * P0(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA - + siga1 * P0(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - + siga1 * P0(t) * phi * M^2 * Ks * beta * siga2^2 - + siga1 * P0(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - + 2 * siga1 * P0(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA + + siga1 * P0(t) * M^2 * P2(t) * Mar * Ks * beta_SA + + siga1 * P0(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + + siga1 * P0(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + + siga1 * P0(t) * M^2 * Ks * beta * siga2^2 + + siga1 * P0(t) * M * P2(t) * Mar * Ks^2 * beta_SA + + 2 * siga1 * P0(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * M * P2(t) * Ks^2 * siga2 * beta_SA - + siga1 * P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Mar * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Ks * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * Mar * Ks * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * Ks^2 * siga2^2 + + siga1 * P1(t)^2 * beta_SI * M * Mar * Ks * siga2 + + siga1 * P1(t)^2 * beta_SI * M * Mar * siga2^2 + + siga1 * P1(t)^2 * beta_SI * M * Ks^2 * siga2 + + siga1 * P1(t)^2 * beta_SI * M * Ks * siga2^2 + + siga1 * P1(t)^2 * beta_SI * Mar * Ks * siga2^2 + + siga1 * P1(t)^2 * beta_SI * Ks^2 * siga2^2 - + siga1 * P1(t)^2 * phi * M^2 * Mar * Ks * beta_SA - + siga1 * P1(t)^2 * phi * M^2 * Mar * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M^2 * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M^2 * siga2^2 * beta_SA - + siga1 * P1(t)^2 * phi * M * Mar * Ks^2 * beta_SA - + 2 * siga1 * P1(t)^2 * phi * M * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P1(t)^2 * phi * M * Ks * siga2^2 * beta_SA - + siga1 * P1(t)^2 * phi * Mar * Ks^2 * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * Ks^2 * siga2^2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Mar * Ks * beta_SA + + siga1 * P1(t)^2 * M^2 * Mar * siga2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Ks^2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * M * Mar * Ks^2 * beta_SA + + 2 * siga1 * P1(t)^2 * M * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * M * Ks^2 * siga2 * beta_SA + + siga1 * P1(t)^2 * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P1(t) * beta_SI * P3(t) * phi * Mar * siga2 - + siga1 * P1(t) * beta_SI * P3(t) * phi * Ks * siga2 + + siga1 * P1(t) * beta_SI * P3(t) * Mar * siga2 + + siga1 * P1(t) * beta_SI * P3(t) * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2 - + 2 * siga1 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * M * P2(t) * siga2^2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * siga2^2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2 - + 2 * siga1 * P1(t) * beta_SI * phi * P2(t) * Ks * siga2^2 + + siga1 * P1(t) * beta_SI * M * P2(t) * Mar * siga2 + + 2 * siga1 * P1(t) * beta_SI * M * P2(t) * Ks * siga2 + + siga1 * P1(t) * beta_SI * M * P2(t) * siga2^2 + + siga1 * P1(t) * beta_SI * P2(t) * Mar * Ks * siga2 + + siga1 * P1(t) * beta_SI * P2(t) * Mar * siga2^2 + + siga1 * P1(t) * beta_SI * P2(t) * Ks^2 * siga2 + + 2 * siga1 * P1(t) * beta_SI * P2(t) * Ks * siga2^2 - + siga1 * P1(t) * P3(t) * phi * M * Mar * beta_SA + + siga1 * P1(t) * P3(t) * phi * M * siga2 * beta_SA - + siga1 * P1(t) * P3(t) * phi * Mar * Ks * beta_SA + + siga1 * P1(t) * P3(t) * phi * Ks * siga2 * beta_SA + + siga1 * P1(t) * P3(t) * M * Mar * beta_SA + + 2 * siga1 * P1(t) * P3(t) * M * Ks * beta_SA + + siga1 * P1(t) * P3(t) * M * siga2 * beta_SA + + siga1 * P1(t) * P3(t) * Mar * Ks * beta_SA + + siga1 * P1(t) * P3(t) * Ks * siga2 * beta_SA - + siga1 * P1(t) * phi * M^2 * P2(t) * Mar * beta_SA + + siga1 * P1(t) * phi * M^2 * P2(t) * siga2 * beta_SA - + siga1 * P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2 - + siga1 * P1(t) * phi * M^2 * Mar * P5(t) * siga2^2 - + siga1 * P1(t) * phi * M^2 * Ks * beta * siga2 - + siga1 * P1(t) * phi * M^2 * Ks * siga2^2 - + siga1 * P1(t) * phi * M^2 * beta * siga2^2 - + 3 * siga1 * P1(t) * phi * M * P2(t) * Mar * Ks * beta_SA - + 2 * siga1 * P1(t) * phi * M * P2(t) * Mar * siga2 * beta_SA + + 3 * siga1 * P1(t) * phi * M * P2(t) * Ks * siga2 * beta_SA + + 2 * siga1 * P1(t) * phi * M * P2(t) * siga2^2 * beta_SA - + siga1 * P1(t) * phi * M * Mar * P5(t) * Ks * siga2^2 - + siga1 * P1(t) * phi * M * Ks * beta * siga2^2 - + siga1 * P1(t) * phi * P2(t) * Mar * Ks^2 * beta_SA - + 2 * siga1 * P1(t) * phi * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t) * phi * P2(t) * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P1(t) * phi * P2(t) * Ks * siga2^2 * beta_SA + + siga1 * P1(t) * M^2 * P2(t) * Mar * beta_SA + + 2 * siga1 * P1(t) * M^2 * P2(t) * Ks * beta_SA + + siga1 * P1(t) * M^2 * P2(t) * siga2 * beta_SA + + siga1 * P1(t) * M^2 * Mar * P5(t) * Ks * siga2 + + siga1 * P1(t) * M^2 * Mar * P5(t) * siga2^2 + + siga1 * P1(t) * M^2 * Ks * beta * siga2 + + siga1 * P1(t) * M^2 * Ks * siga2^2 + + siga1 * P1(t) * M^2 * beta * siga2^2 + + 3 * siga1 * P1(t) * M * P2(t) * Mar * Ks * beta_SA + + 2 * siga1 * P1(t) * M * P2(t) * Mar * siga2 * beta_SA + + 2 * siga1 * P1(t) * M * P2(t) * Ks^2 * beta_SA + + 3 * siga1 * P1(t) * M * P2(t) * Ks * siga2 * beta_SA + + siga1 * P1(t) * M * Mar * P5(t) * Ks * siga2^2 + + siga1 * P1(t) * M * Ks * beta * siga2^2 + + siga1 * P1(t) * P2(t) * Mar * Ks^2 * beta_SA + + 2 * siga1 * P1(t) * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t) * P2(t) * Ks^2 * siga2 * beta_SA - + siga1 * beta_SI * P3(t) * phi * P2(t) * siga2 + + siga1 * beta_SI * P3(t) * P2(t) * siga2 - + siga1 * beta_SI * phi * M * P2(t)^2 * siga2 - + siga1 * beta_SI * phi * P2(t)^2 * Ks * siga2 - + siga1 * beta_SI * phi * P2(t)^2 * siga2^2 + + siga1 * beta_SI * M * P2(t)^2 * siga2 + + siga1 * beta_SI * P2(t)^2 * Ks * siga2 + + siga1 * beta_SI * P2(t)^2 * siga2^2 + + siga1 * P3(t)^2 * beta_SA - siga1 * P3(t) * phi * M^2 * siga2 - + siga1 * P3(t) * phi * M * Mar * P5(t) * siga2 - + siga1 * P3(t) * phi * M * Ks * siga2 - + siga1 * P3(t) * phi * M * beta * siga2 - + siga1 * P3(t) * phi * M * siga2^2 - + siga1 * P3(t) * phi * P2(t) * Mar * beta_SA + + siga1 * P3(t) * phi * P2(t) * siga2 * beta_SA + + siga1 * P3(t) * M^2 * siga2 + + 2 * siga1 * P3(t) * M * P2(t) * beta_SA + + siga1 * P3(t) * M * Mar * P5(t) * siga2 + + siga1 * P3(t) * M * Ks * siga2 + + siga1 * P3(t) * M * beta * siga2 + + siga1 * P3(t) * M * siga2^2 + + siga1 * P3(t) * P2(t) * Mar * beta_SA + + 2 * siga1 * P3(t) * P2(t) * Ks * beta_SA + + siga1 * P3(t) * P2(t) * siga2 * beta_SA - + siga1 * phi * M^2 * P2(t) * Mar * P5(t) * siga2 - + siga1 * phi * M^2 * P2(t) * Ks * siga2 - + siga1 * phi * M^2 * P2(t) * beta * siga2 - + siga1 * phi * M^2 * P2(t) * siga2^2 - siga1 * phi * M * P4(t) * siga2 - + siga1 * phi * M * P2(t)^2 * Mar * beta_SA + + siga1 * phi * M * P2(t)^2 * siga2 * beta_SA - + siga1 * phi * M * P2(t) * Mar * P5(t) * Ks * siga2 - + siga1 * phi * M * P2(t) * Mar * P5(t) * siga2^2 - + siga1 * phi * M * P2(t) * Ks * beta * siga2 - + siga1 * phi * M * P2(t) * Ks * siga2^2 - + siga1 * phi * M * P2(t) * beta * siga2^2 - + siga1 * phi * P2(t)^2 * Mar * Ks * beta_SA - + siga1 * phi * P2(t)^2 * Mar * siga2 * beta_SA + + siga1 * phi * P2(t)^2 * Ks * siga2 * beta_SA + + siga1 * phi * P2(t)^2 * siga2^2 * beta_SA + + siga1 * M^2 * P2(t)^2 * beta_SA + + siga1 * M^2 * P2(t) * Mar * P5(t) * siga2 + + siga1 * M^2 * P2(t) * Ks * siga2 + + siga1 * M^2 * P2(t) * beta * siga2 + + siga1 * M^2 * P2(t) * siga2^2 + + siga1 * M * P4(t) * siga2 + + siga1 * M * P2(t)^2 * Mar * beta_SA + + 2 * siga1 * M * P2(t)^2 * Ks * beta_SA + + siga1 * M * P2(t)^2 * siga2 * beta_SA + + siga1 * M * P2(t) * Mar * P5(t) * Ks * siga2 + + siga1 * M * P2(t) * Mar * P5(t) * siga2^2 + + siga1 * M * P2(t) * Ks * beta * siga2 + + siga1 * M * P2(t) * Ks * siga2^2 + + siga1 * M * P2(t) * beta * siga2^2 + + siga1 * P2(t)^2 * Mar * Ks * beta_SA + + siga1 * P2(t)^2 * Mar * siga2 * beta_SA + + siga1 * P2(t)^2 * Ks^2 * beta_SA + + siga1 * P2(t)^2 * Ks * siga2 * beta_SA - + P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + + P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2^2 - + P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + + P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2^2 * beta_SA + + P0(t) * P1(t) * M^2 * Mar * Ks^2 * siga2 * beta_SA - + P0(t) * beta_SI * P3(t) * phi * M * Mar * Ks * siga2 - + P0(t) * beta_SI * P3(t) * phi * Mar * Ks^2 * siga2 - + P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2^2 + + P0(t) * beta_SI * P3(t) * M * Mar * Ks * siga2 + + P0(t) * beta_SI * P3(t) * Mar * Ks^2 * siga2 + + P0(t) * beta_SI * P3(t) * Mar * Ks * siga2^2 - + P0(t) * beta_SI * phi * alpa * Mar * Ks * siga2 - + P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks^2 * siga2 - + P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2^2 - + P0(t) * beta_SI * phi * P4(t) * Mar * Ks * siga2 - + P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2^2 + + P0(t) * beta_SI * alpa * Mar * Ks * siga2 + + P0(t) * beta_SI * M * P2(t) * Mar * Ks^2 * siga2 + + P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2^2 + + P0(t) * beta_SI * P4(t) * Mar * Ks * siga2 + + P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2^2 - + P0(t) * P3(t) * phi * M^2 * Mar * Ks * beta_SA + + P0(t) * P3(t) * phi * M^2 * Ks * siga2 * beta_SA - + P0(t) * P3(t) * phi * M * Mar * Ks^2 * beta_SA - + P0(t) * P3(t) * phi * M * Mar * Ks * siga2 * beta_SA + + P0(t) * P3(t) * phi * M * Ks^2 * siga2 * beta_SA + + P0(t) * P3(t) * phi * M * Ks * siga2^2 * beta_SA + + P0(t) * P3(t) * M^2 * Mar * Ks * beta_SA + + P0(t) * P3(t) * M * Mar * Ks^2 * beta_SA + + P0(t) * P3(t) * M * Mar * Ks * siga2 * beta_SA - + P0(t) * phi * alpa * M * Mar * Ks * beta_SA + + P0(t) * phi * alpa * M * Ks * siga2 * beta_SA - + P0(t) * phi * M^2 * P2(t) * Mar * Ks^2 * beta_SA - + P0(t) * phi * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + + P0(t) * phi * M^2 * P2(t) * Ks^2 * siga2 * beta_SA + + P0(t) * phi * M^2 * P2(t) * Ks * siga2^2 * beta_SA - + P0(t) * phi * M * P4(t) * Mar * Ks * beta_SA + + P0(t) * phi * M * P4(t) * Ks * siga2 * beta_SA - + P0(t) * phi * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA + + P0(t) * phi * M * P2(t) * Ks^2 * siga2^2 * beta_SA + + P0(t) * alpa * M * Mar * Ks * beta_SA + + P0(t) * M^2 * P2(t) * Mar * Ks^2 * beta_SA + + P0(t) * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + + P0(t) * M * P4(t) * Mar * Ks * beta_SA + + P0(t) * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA - + P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2^2 - + P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2^2 + + P1(t)^2 * beta_SI * M * Mar * Ks * siga2^2 + + P1(t)^2 * beta_SI * M * Ks^2 * siga2^2 - + P1(t)^2 * phi * M^2 * Mar * Ks * siga2 * beta_SA + + P1(t)^2 * phi * M^2 * Ks * siga2^2 * beta_SA - + P1(t)^2 * phi * M * Mar * Ks^2 * siga2 * beta_SA + + P1(t)^2 * phi * M * Ks^2 * siga2^2 * beta_SA + + P1(t)^2 * M^2 * Mar * Ks * siga2 * beta_SA + + P1(t)^2 * M^2 * Ks^2 * siga2 * beta_SA + + P1(t)^2 * M * Mar * Ks^2 * siga2 * beta_SA - + P1(t) * beta_SI * P3(t) * phi * M * Mar * siga2 - + P1(t) * beta_SI * P3(t) * phi * M * Ks * siga2 - + P1(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 - + P1(t) * beta_SI * P3(t) * phi * Mar * siga2^2 - + P1(t) * beta_SI * P3(t) * phi * Ks^2 * siga2 - + P1(t) * beta_SI * P3(t) * phi * Ks * siga2^2 + + P1(t) * beta_SI * P3(t) * M * Mar * siga2 + + P1(t) * beta_SI * P3(t) * M * Ks * siga2 + + P1(t) * beta_SI * P3(t) * Mar * Ks * siga2 + + P1(t) * beta_SI * P3(t) * Mar * siga2^2 + + P1(t) * beta_SI * P3(t) * Ks^2 * siga2 + + P1(t) * beta_SI * P3(t) * Ks * siga2^2 - + P1(t) * beta_SI * phi * alpa * Mar * siga2 - + P1(t) * beta_SI * phi * alpa * Ks * siga2 - + P1(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - + P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2^2 - + P1(t) * beta_SI * phi * M * P2(t) * Ks^2 * siga2 - + 2 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - + P1(t) * beta_SI * phi * P4(t) * Mar * siga2 - + P1(t) * beta_SI * phi * P4(t) * Ks * siga2 - + P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 - + P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2^2 + + P1(t) * beta_SI * alpa * Mar * siga2 + + P1(t) * beta_SI * alpa * Ks * siga2 + + P1(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + + P1(t) * beta_SI * M * P2(t) * Mar * siga2^2 + + P1(t) * beta_SI * M * P2(t) * Ks^2 * siga2 + + 2 * P1(t) * beta_SI * M * P2(t) * Ks * siga2^2 + + P1(t) * beta_SI * P4(t) * Mar * siga2 + + P1(t) * beta_SI * P4(t) * Ks * siga2 + + P1(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 + + P1(t) * beta_SI * P2(t) * Ks^2 * siga2^2 - + P1(t) * P3(t) * phi * M^2 * Mar * beta_SA + + P1(t) * P3(t) * phi * M^2 * siga2 * beta_SA - + 2 * P1(t) * P3(t) * phi * M * Mar * Ks * beta_SA - + P1(t) * P3(t) * phi * M * Mar * siga2 * beta_SA + + 2 * P1(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + + P1(t) * P3(t) * phi * M * siga2^2 * beta_SA - + P1(t) * P3(t) * phi * Mar * Ks^2 * beta_SA - + P1(t) * P3(t) * phi * Mar * Ks * siga2 * beta_SA + + P1(t) * P3(t) * phi * Ks^2 * siga2 * beta_SA + + P1(t) * P3(t) * phi * Ks * siga2^2 * beta_SA + + P1(t) * P3(t) * M^2 * Mar * beta_SA + + P1(t) * P3(t) * M^2 * Ks * beta_SA + + 2 * P1(t) * P3(t) * M * Mar * Ks * beta_SA + + P1(t) * P3(t) * M * Mar * siga2 * beta_SA + + P1(t) * P3(t) * M * Ks^2 * beta_SA + + 2 * P1(t) * P3(t) * M * Ks * siga2 * beta_SA + + P1(t) * P3(t) * Mar * Ks^2 * beta_SA + + P1(t) * P3(t) * Mar * Ks * siga2 * beta_SA - + P1(t) * phi * alpa * M * Mar * beta_SA + + P1(t) * phi * alpa * M * siga2 * beta_SA - + P1(t) * phi * alpa * Mar * Ks * beta_SA + + P1(t) * phi * alpa * Ks * siga2 * beta_SA - + P1(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA - + P1(t) * phi * M^2 * P2(t) * Mar * siga2 * beta_SA + + P1(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA + + P1(t) * phi * M^2 * P2(t) * siga2^2 * beta_SA - + P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - + P1(t) * phi * M^2 * Ks * beta * siga2^2 - + P1(t) * phi * M * P4(t) * Mar * beta_SA + + P1(t) * phi * M * P4(t) * siga2 * beta_SA - + P1(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - + 3 * P1(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + + P1(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + + 3 * P1(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA - + P1(t) * phi * P4(t) * Mar * Ks * beta_SA + + P1(t) * phi * P4(t) * Ks * siga2 * beta_SA - + P1(t) * phi * P2(t) * Mar * Ks^2 * siga2 * beta_SA + + P1(t) * phi * P2(t) * Ks^2 * siga2^2 * beta_SA + + P1(t) * alpa * M * Mar * beta_SA + + P1(t) * alpa * M * Ks * beta_SA + + P1(t) * alpa * Mar * Ks * beta_SA + + P1(t) * M^2 * P2(t) * Mar * Ks * beta_SA + + P1(t) * M^2 * P2(t) * Mar * siga2 * beta_SA + + P1(t) * M^2 * P2(t) * Ks^2 * beta_SA + + 2 * P1(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + + P1(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + + P1(t) * M^2 * Ks * beta * siga2^2 + + P1(t) * M * P4(t) * Mar * beta_SA + + P1(t) * M * P4(t) * Ks * beta_SA + + P1(t) * M * P2(t) * Mar * Ks^2 * beta_SA + + 3 * P1(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + + 2 * P1(t) * M * P2(t) * Ks^2 * siga2 * beta_SA + + P1(t) * P4(t) * Mar * Ks * beta_SA + + P1(t) * P2(t) * Mar * Ks^2 * siga2 * beta_SA - + beta_SI * P3(t) * phi * M * P2(t) * siga2 - + beta_SI * P3(t) * phi * P2(t) * Ks * siga2 - + beta_SI * P3(t) * phi * P2(t) * siga2^2 + + beta_SI * P3(t) * M * P2(t) * siga2 + + beta_SI * P3(t) * P2(t) * Ks * siga2 + + beta_SI * P3(t) * P2(t) * siga2^2 - + beta_SI * phi * alpa * P2(t) * siga2 - + beta_SI * phi * M * P2(t)^2 * Ks * siga2 - + beta_SI * phi * M * P2(t)^2 * siga2^2 - + beta_SI * phi * P4(t) * P2(t) * siga2 - + beta_SI * phi * P2(t)^2 * Ks * siga2^2 + + beta_SI * alpa * P2(t) * siga2 + + beta_SI * M * P2(t)^2 * Ks * siga2 + + beta_SI * M * P2(t)^2 * siga2^2 + + beta_SI * P4(t) * P2(t) * siga2 + + beta_SI * P2(t)^2 * Ks * siga2^2 + + P3(t)^2 * M * beta_SA + + P3(t)^2 * Ks * beta_SA + + P3(t)^2 * siga2 * beta_SA - P3(t) * phi * M^2 * Mar * P5(t) * siga2 - + P3(t) * phi * M^2 * Ks * siga2 - P3(t) * phi * M^2 * beta * siga2 - + P3(t) * phi * M^2 * siga2^2 - P3(t) * phi * M * P2(t) * Mar * beta_SA + + P3(t) * phi * M * P2(t) * siga2 * beta_SA - + P3(t) * phi * M * Mar * P5(t) * Ks * siga2 - + P3(t) * phi * M * Mar * P5(t) * siga2^2 - + P3(t) * phi * M * Ks * beta * siga2 - P3(t) * phi * M * Ks * siga2^2 - + P3(t) * phi * M * beta * siga2^2 - + P3(t) * phi * P2(t) * Mar * Ks * beta_SA - + P3(t) * phi * P2(t) * Mar * siga2 * beta_SA + + P3(t) * phi * P2(t) * Ks * siga2 * beta_SA + + P3(t) * phi * P2(t) * siga2^2 * beta_SA + + P3(t) * alpa * beta_SA + + P3(t) * M^2 * P2(t) * beta_SA + + P3(t) * M^2 * Mar * P5(t) * siga2 + + P3(t) * M^2 * Ks * siga2 + + P3(t) * M^2 * beta * siga2 + + P3(t) * M^2 * siga2^2 + + P3(t) * M * P2(t) * Mar * beta_SA + + 3 * P3(t) * M * P2(t) * Ks * beta_SA + + 2 * P3(t) * M * P2(t) * siga2 * beta_SA + + P3(t) * M * Mar * P5(t) * Ks * siga2 + + P3(t) * M * Mar * P5(t) * siga2^2 + + P3(t) * M * Ks * beta * siga2 + + P3(t) * M * Ks * siga2^2 + + P3(t) * M * beta * siga2^2 + + P3(t) * P4(t) * beta_SA + + P3(t) * P2(t) * Mar * Ks * beta_SA + + P3(t) * P2(t) * Mar * siga2 * beta_SA + + P3(t) * P2(t) * Ks^2 * beta_SA + + 2 * P3(t) * P2(t) * Ks * siga2 * beta_SA - + phi * alpa * M * Mar * P5(t) * siga2 - phi * alpa * M * beta * siga2 - + phi * alpa * P2(t) * Mar * beta_SA + + phi * alpa * P2(t) * siga2 * beta_SA - phi * M^2 * P4(t) * siga2 - + phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - + phi * M^2 * P2(t) * Mar * P5(t) * siga2^2 - + phi * M^2 * P2(t) * Ks * beta * siga2 - + phi * M^2 * P2(t) * Ks * siga2^2 - phi * M^2 * P2(t) * beta * siga2^2 - + phi * M * P4(t) * Mar * P5(t) * siga2 - phi * M * P4(t) * Ks * siga2 - + phi * M * P4(t) * beta * siga2 - phi * M * P4(t) * siga2^2 - + phi * M * P2(t)^2 * Mar * Ks * beta_SA - + phi * M * P2(t)^2 * Mar * siga2 * beta_SA + + phi * M * P2(t)^2 * Ks * siga2 * beta_SA + + phi * M * P2(t)^2 * siga2^2 * beta_SA - + phi * M * P2(t) * Mar * P5(t) * Ks * siga2^2 - + phi * M * P2(t) * Ks * beta * siga2^2 - + phi * P4(t) * P2(t) * Mar * beta_SA + + phi * P4(t) * P2(t) * siga2 * beta_SA - + phi * P2(t)^2 * Mar * Ks * siga2 * beta_SA + + phi * P2(t)^2 * Ks * siga2^2 * beta_SA + + alpa * M * P2(t) * beta_SA + + alpa * M * Mar * P5(t) * siga2 + + alpa * M * beta * siga2 + + alpa * P2(t) * Mar * beta_SA + + alpa * P2(t) * Ks * beta_SA + + M^2 * P4(t) * siga2 + + M^2 * P2(t)^2 * Ks * beta_SA + + M^2 * P2(t)^2 * siga2 * beta_SA + + M^2 * P2(t) * Mar * P5(t) * Ks * siga2 + + M^2 * P2(t) * Mar * P5(t) * siga2^2 + + M^2 * P2(t) * Ks * beta * siga2 + + M^2 * P2(t) * Ks * siga2^2 + + M^2 * P2(t) * beta * siga2^2 + + M * P4(t) * P2(t) * beta_SA + + M * P4(t) * Mar * P5(t) * siga2 + + M * P4(t) * Ks * siga2 + + M * P4(t) * beta * siga2 + + M * P4(t) * siga2^2 + + M * P2(t)^2 * Mar * Ks * beta_SA + + M * P2(t)^2 * Mar * siga2 * beta_SA + + M * P2(t)^2 * Ks^2 * beta_SA + + 2 * M * P2(t)^2 * Ks * siga2 * beta_SA + + M * P2(t) * Mar * P5(t) * Ks * siga2^2 + + M * P2(t) * Ks * beta * siga2^2 + + P4(t) * P2(t) * Mar * beta_SA + + P4(t) * P2(t) * Ks * beta_SA + + P2(t)^2 * Mar * Ks * siga2 * beta_SA + + P2(t)^2 * Ks^2 * siga2 * beta_SA + ) // (phi * M * siga2 - M * siga2), + P1'(t) = P2(t), + P2'(t) = P3(t), + y(t) = P0(t) + ) + ident_funcs = [ + Mar, + Ks, + alpa, + (siga1 + phi * Mar - Mar) // phi, + (siga1 * phi - siga1 - phi * Mar + Mar) // (siga1 * phi * beta_SA), ( - k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - k6 * x5(t) * k7 - x5(t) * k7 * x4(t) - ) // ( - k8 * k6 + k8 * x4(t) + k6 * x6(t) + k6 * x5(t) + x6(t) * x4(t) + x5(t) * x4(t) - ), - x7'(t) = (k9 * k10 * x6(t) - k9 * x6(t)^2) // k10, - x4'(t) = (-k5 * x4(t)) // (k6 + x4(t)), - x6'(t) = + siga1 * beta_SA + beta_SI * phi * siga2 + phi * Mar * beta_SA - + phi * siga2 * beta_SA - Mar * beta_SA + ) // (phi * M * siga2), ( - -k8 * k9 * k10 * x6(t) + k8 * k9 * x6(t)^2 - k9 * k10 * x6(t)^2 - - k9 * k10 * x6(t) * x5(t) + - k9 * x6(t)^3 + - k9 * x6(t)^2 * x5(t) + - k10 * x5(t) * k7 - ) // (k8 * k10 + k10 * x6(t) + k10 * x5(t)), - y1(t) = x4(t), - y2(t) = x5(t) -) -ident_funcs = [k7, k6, k5, k10^2, k9 * k10, k8 + 1 // 2 * k10] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# SLIQR -ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -b * In(t) * S(t) * Ninv - S(t) * Ninv * u(t), - In'(t) = -In(t) * g + s * Q(t) + a * L(t), - L'(t) = b * In(t) * S(t) * Ninv - a * L(t), - Q'(t) = -e * In(t) * g + In(t) * g - s * Q(t), - y(t) = In(t) * Ninv -) -ident_funcs = [ - g + a, - s + g + a, - s, - Ninv, - b, - (e * s - s + a) // (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), - e * s * g + s * a + g * a, - (e * s^2 + e * s * g - s^2 - s * g + g * a + a^2) // - (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), - e * s * g * a, - 2 * e * Ninv * s * g + 2 * Ninv * s * a + 2 * Ninv * g * a, -] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# St. -# Regression test: -# Failed before, as the degrees of Groebner basis were too large -ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -e * S(t) - S(t) * d * W(t) + S(t) * r - S(t) * a * W(t) + R(t) * g, - R'(t) = e * S(t) + rR * R(t) + S(t) * a * W(t) - dr * R(t) * W(t) - R(t) * g, - W'(t) = T * Dd - W(t) * Dd, - y1(t) = S(t) + R(t), - y2(t) = T -) -ident_funcs = [ - T, - Dd, - e - rR + dr * T + d * T + g - r + a * T, - (2 * rR * d - 2 * dr * r) // (dr - d), - (dr^2 + d^2 + 2 * d * a + a^2) // (dr * d + dr * a), - (e * dr - e * d + rR * a + dr * g - d * g - r * a) // (dr - d), - (e * dr^2 - e * dr * d + rR * dr * a + dr * d * g - dr * r * a - d^2 * g) // - (dr^2 + dr * a - d^2 - d * a), -] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -# QY system. -# (this is a big one) -ode = StructuralIdentifiability.@ODEmodel( - P3'(t) = P4(t), - P0'(t) = P1(t), - P5'(t) = + siga1 * phi * M * siga2 - siga1 * M * siga2 - phi * M * Mar * siga2 + + M * Mar * siga2 + ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), ( - -P0(t) * beta_SI * phi * Mar * Ks * siga2 + P0(t) * beta_SI * Mar * Ks * siga2 - P0(t) * phi * M * Mar * Ks * beta_SA + - P0(t) * phi * M * Ks * siga2 * beta_SA + - P0(t) * M * Mar * Ks * beta_SA - P1(t) * beta_SI * phi * Mar * siga2 - - P1(t) * beta_SI * phi * Ks * siga2 + - P1(t) * beta_SI * Mar * siga2 + - P1(t) * beta_SI * Ks * siga2 - P1(t) * phi * M * Mar * beta_SA + - P1(t) * phi * M * siga2 * beta_SA - P1(t) * phi * Mar * Ks * beta_SA + - P1(t) * phi * Ks * siga2 * beta_SA + - P1(t) * M * Mar * beta_SA + - P1(t) * M * Ks * beta_SA + - P1(t) * Mar * Ks * beta_SA - beta_SI * phi * P2(t) * siga2 + - beta_SI * P2(t) * siga2 + - P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - phi * M * beta * siga2 - - phi * P2(t) * Mar * beta_SA + - phi * P2(t) * siga2 * beta_SA + - M * P2(t) * beta_SA + - M * Mar * P5(t) * siga2 + - M * beta * siga2 + - P2(t) * Mar * beta_SA + - P2(t) * Ks * beta_SA - ) // (phi * M * siga2 - M * siga2), - P4'(t) = + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 - + siga1 * phi * siga2 * beta_SA - siga1 * M * beta_SA - + beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - + phi * M * Mar * beta_SA + M * Mar * beta_SA + ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), ( - -siga1 * P0(t)^2 * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + - siga1 * P0(t)^2 * beta_SI * M * Mar * Ks^2 * siga2^2 - - siga1 * P0(t)^2 * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + - siga1 * P0(t)^2 * phi * M^2 * Ks^2 * siga2^2 * beta_SA + - siga1 * P0(t)^2 * M^2 * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2 - - 2 * siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks * siga2^2 - - siga1 * P0(t) * P1(t) * beta_SI * phi * M * Ks^2 * siga2^2 - - siga1 * P0(t) * P1(t) * beta_SI * phi * Mar * Ks^2 * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2 + - 2 * siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * M * Ks^2 * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * Mar * Ks^2 * siga2^2 - - siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * beta_SA - - 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Ks * siga2^2 * beta_SA - - 2 * siga1 * P0(t) * P1(t) * phi * M * Mar * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * phi * M * Ks^2 * siga2^2 * beta_SA + - siga1 * P0(t) * P1(t) * M^2 * Mar * Ks^2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * M^2 * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * P1(t) * M^2 * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * M * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 + - siga1 * P0(t) * beta_SI * P3(t) * Mar * Ks * siga2 - - siga1 * P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - - siga1 * P0(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - - siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2 - - siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 + - siga1 * P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + - siga1 * P0(t) * beta_SI * M * P2(t) * Ks * siga2^2 + - siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2 + - siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 - - siga1 * P0(t) * P3(t) * phi * M * Mar * Ks * beta_SA + - siga1 * P0(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + - siga1 * P0(t) * P3(t) * M * Mar * Ks * beta_SA + - siga1 * P0(t) * P3(t) * M * Ks * siga2 * beta_SA - - siga1 * P0(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA + - siga1 * P0(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA - - siga1 * P0(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - - siga1 * P0(t) * phi * M^2 * Ks * beta * siga2^2 - - siga1 * P0(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - - 2 * siga1 * P0(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA + - siga1 * P0(t) * M^2 * P2(t) * Mar * Ks * beta_SA + - siga1 * P0(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + - siga1 * P0(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + - siga1 * P0(t) * M^2 * Ks * beta * siga2^2 + - siga1 * P0(t) * M * P2(t) * Mar * Ks^2 * beta_SA + - 2 * siga1 * P0(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * M * P2(t) * Ks^2 * siga2 * beta_SA - - siga1 * P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Mar * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Ks * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * Mar * Ks * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * Ks^2 * siga2^2 + - siga1 * P1(t)^2 * beta_SI * M * Mar * Ks * siga2 + - siga1 * P1(t)^2 * beta_SI * M * Mar * siga2^2 + - siga1 * P1(t)^2 * beta_SI * M * Ks^2 * siga2 + - siga1 * P1(t)^2 * beta_SI * M * Ks * siga2^2 + - siga1 * P1(t)^2 * beta_SI * Mar * Ks * siga2^2 + - siga1 * P1(t)^2 * beta_SI * Ks^2 * siga2^2 - - siga1 * P1(t)^2 * phi * M^2 * Mar * Ks * beta_SA - - siga1 * P1(t)^2 * phi * M^2 * Mar * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M^2 * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M^2 * siga2^2 * beta_SA - - siga1 * P1(t)^2 * phi * M * Mar * Ks^2 * beta_SA - - 2 * siga1 * P1(t)^2 * phi * M * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P1(t)^2 * phi * M * Ks * siga2^2 * beta_SA - - siga1 * P1(t)^2 * phi * Mar * Ks^2 * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * Ks^2 * siga2^2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Mar * Ks * beta_SA + - siga1 * P1(t)^2 * M^2 * Mar * siga2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Ks^2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * M * Mar * Ks^2 * beta_SA + - 2 * siga1 * P1(t)^2 * M * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * M * Ks^2 * siga2 * beta_SA + - siga1 * P1(t)^2 * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P1(t) * beta_SI * P3(t) * phi * Mar * siga2 - - siga1 * P1(t) * beta_SI * P3(t) * phi * Ks * siga2 + - siga1 * P1(t) * beta_SI * P3(t) * Mar * siga2 + - siga1 * P1(t) * beta_SI * P3(t) * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2 - - 2 * siga1 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * M * P2(t) * siga2^2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * siga2^2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2 - - 2 * siga1 * P1(t) * beta_SI * phi * P2(t) * Ks * siga2^2 + - siga1 * P1(t) * beta_SI * M * P2(t) * Mar * siga2 + - 2 * siga1 * P1(t) * beta_SI * M * P2(t) * Ks * siga2 + - siga1 * P1(t) * beta_SI * M * P2(t) * siga2^2 + - siga1 * P1(t) * beta_SI * P2(t) * Mar * Ks * siga2 + - siga1 * P1(t) * beta_SI * P2(t) * Mar * siga2^2 + - siga1 * P1(t) * beta_SI * P2(t) * Ks^2 * siga2 + - 2 * siga1 * P1(t) * beta_SI * P2(t) * Ks * siga2^2 - - siga1 * P1(t) * P3(t) * phi * M * Mar * beta_SA + - siga1 * P1(t) * P3(t) * phi * M * siga2 * beta_SA - - siga1 * P1(t) * P3(t) * phi * Mar * Ks * beta_SA + - siga1 * P1(t) * P3(t) * phi * Ks * siga2 * beta_SA + - siga1 * P1(t) * P3(t) * M * Mar * beta_SA + - 2 * siga1 * P1(t) * P3(t) * M * Ks * beta_SA + - siga1 * P1(t) * P3(t) * M * siga2 * beta_SA + - siga1 * P1(t) * P3(t) * Mar * Ks * beta_SA + - siga1 * P1(t) * P3(t) * Ks * siga2 * beta_SA - - siga1 * P1(t) * phi * M^2 * P2(t) * Mar * beta_SA + - siga1 * P1(t) * phi * M^2 * P2(t) * siga2 * beta_SA - - siga1 * P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2 - - siga1 * P1(t) * phi * M^2 * Mar * P5(t) * siga2^2 - - siga1 * P1(t) * phi * M^2 * Ks * beta * siga2 - - siga1 * P1(t) * phi * M^2 * Ks * siga2^2 - - siga1 * P1(t) * phi * M^2 * beta * siga2^2 - - 3 * siga1 * P1(t) * phi * M * P2(t) * Mar * Ks * beta_SA - - 2 * siga1 * P1(t) * phi * M * P2(t) * Mar * siga2 * beta_SA + - 3 * siga1 * P1(t) * phi * M * P2(t) * Ks * siga2 * beta_SA + - 2 * siga1 * P1(t) * phi * M * P2(t) * siga2^2 * beta_SA - - siga1 * P1(t) * phi * M * Mar * P5(t) * Ks * siga2^2 - - siga1 * P1(t) * phi * M * Ks * beta * siga2^2 - - siga1 * P1(t) * phi * P2(t) * Mar * Ks^2 * beta_SA - - 2 * siga1 * P1(t) * phi * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t) * phi * P2(t) * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P1(t) * phi * P2(t) * Ks * siga2^2 * beta_SA + - siga1 * P1(t) * M^2 * P2(t) * Mar * beta_SA + - 2 * siga1 * P1(t) * M^2 * P2(t) * Ks * beta_SA + - siga1 * P1(t) * M^2 * P2(t) * siga2 * beta_SA + - siga1 * P1(t) * M^2 * Mar * P5(t) * Ks * siga2 + - siga1 * P1(t) * M^2 * Mar * P5(t) * siga2^2 + - siga1 * P1(t) * M^2 * Ks * beta * siga2 + - siga1 * P1(t) * M^2 * Ks * siga2^2 + - siga1 * P1(t) * M^2 * beta * siga2^2 + - 3 * siga1 * P1(t) * M * P2(t) * Mar * Ks * beta_SA + - 2 * siga1 * P1(t) * M * P2(t) * Mar * siga2 * beta_SA + - 2 * siga1 * P1(t) * M * P2(t) * Ks^2 * beta_SA + - 3 * siga1 * P1(t) * M * P2(t) * Ks * siga2 * beta_SA + - siga1 * P1(t) * M * Mar * P5(t) * Ks * siga2^2 + - siga1 * P1(t) * M * Ks * beta * siga2^2 + - siga1 * P1(t) * P2(t) * Mar * Ks^2 * beta_SA + - 2 * siga1 * P1(t) * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t) * P2(t) * Ks^2 * siga2 * beta_SA - - siga1 * beta_SI * P3(t) * phi * P2(t) * siga2 + - siga1 * beta_SI * P3(t) * P2(t) * siga2 - - siga1 * beta_SI * phi * M * P2(t)^2 * siga2 - - siga1 * beta_SI * phi * P2(t)^2 * Ks * siga2 - - siga1 * beta_SI * phi * P2(t)^2 * siga2^2 + - siga1 * beta_SI * M * P2(t)^2 * siga2 + - siga1 * beta_SI * P2(t)^2 * Ks * siga2 + - siga1 * beta_SI * P2(t)^2 * siga2^2 + - siga1 * P3(t)^2 * beta_SA - siga1 * P3(t) * phi * M^2 * siga2 - - siga1 * P3(t) * phi * M * Mar * P5(t) * siga2 - - siga1 * P3(t) * phi * M * Ks * siga2 - - siga1 * P3(t) * phi * M * beta * siga2 - siga1 * P3(t) * phi * M * siga2^2 - - siga1 * P3(t) * phi * P2(t) * Mar * beta_SA + - siga1 * P3(t) * phi * P2(t) * siga2 * beta_SA + - siga1 * P3(t) * M^2 * siga2 + - 2 * siga1 * P3(t) * M * P2(t) * beta_SA + - siga1 * P3(t) * M * Mar * P5(t) * siga2 + - siga1 * P3(t) * M * Ks * siga2 + - siga1 * P3(t) * M * beta * siga2 + - siga1 * P3(t) * M * siga2^2 + - siga1 * P3(t) * P2(t) * Mar * beta_SA + - 2 * siga1 * P3(t) * P2(t) * Ks * beta_SA + - siga1 * P3(t) * P2(t) * siga2 * beta_SA - - siga1 * phi * M^2 * P2(t) * Mar * P5(t) * siga2 - - siga1 * phi * M^2 * P2(t) * Ks * siga2 - - siga1 * phi * M^2 * P2(t) * beta * siga2 - - siga1 * phi * M^2 * P2(t) * siga2^2 - siga1 * phi * M * P4(t) * siga2 - - siga1 * phi * M * P2(t)^2 * Mar * beta_SA + - siga1 * phi * M * P2(t)^2 * siga2 * beta_SA - - siga1 * phi * M * P2(t) * Mar * P5(t) * Ks * siga2 - - siga1 * phi * M * P2(t) * Mar * P5(t) * siga2^2 - - siga1 * phi * M * P2(t) * Ks * beta * siga2 - - siga1 * phi * M * P2(t) * Ks * siga2^2 - - siga1 * phi * M * P2(t) * beta * siga2^2 - - siga1 * phi * P2(t)^2 * Mar * Ks * beta_SA - - siga1 * phi * P2(t)^2 * Mar * siga2 * beta_SA + - siga1 * phi * P2(t)^2 * Ks * siga2 * beta_SA + - siga1 * phi * P2(t)^2 * siga2^2 * beta_SA + - siga1 * M^2 * P2(t)^2 * beta_SA + - siga1 * M^2 * P2(t) * Mar * P5(t) * siga2 + - siga1 * M^2 * P2(t) * Ks * siga2 + - siga1 * M^2 * P2(t) * beta * siga2 + - siga1 * M^2 * P2(t) * siga2^2 + - siga1 * M * P4(t) * siga2 + - siga1 * M * P2(t)^2 * Mar * beta_SA + - 2 * siga1 * M * P2(t)^2 * Ks * beta_SA + - siga1 * M * P2(t)^2 * siga2 * beta_SA + - siga1 * M * P2(t) * Mar * P5(t) * Ks * siga2 + - siga1 * M * P2(t) * Mar * P5(t) * siga2^2 + - siga1 * M * P2(t) * Ks * beta * siga2 + - siga1 * M * P2(t) * Ks * siga2^2 + - siga1 * M * P2(t) * beta * siga2^2 + - siga1 * P2(t)^2 * Mar * Ks * beta_SA + - siga1 * P2(t)^2 * Mar * siga2 * beta_SA + - siga1 * P2(t)^2 * Ks^2 * beta_SA + - siga1 * P2(t)^2 * Ks * siga2 * beta_SA - - P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + - P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2^2 - - P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + - P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2^2 * beta_SA + - P0(t) * P1(t) * M^2 * Mar * Ks^2 * siga2 * beta_SA - - P0(t) * beta_SI * P3(t) * phi * M * Mar * Ks * siga2 - - P0(t) * beta_SI * P3(t) * phi * Mar * Ks^2 * siga2 - - P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2^2 + - P0(t) * beta_SI * P3(t) * M * Mar * Ks * siga2 + - P0(t) * beta_SI * P3(t) * Mar * Ks^2 * siga2 + - P0(t) * beta_SI * P3(t) * Mar * Ks * siga2^2 - - P0(t) * beta_SI * phi * alpa * Mar * Ks * siga2 - - P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks^2 * siga2 - - P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2^2 - - P0(t) * beta_SI * phi * P4(t) * Mar * Ks * siga2 - - P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2^2 + - P0(t) * beta_SI * alpa * Mar * Ks * siga2 + - P0(t) * beta_SI * M * P2(t) * Mar * Ks^2 * siga2 + - P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2^2 + - P0(t) * beta_SI * P4(t) * Mar * Ks * siga2 + - P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2^2 - - P0(t) * P3(t) * phi * M^2 * Mar * Ks * beta_SA + - P0(t) * P3(t) * phi * M^2 * Ks * siga2 * beta_SA - - P0(t) * P3(t) * phi * M * Mar * Ks^2 * beta_SA - - P0(t) * P3(t) * phi * M * Mar * Ks * siga2 * beta_SA + - P0(t) * P3(t) * phi * M * Ks^2 * siga2 * beta_SA + - P0(t) * P3(t) * phi * M * Ks * siga2^2 * beta_SA + - P0(t) * P3(t) * M^2 * Mar * Ks * beta_SA + - P0(t) * P3(t) * M * Mar * Ks^2 * beta_SA + - P0(t) * P3(t) * M * Mar * Ks * siga2 * beta_SA - - P0(t) * phi * alpa * M * Mar * Ks * beta_SA + - P0(t) * phi * alpa * M * Ks * siga2 * beta_SA - - P0(t) * phi * M^2 * P2(t) * Mar * Ks^2 * beta_SA - - P0(t) * phi * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + - P0(t) * phi * M^2 * P2(t) * Ks^2 * siga2 * beta_SA + - P0(t) * phi * M^2 * P2(t) * Ks * siga2^2 * beta_SA - - P0(t) * phi * M * P4(t) * Mar * Ks * beta_SA + - P0(t) * phi * M * P4(t) * Ks * siga2 * beta_SA - - P0(t) * phi * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA + - P0(t) * phi * M * P2(t) * Ks^2 * siga2^2 * beta_SA + - P0(t) * alpa * M * Mar * Ks * beta_SA + - P0(t) * M^2 * P2(t) * Mar * Ks^2 * beta_SA + - P0(t) * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + - P0(t) * M * P4(t) * Mar * Ks * beta_SA + - P0(t) * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA - - P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2^2 - - P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2^2 + - P1(t)^2 * beta_SI * M * Mar * Ks * siga2^2 + - P1(t)^2 * beta_SI * M * Ks^2 * siga2^2 - - P1(t)^2 * phi * M^2 * Mar * Ks * siga2 * beta_SA + - P1(t)^2 * phi * M^2 * Ks * siga2^2 * beta_SA - - P1(t)^2 * phi * M * Mar * Ks^2 * siga2 * beta_SA + - P1(t)^2 * phi * M * Ks^2 * siga2^2 * beta_SA + - P1(t)^2 * M^2 * Mar * Ks * siga2 * beta_SA + - P1(t)^2 * M^2 * Ks^2 * siga2 * beta_SA + - P1(t)^2 * M * Mar * Ks^2 * siga2 * beta_SA - - P1(t) * beta_SI * P3(t) * phi * M * Mar * siga2 - - P1(t) * beta_SI * P3(t) * phi * M * Ks * siga2 - - P1(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 - - P1(t) * beta_SI * P3(t) * phi * Mar * siga2^2 - - P1(t) * beta_SI * P3(t) * phi * Ks^2 * siga2 - - P1(t) * beta_SI * P3(t) * phi * Ks * siga2^2 + - P1(t) * beta_SI * P3(t) * M * Mar * siga2 + - P1(t) * beta_SI * P3(t) * M * Ks * siga2 + - P1(t) * beta_SI * P3(t) * Mar * Ks * siga2 + - P1(t) * beta_SI * P3(t) * Mar * siga2^2 + - P1(t) * beta_SI * P3(t) * Ks^2 * siga2 + - P1(t) * beta_SI * P3(t) * Ks * siga2^2 - - P1(t) * beta_SI * phi * alpa * Mar * siga2 - - P1(t) * beta_SI * phi * alpa * Ks * siga2 - - P1(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - - P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2^2 - - P1(t) * beta_SI * phi * M * P2(t) * Ks^2 * siga2 - - 2 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - - P1(t) * beta_SI * phi * P4(t) * Mar * siga2 - - P1(t) * beta_SI * phi * P4(t) * Ks * siga2 - - P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 - - P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2^2 + - P1(t) * beta_SI * alpa * Mar * siga2 + - P1(t) * beta_SI * alpa * Ks * siga2 + - P1(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + - P1(t) * beta_SI * M * P2(t) * Mar * siga2^2 + - P1(t) * beta_SI * M * P2(t) * Ks^2 * siga2 + - 2 * P1(t) * beta_SI * M * P2(t) * Ks * siga2^2 + - P1(t) * beta_SI * P4(t) * Mar * siga2 + - P1(t) * beta_SI * P4(t) * Ks * siga2 + - P1(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 + - P1(t) * beta_SI * P2(t) * Ks^2 * siga2^2 - - P1(t) * P3(t) * phi * M^2 * Mar * beta_SA + - P1(t) * P3(t) * phi * M^2 * siga2 * beta_SA - - 2 * P1(t) * P3(t) * phi * M * Mar * Ks * beta_SA - - P1(t) * P3(t) * phi * M * Mar * siga2 * beta_SA + - 2 * P1(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + - P1(t) * P3(t) * phi * M * siga2^2 * beta_SA - - P1(t) * P3(t) * phi * Mar * Ks^2 * beta_SA - - P1(t) * P3(t) * phi * Mar * Ks * siga2 * beta_SA + - P1(t) * P3(t) * phi * Ks^2 * siga2 * beta_SA + - P1(t) * P3(t) * phi * Ks * siga2^2 * beta_SA + - P1(t) * P3(t) * M^2 * Mar * beta_SA + - P1(t) * P3(t) * M^2 * Ks * beta_SA + - 2 * P1(t) * P3(t) * M * Mar * Ks * beta_SA + - P1(t) * P3(t) * M * Mar * siga2 * beta_SA + - P1(t) * P3(t) * M * Ks^2 * beta_SA + - 2 * P1(t) * P3(t) * M * Ks * siga2 * beta_SA + - P1(t) * P3(t) * Mar * Ks^2 * beta_SA + - P1(t) * P3(t) * Mar * Ks * siga2 * beta_SA - - P1(t) * phi * alpa * M * Mar * beta_SA + - P1(t) * phi * alpa * M * siga2 * beta_SA - - P1(t) * phi * alpa * Mar * Ks * beta_SA + - P1(t) * phi * alpa * Ks * siga2 * beta_SA - - P1(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA - - P1(t) * phi * M^2 * P2(t) * Mar * siga2 * beta_SA + - P1(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA + - P1(t) * phi * M^2 * P2(t) * siga2^2 * beta_SA - - P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - - P1(t) * phi * M^2 * Ks * beta * siga2^2 - - P1(t) * phi * M * P4(t) * Mar * beta_SA + - P1(t) * phi * M * P4(t) * siga2 * beta_SA - - P1(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - - 3 * P1(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + - P1(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + - 3 * P1(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA - - P1(t) * phi * P4(t) * Mar * Ks * beta_SA + - P1(t) * phi * P4(t) * Ks * siga2 * beta_SA - - P1(t) * phi * P2(t) * Mar * Ks^2 * siga2 * beta_SA + - P1(t) * phi * P2(t) * Ks^2 * siga2^2 * beta_SA + - P1(t) * alpa * M * Mar * beta_SA + - P1(t) * alpa * M * Ks * beta_SA + - P1(t) * alpa * Mar * Ks * beta_SA + - P1(t) * M^2 * P2(t) * Mar * Ks * beta_SA + - P1(t) * M^2 * P2(t) * Mar * siga2 * beta_SA + - P1(t) * M^2 * P2(t) * Ks^2 * beta_SA + - 2 * P1(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + - P1(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + - P1(t) * M^2 * Ks * beta * siga2^2 + - P1(t) * M * P4(t) * Mar * beta_SA + - P1(t) * M * P4(t) * Ks * beta_SA + - P1(t) * M * P2(t) * Mar * Ks^2 * beta_SA + - 3 * P1(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + - 2 * P1(t) * M * P2(t) * Ks^2 * siga2 * beta_SA + - P1(t) * P4(t) * Mar * Ks * beta_SA + - P1(t) * P2(t) * Mar * Ks^2 * siga2 * beta_SA - - beta_SI * P3(t) * phi * M * P2(t) * siga2 - - beta_SI * P3(t) * phi * P2(t) * Ks * siga2 - - beta_SI * P3(t) * phi * P2(t) * siga2^2 + - beta_SI * P3(t) * M * P2(t) * siga2 + - beta_SI * P3(t) * P2(t) * Ks * siga2 + - beta_SI * P3(t) * P2(t) * siga2^2 - beta_SI * phi * alpa * P2(t) * siga2 - - beta_SI * phi * M * P2(t)^2 * Ks * siga2 - - beta_SI * phi * M * P2(t)^2 * siga2^2 - - beta_SI * phi * P4(t) * P2(t) * siga2 - - beta_SI * phi * P2(t)^2 * Ks * siga2^2 + - beta_SI * alpa * P2(t) * siga2 + - beta_SI * M * P2(t)^2 * Ks * siga2 + - beta_SI * M * P2(t)^2 * siga2^2 + - beta_SI * P4(t) * P2(t) * siga2 + - beta_SI * P2(t)^2 * Ks * siga2^2 + - P3(t)^2 * M * beta_SA + - P3(t)^2 * Ks * beta_SA + - P3(t)^2 * siga2 * beta_SA - P3(t) * phi * M^2 * Mar * P5(t) * siga2 - - P3(t) * phi * M^2 * Ks * siga2 - P3(t) * phi * M^2 * beta * siga2 - - P3(t) * phi * M^2 * siga2^2 - P3(t) * phi * M * P2(t) * Mar * beta_SA + - P3(t) * phi * M * P2(t) * siga2 * beta_SA - - P3(t) * phi * M * Mar * P5(t) * Ks * siga2 - - P3(t) * phi * M * Mar * P5(t) * siga2^2 - - P3(t) * phi * M * Ks * beta * siga2 - P3(t) * phi * M * Ks * siga2^2 - - P3(t) * phi * M * beta * siga2^2 - - P3(t) * phi * P2(t) * Mar * Ks * beta_SA - - P3(t) * phi * P2(t) * Mar * siga2 * beta_SA + - P3(t) * phi * P2(t) * Ks * siga2 * beta_SA + - P3(t) * phi * P2(t) * siga2^2 * beta_SA + - P3(t) * alpa * beta_SA + - P3(t) * M^2 * P2(t) * beta_SA + - P3(t) * M^2 * Mar * P5(t) * siga2 + - P3(t) * M^2 * Ks * siga2 + - P3(t) * M^2 * beta * siga2 + - P3(t) * M^2 * siga2^2 + - P3(t) * M * P2(t) * Mar * beta_SA + - 3 * P3(t) * M * P2(t) * Ks * beta_SA + - 2 * P3(t) * M * P2(t) * siga2 * beta_SA + - P3(t) * M * Mar * P5(t) * Ks * siga2 + - P3(t) * M * Mar * P5(t) * siga2^2 + - P3(t) * M * Ks * beta * siga2 + - P3(t) * M * Ks * siga2^2 + - P3(t) * M * beta * siga2^2 + - P3(t) * P4(t) * beta_SA + - P3(t) * P2(t) * Mar * Ks * beta_SA + - P3(t) * P2(t) * Mar * siga2 * beta_SA + - P3(t) * P2(t) * Ks^2 * beta_SA + - 2 * P3(t) * P2(t) * Ks * siga2 * beta_SA - - phi * alpa * M * Mar * P5(t) * siga2 - phi * alpa * M * beta * siga2 - - phi * alpa * P2(t) * Mar * beta_SA + phi * alpa * P2(t) * siga2 * beta_SA - - phi * M^2 * P4(t) * siga2 - phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - - phi * M^2 * P2(t) * Mar * P5(t) * siga2^2 - - phi * M^2 * P2(t) * Ks * beta * siga2 - phi * M^2 * P2(t) * Ks * siga2^2 - - phi * M^2 * P2(t) * beta * siga2^2 - phi * M * P4(t) * Mar * P5(t) * siga2 - - phi * M * P4(t) * Ks * siga2 - phi * M * P4(t) * beta * siga2 - - phi * M * P4(t) * siga2^2 - phi * M * P2(t)^2 * Mar * Ks * beta_SA - - phi * M * P2(t)^2 * Mar * siga2 * beta_SA + - phi * M * P2(t)^2 * Ks * siga2 * beta_SA + - phi * M * P2(t)^2 * siga2^2 * beta_SA - - phi * M * P2(t) * Mar * P5(t) * Ks * siga2^2 - - phi * M * P2(t) * Ks * beta * siga2^2 - - phi * P4(t) * P2(t) * Mar * beta_SA + - phi * P4(t) * P2(t) * siga2 * beta_SA - - phi * P2(t)^2 * Mar * Ks * siga2 * beta_SA + - phi * P2(t)^2 * Ks * siga2^2 * beta_SA + - alpa * M * P2(t) * beta_SA + - alpa * M * Mar * P5(t) * siga2 + - alpa * M * beta * siga2 + - alpa * P2(t) * Mar * beta_SA + - alpa * P2(t) * Ks * beta_SA + - M^2 * P4(t) * siga2 + - M^2 * P2(t)^2 * Ks * beta_SA + - M^2 * P2(t)^2 * siga2 * beta_SA + - M^2 * P2(t) * Mar * P5(t) * Ks * siga2 + - M^2 * P2(t) * Mar * P5(t) * siga2^2 + - M^2 * P2(t) * Ks * beta * siga2 + - M^2 * P2(t) * Ks * siga2^2 + - M^2 * P2(t) * beta * siga2^2 + - M * P4(t) * P2(t) * beta_SA + - M * P4(t) * Mar * P5(t) * siga2 + - M * P4(t) * Ks * siga2 + - M * P4(t) * beta * siga2 + - M * P4(t) * siga2^2 + - M * P2(t)^2 * Mar * Ks * beta_SA + - M * P2(t)^2 * Mar * siga2 * beta_SA + - M * P2(t)^2 * Ks^2 * beta_SA + - 2 * M * P2(t)^2 * Ks * siga2 * beta_SA + - M * P2(t) * Mar * P5(t) * Ks * siga2^2 + - M * P2(t) * Ks * beta * siga2^2 + - P4(t) * P2(t) * Mar * beta_SA + - P4(t) * P2(t) * Ks * beta_SA + - P2(t)^2 * Mar * Ks * siga2 * beta_SA + - P2(t)^2 * Ks^2 * siga2 * beta_SA - ) // (phi * M * siga2 - M * siga2), - P1'(t) = P2(t), - P2'(t) = P3(t), - y(t) = P0(t) -) -ident_funcs = [ - Mar, - Ks, - alpa, - (siga1 + phi * Mar - Mar) // phi, - (siga1 * phi - siga1 - phi * Mar + Mar) // (siga1 * phi * beta_SA), - ( - siga1 * beta_SA + beta_SI * phi * siga2 + phi * Mar * beta_SA - - phi * siga2 * beta_SA - Mar * beta_SA - ) // (phi * M * siga2), - ( - siga1 * phi * M * siga2 - siga1 * M * siga2 - phi * M * Mar * siga2 + - M * Mar * siga2 - ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), - ( - siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 - - siga1 * phi * siga2 * beta_SA - siga1 * M * beta_SA - - beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - phi * M * Mar * beta_SA + - M * Mar * beta_SA - ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), - ( - siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + - siga1 * phi * Mar * beta_SA - siga1 * phi * siga2 * beta_SA - - siga1 * Mar * beta_SA + - siga1 * siga2 * beta_SA + - beta_SI * phi * M * siga2 - beta_SI * phi * Mar * siga2 + - beta_SI * phi * siga2^2 + - beta_SI * Mar * siga2 + - phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - Mar * siga2 * beta_SA - ) // (phi * M * siga2), -] -# Really large and takes a lot of time, so commented -# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -### -# Cases with states - -ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t), y(t) = x(t)) -T = typeof(x) -ident_funcs = [x] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) - -ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) -ident_funcs = [a, x] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) -) -ident_funcs = [x1, c, d, a, b * x2] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) -) -ident_funcs = [x1, c, d, a, b * x2] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -# Diagonal with simple spectrum and observable states -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = λ1 * x1(t) + β1 * u1(t), - x2'(t) = λ2 * x2(t) + β2 * u2(t), - x3'(t) = λ3 * x3(t) + β3 * u3(t), - y(t) = x1(t) + x2(t) + x3(t) -) -ident_funcs = [λ1, λ2, λ3, β1, β2, β3, x1, x2, x3] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + Θ * x2(t), - x2'(t) = 0, - y(t) = x1(t) -) -ident_funcs = [x1, x2 * Θ] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * x2(t), - x2'(t) = x3(t), - x3'(t) = C, - y(t) = x1(t) -) -ident_funcs = [x1, α * x2, α * x3, α * C] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * (x1 - x2), - x2'(t) = α * (x1 + x2), - y(t) = (x1^2 + x2^2) // 2, -) -ident_funcs = [α, x1^2 + x2^2] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) -ident_funcs = [b * c, a, x // b] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -# llw1987 model -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -p1 * x1(t) + p2 * u(t), - x2'(t) = -p3 * x2(t) + p4 * u(t), - x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), - y1(t) = x3(t) -) -ident_funcs = [ - x3, - x2 * x1 // one(x1), - p3 * p1 // one(x1), - p2 * p4 // one(x1), - (p3 + p1) // one(x1), - (p2 * x2 + p4 * x1) // one(x1), - (p3 - p1) // (p2 * x2 - p4 * x1), -] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -# Regression test: Previously failed for with_states=true because of the bug in -# `linear_relations_between_normal_forms` -# Fujita -ode = StructuralIdentifiability.@ODEmodel( - EGFR'(t) = - EGFR_turnover * pro_EGFR(t) + EGF_EGFR(t) * reaction_1_k2 - - EGFR(t) * EGFR_turnover - EGF_EGFR(t) * reaction_1_k1, - pEGFR'(t) = - EGF_EGFR(t) * reaction_9_k1 - pEGFR(t) * reaction_4_k1 + - pEGFR_Akt(t) * reaction_2_k2 + - pEGFR_Akt(t) * reaction_3_k1 - Akt(t) * pEGFR(t) * reaction_2_k1, - pEGFR_Akt'(t) = - Akt(t) * pEGFR(t) * reaction_2_k1 - pEGFR_Akt(t) * reaction_3_k1 - - pEGFR_Akt(t) * reaction_2_k2, - Akt'(t) = - pAkt(t) * reaction_7_k1 + pEGFR_Akt(t) * reaction_2_k2 - - Akt(t) * pEGFR(t) * reaction_2_k1, - pAkt'(t) = - pAkt_S6(t) * reaction_5_k2 - pAkt(t) * reaction_7_k1 + - pAkt_S6(t) * reaction_6_k1 + - pEGFR_Akt(t) * reaction_3_k1 - S6(t) * pAkt(t) * reaction_5_k1, - S6'(t) = - pAkt_S6(t) * reaction_5_k2 + pS6(t) * reaction_8_k1 - - S6(t) * pAkt(t) * reaction_5_k1, - pAkt_S6'(t) = - S6(t) * pAkt(t) * reaction_5_k1 - pAkt_S6(t) * reaction_6_k1 - - pAkt_S6(t) * reaction_5_k2, - pS6'(t) = pAkt_S6(t) * reaction_6_k1 - pS6(t) * reaction_8_k1, - EGF_EGFR'(t) = - EGF_EGFR(t) * reaction_1_k1 - EGF_EGFR(t) * reaction_9_k1 - - EGF_EGFR(t) * reaction_1_k2, - y1(t) = a1 * (pEGFR(t) + pEGFR_Akt(t)), - y2(t) = a2 * (pAkt(t) + pAkt_S6(t)), - y3(t) = a3 * pS6(t) -) -ident_funcs = [ - reaction_8_k1, - reaction_2_k2, - reaction_5_k2, - reaction_3_k1, - reaction_6_k1, - reaction_7_k1, - reaction_4_k1, - reaction_2_k1 * pAkt_S6, - reaction_2_k1 * S6, - reaction_5_k1 * pAkt_S6, - pEGFR_Akt * reaction_2_k1, - Akt * reaction_2_k1, - a1 * pAkt_S6, - pEGFR * reaction_2_k1, - pAkt * reaction_2_k1, - a3 * pAkt_S6, - pS6 * reaction_2_k1, - a2 * pAkt_S6, - reaction_9_k1 * reaction_2_k1 * EGF_EGFR, - reaction_1_k1 - reaction_9_k1 - reaction_1_k2, -] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -# Bruno2016 model -ode = StructuralIdentifiability.@ODEmodel( - beta'(t) = -kbeta * beta(t), - cry'(t) = -cry(t) * kcrybeta - cry(t) * kcryOH, - zea'(t) = -zea(t) * kzea, - beta10'(t) = cry(t) * kcryOH - beta10(t) * kbeta10 + kbeta * beta(t), - OHbeta10'(t) = cry(t) * kcrybeta + zea(t) * kzea - OHbeta10(t) * kOHbeta10, - betaio'(t) = cry(t) * kcrybeta + beta10(t) * kbeta10 + kbeta * beta(t), - OHbetaio'(t) = cry(t) * kcryOH + zea(t) * kzea + OHbeta10(t) * kOHbeta10, - y1(t) = beta(t), - y2(t) = beta10(t) -) -ident_funcs = [beta10, beta, kbeta, kbeta10, cry * kcryOH, kcrybeta + kcryOH] -push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - -# STAT-5 model from -# MODELING THE NONLINEAR DYNAMICS OF CELLULAR SIGNAL TRANSDUCTION -# DOI: https://doi.org/10.1142/S0218127404010461 -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -k1 * x1 * EpoR_A, - x2'(t) = k1 * x1 * EpoR_A - k2 * x2^2, - x3'(t) = -k3 * x3 + 0.5 * k2 * x2^2, - x4'(t) = k3 * x3, - y1(t) = k5 * (x2 + 2x3), - y2(t) = k6 * (x1 + x2 + 2x3), - y3(t) = k7 * EpoR_A -) -ident_funcs = [k3, k1 // k7, k5 // k2, k6 // k2, k7 * EpoR_A] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - -ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) -ident_funcs = [x1 + x2] -push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) - -# TODO: verify that Maple returns the same -@testset "Identifiable functions of parameters" begin - p = 0.99 - for case in test_cases - for simplify in [:weak, :standard] # :strong] - ode = case.ode - true_ident_funcs = case.ident_funcs - with_states = false - if haskey(case, :with_states) - with_states = case.with_states + siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + siga1 * phi * Mar * beta_SA - + siga1 * phi * siga2 * beta_SA - siga1 * Mar * beta_SA + + siga1 * siga2 * beta_SA + + beta_SI * phi * M * siga2 - beta_SI * phi * Mar * siga2 + + beta_SI * phi * siga2^2 + + beta_SI * Mar * siga2 + + phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - + Mar * siga2 * beta_SA + ) // (phi * M * siga2), + ] + # Really large and takes a lot of time, so commented + # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + ### + # Cases with states + + ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t), y(t) = x(t)) + T = typeof(x) + ident_funcs = [x] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) + + ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) + ident_funcs = [a, x] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) + ) + ident_funcs = [x1, c, d, a, b * x2] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) + ) + ident_funcs = [x1, c, d, a, b * x2] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + # Diagonal with simple spectrum and observable states + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = λ1 * x1(t) + β1 * u1(t), + x2'(t) = λ2 * x2(t) + β2 * u2(t), + x3'(t) = λ3 * x3(t) + β3 * u3(t), + y(t) = x1(t) + x2(t) + x3(t) + ) + ident_funcs = [λ1, λ2, λ3, β1, β2, β3, x1, x2, x3] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + Θ * x2(t), + x2'(t) = 0, + y(t) = x1(t) + ) + ident_funcs = [x1, x2 * Θ] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * x2(t), + x2'(t) = x3(t), + x3'(t) = C, + y(t) = x1(t) + ) + ident_funcs = [x1, α * x2, α * x3, α * C] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * (x1 - x2), + x2'(t) = α * (x1 + x2), + y(t) = (x1^2 + x2^2) // 2, + ) + ident_funcs = [α, x1^2 + x2^2] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) + ident_funcs = [b * c, a, x // b] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + # llw1987 model + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -p1 * x1(t) + p2 * u(t), + x2'(t) = -p3 * x2(t) + p4 * u(t), + x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), + y1(t) = x3(t) + ) + ident_funcs = [ + x3, + x2 * x1 // one(x1), + p3 * p1 // one(x1), + p2 * p4 // one(x1), + (p3 + p1) // one(x1), + (p2 * x2 + p4 * x1) // one(x1), + (p3 - p1) // (p2 * x2 - p4 * x1), + ] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + # Regression test: Previously failed for with_states=true because of the bug in + # `linear_relations_between_normal_forms` + # Fujita + ode = StructuralIdentifiability.@ODEmodel( + EGFR'(t) = + EGFR_turnover * pro_EGFR(t) + EGF_EGFR(t) * reaction_1_k2 - + EGFR(t) * EGFR_turnover - EGF_EGFR(t) * reaction_1_k1, + pEGFR'(t) = + EGF_EGFR(t) * reaction_9_k1 - pEGFR(t) * reaction_4_k1 + + pEGFR_Akt(t) * reaction_2_k2 + + pEGFR_Akt(t) * reaction_3_k1 - Akt(t) * pEGFR(t) * reaction_2_k1, + pEGFR_Akt'(t) = + Akt(t) * pEGFR(t) * reaction_2_k1 - pEGFR_Akt(t) * reaction_3_k1 - + pEGFR_Akt(t) * reaction_2_k2, + Akt'(t) = + pAkt(t) * reaction_7_k1 + pEGFR_Akt(t) * reaction_2_k2 - + Akt(t) * pEGFR(t) * reaction_2_k1, + pAkt'(t) = + pAkt_S6(t) * reaction_5_k2 - pAkt(t) * reaction_7_k1 + + pAkt_S6(t) * reaction_6_k1 + + pEGFR_Akt(t) * reaction_3_k1 - S6(t) * pAkt(t) * reaction_5_k1, + S6'(t) = + pAkt_S6(t) * reaction_5_k2 + pS6(t) * reaction_8_k1 - + S6(t) * pAkt(t) * reaction_5_k1, + pAkt_S6'(t) = + S6(t) * pAkt(t) * reaction_5_k1 - pAkt_S6(t) * reaction_6_k1 - + pAkt_S6(t) * reaction_5_k2, + pS6'(t) = pAkt_S6(t) * reaction_6_k1 - pS6(t) * reaction_8_k1, + EGF_EGFR'(t) = + EGF_EGFR(t) * reaction_1_k1 - EGF_EGFR(t) * reaction_9_k1 - + EGF_EGFR(t) * reaction_1_k2, + y1(t) = a1 * (pEGFR(t) + pEGFR_Akt(t)), + y2(t) = a2 * (pAkt(t) + pAkt_S6(t)), + y3(t) = a3 * pS6(t) + ) + ident_funcs = [ + reaction_8_k1, + reaction_2_k2, + reaction_5_k2, + reaction_3_k1, + reaction_6_k1, + reaction_7_k1, + reaction_4_k1, + reaction_2_k1 * pAkt_S6, + reaction_2_k1 * S6, + reaction_5_k1 * pAkt_S6, + pEGFR_Akt * reaction_2_k1, + Akt * reaction_2_k1, + a1 * pAkt_S6, + pEGFR * reaction_2_k1, + pAkt * reaction_2_k1, + a3 * pAkt_S6, + pS6 * reaction_2_k1, + a2 * pAkt_S6, + reaction_9_k1 * reaction_2_k1 * EGF_EGFR, + reaction_1_k1 - reaction_9_k1 - reaction_1_k2, + ] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + # Bruno2016 model + ode = StructuralIdentifiability.@ODEmodel( + beta'(t) = -kbeta * beta(t), + cry'(t) = -cry(t) * kcrybeta - cry(t) * kcryOH, + zea'(t) = -zea(t) * kzea, + beta10'(t) = cry(t) * kcryOH - beta10(t) * kbeta10 + kbeta * beta(t), + OHbeta10'(t) = cry(t) * kcrybeta + zea(t) * kzea - OHbeta10(t) * kOHbeta10, + betaio'(t) = cry(t) * kcrybeta + beta10(t) * kbeta10 + kbeta * beta(t), + OHbetaio'(t) = cry(t) * kcryOH + zea(t) * kzea + OHbeta10(t) * kOHbeta10, + y1(t) = beta(t), + y2(t) = beta10(t) + ) + ident_funcs = [beta10, beta, kbeta, kbeta10, cry * kcryOH, kcrybeta + kcryOH] + push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + + # STAT-5 model from + # MODELING THE NONLINEAR DYNAMICS OF CELLULAR SIGNAL TRANSDUCTION + # DOI: https://doi.org/10.1142/S0218127404010461 + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -k1 * x1 * EpoR_A, + x2'(t) = k1 * x1 * EpoR_A - k2 * x2^2, + x3'(t) = -k3 * x3 + 0.5 * k2 * x2^2, + x4'(t) = k3 * x3, + y1(t) = k5 * (x2 + 2x3), + y2(t) = k6 * (x1 + x2 + 2x3), + y3(t) = k7 * EpoR_A + ) + ident_funcs = [k3, k1 // k7, k5 // k2, k6 // k2, k7 * EpoR_A] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + + ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) + ident_funcs = [x1 + x2] + push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) + + # TODO: verify that Maple returns the same + @testset "Identifiable functions of parameters" begin + p = 0.99 + for case in test_cases + for simplify in [:weak, :standard] # :strong] + ode = case.ode + true_ident_funcs = case.ident_funcs + with_states = false + if haskey(case, :with_states) + with_states = case.with_states + end + result_funcs = StructuralIdentifiability.find_identifiable_functions( + ode, + simplify = simplify, + with_states = with_states, + ) + + if isempty(true_ident_funcs) + @test isempty(result_funcs) + continue + end + + @test parent(numerator(result_funcs[1])) == parent(ode) + + R = parent(numerator(result_funcs[1])) + + @info "Test, result_funcs = \n$result_funcs" case simplify R with_states + + true_ident_funcs = map(f -> f // one(f), true_ident_funcs) + true_ident_funcs = map( + f -> StructuralIdentifiability.parent_ring_change(f, R), + true_ident_funcs, + ) + + # Check inclusion in + @test StructuralIdentifiability.fields_equal( + StructuralIdentifiability.RationalFunctionField(result_funcs), + StructuralIdentifiability.RationalFunctionField(true_ident_funcs), + p, + ) end - result_funcs = StructuralIdentifiability.find_identifiable_functions( - ode, - simplify = simplify, - with_states = with_states, - ) - - if isempty(true_ident_funcs) - @test isempty(result_funcs) - continue - end - - @test parent(numerator(result_funcs[1])) == parent(ode) - - R = parent(numerator(result_funcs[1])) - - @info "Test, result_funcs = \n$result_funcs" case simplify R with_states - - true_ident_funcs = map(f -> f // one(f), true_ident_funcs) - true_ident_funcs = map( - f -> StructuralIdentifiability.parent_ring_change(f, R), - true_ident_funcs, - ) - - # Check inclusion in - @test StructuralIdentifiability.fields_equal( - StructuralIdentifiability.RationalFunctionField(result_funcs), - StructuralIdentifiability.RationalFunctionField(true_ident_funcs), - p, - ) end end end diff --git a/test/io_cases.jl b/test/io_cases.jl index 3242d75c1..00dc85366 100644 --- a/test/io_cases.jl +++ b/test/io_cases.jl @@ -1,151 +1,155 @@ -@testset "Checking io-equations: single output" begin - test_cases = [] +if GROUP == "All" || GROUP == "Core" + @testset "Checking io-equations: single output" begin + test_cases = [] - # 2-compartiment model - R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = PolynomialRing( - QQ, - ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], - ) - correct = y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t) + u(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) + # 2-compartiment model + R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = PolynomialRing( + QQ, + ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], + ) + correct = + y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t) + u(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ), + :correct => correct, ), - :correct => correct, - ), - ) + ) - #--------------------------------------- - # Chen-Lee model - R, (y1_0, y1_1, y1_2, y1_3, a, b, c) = - PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) - correct = - 9 * y1_3^2 * y1_0^2 - 18 * y1_3 * y1_2 * y1_1 * y1_0 - - 18 * y1_3 * y1_2 * y1_0^2 * a - 36 * y1_3 * y1_2 * y1_0^2 * b - - 36 * y1_3 * y1_2 * y1_0^2 * c + - 18 * y1_3 * y1_1^2 * y1_0 * a + - 18 * y1_3 * y1_1^2 * y1_0 * b + - 18 * y1_3 * y1_1^2 * y1_0 * c - 24 * y1_3 * y1_1 * y1_0^4 + - 18 * y1_3 * y1_1 * y1_0^2 * a * b + - 18 * y1_3 * y1_1 * y1_0^2 * a * c + - 18 * y1_3 * y1_1 * y1_0^2 * b^2 + - 36 * y1_3 * y1_1 * y1_0^2 * b * c + - 18 * y1_3 * y1_1 * y1_0^2 * c^2 + - 24 * y1_3 * y1_0^5 * a - 18 * y1_3 * y1_0^3 * a * b^2 - - 36 * y1_3 * y1_0^3 * a * b * c - 18 * y1_3 * y1_0^3 * a * c^2 + - 9 * y1_2^2 * y1_1^2 + - 18 * y1_2^2 * y1_1 * y1_0 * a + - 36 * y1_2^2 * y1_1 * y1_0 * b + - 36 * y1_2^2 * y1_1 * y1_0 * c + - 9 * y1_2^2 * y1_0^2 * a^2 + - 36 * y1_2^2 * y1_0^2 * a * b + - 36 * y1_2^2 * y1_0^2 * a * c + - 27 * y1_2^2 * y1_0^2 * b^2 + - 90 * y1_2^2 * y1_0^2 * b * c + - 27 * y1_2^2 * y1_0^2 * c^2 - 18 * y1_2 * y1_1^3 * a - 18 * y1_2 * y1_1^3 * b - - 18 * y1_2 * y1_1^3 * c + 24 * y1_2 * y1_1^2 * y1_0^3 - - 18 * y1_2 * y1_1^2 * y1_0 * a^2 - 72 * y1_2 * y1_1^2 * y1_0 * a * b - - 72 * y1_2 * y1_1^2 * y1_0 * a * c - 54 * y1_2 * y1_1^2 * y1_0 * b^2 - - 108 * y1_2 * y1_1^2 * y1_0 * b * c - 54 * y1_2 * y1_1^2 * y1_0 * c^2 + - 48 * y1_2 * y1_1 * y1_0^4 * b + - 48 * y1_2 * y1_1 * y1_0^4 * c - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * b - - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * c - 18 * y1_2 * y1_1 * y1_0^2 * a * b^2 - - 108 * y1_2 * y1_1 * y1_0^2 * a * b * c - 18 * y1_2 * y1_1 * y1_0^2 * a * c^2 - - 18 * y1_2 * y1_1 * y1_0^2 * b^3 - 126 * y1_2 * y1_1 * y1_0^2 * b^2 * c - - 126 * y1_2 * y1_1 * y1_0^2 * b * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * c^3 - - 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - 48 * y1_2 * y1_0^5 * a * c + - 18 * y1_2 * y1_0^3 * a^2 * b^2 + - 36 * y1_2 * y1_0^3 * a^2 * b * c + - 18 * y1_2 * y1_0^3 * a^2 * c^2 + - 18 * y1_2 * y1_0^3 * a * b^3 + - 126 * y1_2 * y1_0^3 * a * b^2 * c + - 126 * y1_2 * y1_0^3 * a * b * c^2 + - 18 * y1_2 * y1_0^3 * a * c^3 + - 9 * y1_1^4 * a^2 + - 18 * y1_1^4 * a * b + - 18 * y1_1^4 * a * c + - 9 * y1_1^4 * b^2 + - 18 * y1_1^4 * b * c + - 9 * y1_1^4 * c^2 - 24 * y1_1^3 * y1_0^3 * a - 24 * y1_1^3 * y1_0^3 * b - - 24 * y1_1^3 * y1_0^3 * c + - 18 * y1_1^3 * y1_0 * a^2 * b + - 18 * y1_1^3 * y1_0 * a^2 * c + - 36 * y1_1^3 * y1_0 * a * b^2 + - 72 * y1_1^3 * y1_0 * a * b * c + - 36 * y1_1^3 * y1_0 * a * c^2 + - 18 * y1_1^3 * y1_0 * b^3 + - 54 * y1_1^3 * y1_0 * b^2 * c + - 54 * y1_1^3 * y1_0 * b * c^2 + - 18 * y1_1^3 * y1_0 * c^3 + - 16 * y1_1^2 * y1_0^6 + - 24 * y1_1^2 * y1_0^4 * a^2 - 12 * y1_1^2 * y1_0^4 * b^2 - - 72 * y1_1^2 * y1_0^4 * b * c - 12 * y1_1^2 * y1_0^4 * c^2 - - 18 * y1_1^2 * y1_0^2 * a^2 * b^2 - 18 * y1_1^2 * y1_0^2 * a^2 * c^2 - - 18 * y1_1^2 * y1_0^2 * a * b^3 + - 18 * y1_1^2 * y1_0^2 * a * b^2 * c + - 18 * y1_1^2 * y1_0^2 * a * b * c^2 - 18 * y1_1^2 * y1_0^2 * a * c^3 + - 36 * y1_1^2 * y1_0^2 * b^3 * c + - 72 * y1_1^2 * y1_0^2 * b^2 * c^2 + - 36 * y1_1^2 * y1_0^2 * b * c^3 - 32 * y1_1 * y1_0^7 * a + - 24 * y1_1 * y1_0^5 * a^2 * b + - 24 * y1_1 * y1_0^5 * a^2 * c + - 24 * y1_1 * y1_0^5 * a * b^2 + - 144 * y1_1 * y1_0^5 * a * b * c + - 24 * y1_1 * y1_0^5 * a * c^2 - 72 * y1_1 * y1_0^3 * a^2 * b^2 * c - - 72 * y1_1 * y1_0^3 * a^2 * b * c^2 - 72 * y1_1 * y1_0^3 * a * b^3 * c - - 144 * y1_1 * y1_0^3 * a * b^2 * c^2 - 72 * y1_1 * y1_0^3 * a * b * c^3 + - 16 * y1_0^8 * a^2 - 12 * y1_0^6 * a^2 * b^2 - 72 * y1_0^6 * a^2 * b * c - - 12 * y1_0^6 * a^2 * c^2 + - 36 * y1_0^4 * a^2 * b^3 * c + - 72 * y1_0^4 * a^2 * b^2 * c^2 + - 36 * y1_0^4 * a^2 * b * c^3 + #--------------------------------------- + # Chen-Lee model + R, (y1_0, y1_1, y1_2, y1_3, a, b, c) = + PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) + correct = + 9 * y1_3^2 * y1_0^2 - 18 * y1_3 * y1_2 * y1_1 * y1_0 - + 18 * y1_3 * y1_2 * y1_0^2 * a - 36 * y1_3 * y1_2 * y1_0^2 * b - + 36 * y1_3 * y1_2 * y1_0^2 * c + + 18 * y1_3 * y1_1^2 * y1_0 * a + + 18 * y1_3 * y1_1^2 * y1_0 * b + + 18 * y1_3 * y1_1^2 * y1_0 * c - 24 * y1_3 * y1_1 * y1_0^4 + + 18 * y1_3 * y1_1 * y1_0^2 * a * b + + 18 * y1_3 * y1_1 * y1_0^2 * a * c + + 18 * y1_3 * y1_1 * y1_0^2 * b^2 + + 36 * y1_3 * y1_1 * y1_0^2 * b * c + + 18 * y1_3 * y1_1 * y1_0^2 * c^2 + + 24 * y1_3 * y1_0^5 * a - 18 * y1_3 * y1_0^3 * a * b^2 - + 36 * y1_3 * y1_0^3 * a * b * c - 18 * y1_3 * y1_0^3 * a * c^2 + + 9 * y1_2^2 * y1_1^2 + + 18 * y1_2^2 * y1_1 * y1_0 * a + + 36 * y1_2^2 * y1_1 * y1_0 * b + + 36 * y1_2^2 * y1_1 * y1_0 * c + + 9 * y1_2^2 * y1_0^2 * a^2 + + 36 * y1_2^2 * y1_0^2 * a * b + + 36 * y1_2^2 * y1_0^2 * a * c + + 27 * y1_2^2 * y1_0^2 * b^2 + + 90 * y1_2^2 * y1_0^2 * b * c + + 27 * y1_2^2 * y1_0^2 * c^2 - 18 * y1_2 * y1_1^3 * a - 18 * y1_2 * y1_1^3 * b - + 18 * y1_2 * y1_1^3 * c + 24 * y1_2 * y1_1^2 * y1_0^3 - + 18 * y1_2 * y1_1^2 * y1_0 * a^2 - 72 * y1_2 * y1_1^2 * y1_0 * a * b - + 72 * y1_2 * y1_1^2 * y1_0 * a * c - 54 * y1_2 * y1_1^2 * y1_0 * b^2 - + 108 * y1_2 * y1_1^2 * y1_0 * b * c - 54 * y1_2 * y1_1^2 * y1_0 * c^2 + + 48 * y1_2 * y1_1 * y1_0^4 * b + + 48 * y1_2 * y1_1 * y1_0^4 * c - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * b - + 18 * y1_2 * y1_1 * y1_0^2 * a^2 * c - 18 * y1_2 * y1_1 * y1_0^2 * a * b^2 - + 108 * y1_2 * y1_1 * y1_0^2 * a * b * c - 18 * y1_2 * y1_1 * y1_0^2 * a * c^2 - + 18 * y1_2 * y1_1 * y1_0^2 * b^3 - 126 * y1_2 * y1_1 * y1_0^2 * b^2 * c - + 126 * y1_2 * y1_1 * y1_0^2 * b * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * c^3 - + 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - + 48 * y1_2 * y1_0^5 * a * c + + 18 * y1_2 * y1_0^3 * a^2 * b^2 + + 36 * y1_2 * y1_0^3 * a^2 * b * c + + 18 * y1_2 * y1_0^3 * a^2 * c^2 + + 18 * y1_2 * y1_0^3 * a * b^3 + + 126 * y1_2 * y1_0^3 * a * b^2 * c + + 126 * y1_2 * y1_0^3 * a * b * c^2 + + 18 * y1_2 * y1_0^3 * a * c^3 + + 9 * y1_1^4 * a^2 + + 18 * y1_1^4 * a * b + + 18 * y1_1^4 * a * c + + 9 * y1_1^4 * b^2 + + 18 * y1_1^4 * b * c + + 9 * y1_1^4 * c^2 - 24 * y1_1^3 * y1_0^3 * a - 24 * y1_1^3 * y1_0^3 * b - + 24 * y1_1^3 * y1_0^3 * c + + 18 * y1_1^3 * y1_0 * a^2 * b + + 18 * y1_1^3 * y1_0 * a^2 * c + + 36 * y1_1^3 * y1_0 * a * b^2 + + 72 * y1_1^3 * y1_0 * a * b * c + + 36 * y1_1^3 * y1_0 * a * c^2 + + 18 * y1_1^3 * y1_0 * b^3 + + 54 * y1_1^3 * y1_0 * b^2 * c + + 54 * y1_1^3 * y1_0 * b * c^2 + + 18 * y1_1^3 * y1_0 * c^3 + + 16 * y1_1^2 * y1_0^6 + + 24 * y1_1^2 * y1_0^4 * a^2 - 12 * y1_1^2 * y1_0^4 * b^2 - + 72 * y1_1^2 * y1_0^4 * b * c - 12 * y1_1^2 * y1_0^4 * c^2 - + 18 * y1_1^2 * y1_0^2 * a^2 * b^2 - 18 * y1_1^2 * y1_0^2 * a^2 * c^2 - + 18 * y1_1^2 * y1_0^2 * a * b^3 + + 18 * y1_1^2 * y1_0^2 * a * b^2 * c + + 18 * y1_1^2 * y1_0^2 * a * b * c^2 - 18 * y1_1^2 * y1_0^2 * a * c^3 + + 36 * y1_1^2 * y1_0^2 * b^3 * c + + 72 * y1_1^2 * y1_0^2 * b^2 * c^2 + + 36 * y1_1^2 * y1_0^2 * b * c^3 - 32 * y1_1 * y1_0^7 * a + + 24 * y1_1 * y1_0^5 * a^2 * b + + 24 * y1_1 * y1_0^5 * a^2 * c + + 24 * y1_1 * y1_0^5 * a * b^2 + + 144 * y1_1 * y1_0^5 * a * b * c + + 24 * y1_1 * y1_0^5 * a * c^2 - 72 * y1_1 * y1_0^3 * a^2 * b^2 * c - + 72 * y1_1 * y1_0^3 * a^2 * b * c^2 - 72 * y1_1 * y1_0^3 * a * b^3 * c - + 144 * y1_1 * y1_0^3 * a * b^2 * c^2 - 72 * y1_1 * y1_0^3 * a * b * c^3 + + 16 * y1_0^8 * a^2 - 12 * y1_0^6 * a^2 * b^2 - 72 * y1_0^6 * a^2 * b * c - + 12 * y1_0^6 * a^2 * c^2 + + 36 * y1_0^4 * a^2 * b^3 * c + + 72 * y1_0^4 * a^2 * b^2 * c^2 + + 36 * y1_0^4 * a^2 * b * c^3 - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = a * x0(t) - x1(t) * x2(t), - x1'(t) = b * x1(t) + x0(t) * x2(t), - x2'(t) = c * x2(t) + 1 // 3 * x0(t) * x1(t), - y1(t) = x0(t) + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = a * x0(t) - x1(t) * x2(t), + x1'(t) = b * x1(t) + x0(t) * x2(t), + x2'(t) = c * x2(t) + 1 // 3 * x0(t) * x1(t), + y1(t) = x0(t) + ), + :correct => correct, ), - :correct => correct, - ), - ) + ) - #--------------------------------------- + #--------------------------------------- - # predator-prey model - R, (y1_0, y1_1, y1_2, a, b, c, d) = - PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) - correct = - y1_2 * y1_0 - y1_1^2 - y1_1 * y1_0^2 * d + y1_1 * y1_0 * c + y1_0^3 * a * d - - y1_0^2 * a * c + # predator-prey model + R, (y1_0, y1_1, y1_2, a, b, c, d) = + PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) + correct = + y1_2 * y1_0 - y1_1^2 - y1_1 * y1_0^2 * d + y1_1 * y1_0 * c + y1_0^3 * a * d - + y1_0^2 * a * c - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t), - x1'(t) = -c * x1(t) + d * x0(t) * x1(t), - y1(t) = x0(t) + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t), + x1'(t) = -c * x1(t) + d * x0(t) * x1(t), + y1(t) = x0(t) + ), + :correct => correct, ), - :correct => correct, - ), - ) + ) - #--------------------------------------- + #--------------------------------------- - for case in test_cases - ode = case[:ode] - io_eq = collect(values(find_ioequations(ode)))[1] - correct = parent_ring_change(case[:correct], parent(io_eq)) - divisibility, remainder = divides(io_eq, correct) - @test divisibility - @test total_degree(remainder) == 0 + for case in test_cases + ode = case[:ode] + io_eq = collect(values(find_ioequations(ode)))[1] + correct = parent_ring_change(case[:correct], parent(io_eq)) + divisibility, remainder = divides(io_eq, correct) + @test divisibility + @test total_degree(remainder) == 0 + end end end diff --git a/test/io_projections.jl b/test/io_projections.jl index edc33c71d..84cde582a 100644 --- a/test/io_projections.jl +++ b/test/io_projections.jl @@ -1,60 +1,67 @@ -@testset "IO-projections (+ extra projection)" begin - cases = [] +if GROUP == "All" || GROUP == "Core" + @testset "IO-projections (+ extra projection)" begin + cases = [] - # Example from remark 3 in https://arxiv.org/pdf/2111.00991.pdf - ode = @ODEmodel( - x1'(t) = (1 + x1(t)^2) // 2, - x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), - y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), - y2(t) = x2(t) - ) - push!(cases, ode) + # Example from remark 3 in https://arxiv.org/pdf/2111.00991.pdf + ode = @ODEmodel( + x1'(t) = (1 + x1(t)^2) // 2, + x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), + y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), + y2(t) = x2(t) + ) + push!(cases, ode) - #-------------------------------------------------------- + #-------------------------------------------------------- - # Example from https://github.com/SciML/StructuralIdentifiability.jl/issues/132 - ode = @ODEmodel( - Complex'(t) = - 1 / C * ( - ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) - - (ke_Complex * Complex(t) * C) - - ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) - ), - Complex_2'(t) = - 1 / C * ( - ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) - (ke_Complex_2 * Complex_2(t) * C) - ), - Drug'(t) = - 1 / C * ( - -(ke_Drug * Drug(t) * C) - - ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + - (45 / 100 * ka * Drug_SC(t)) - ), - free_receptor'(t) = - 1 / C * ( - -((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + - (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - - ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) - ), - Drug_SC'(t) = - -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + u_SC(t), - y1(t) = Drug(t), - y2(t) = free_receptor(t) + Complex(t) + 2 * Complex_2(t) - ) - push!(cases, ode) + # Example from https://github.com/SciML/StructuralIdentifiability.jl/issues/132 + ode = @ODEmodel( + Complex'(t) = + 1 / C * ( + ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) - + (ke_Complex * Complex(t) * C) - ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) + ), + Complex_2'(t) = + 1 / C * ( + ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) - (ke_Complex_2 * Complex_2(t) * C) + ), + Drug'(t) = + 1 / C * ( + -(ke_Drug * Drug(t) * C) - + ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + + (45 / 100 * ka * Drug_SC(t)) + ), + free_receptor'(t) = + 1 / C * ( + -((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + + (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) + ), + Drug_SC'(t) = + -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + + u_SC(t), + y1(t) = Drug(t), + y2(t) = free_receptor(t) + Complex(t) + 2 * Complex_2(t) + ) + push!(cases, ode) - #--------------------------------------------------------- + #--------------------------------------------------------- - for ode in cases - proj, gpg, _ = find_ioprojections(ode, false) - for p in values(proj) - @test choose([p], gpg) == p + for ode in cases + proj, gpg, _ = find_ioprojections(ode, false) + for p in values(proj) + @test choose([p], gpg) == p + end + @test !check_primality(proj) + # taking simply a sum instead of random linear combination + extra_projection = sum(keys(proj)) + proj, gpg, projection_poly = find_ioprojections(ode, false, extra_projection) + @test choose([projection_poly], gpg) == projection_poly + @test check_primality(proj, [projection_poly]) end - @test !check_primality(proj) - # taking simply a sum instead of random linear combination - extra_projection = sum(keys(proj)) - proj, gpg, projection_poly = find_ioprojections(ode, false, extra_projection) - @test choose([projection_poly], gpg) == projection_poly - @test check_primality(proj, [projection_poly]) end end diff --git a/test/lc_univariate.jl b/test/lc_univariate.jl index 1d0919312..0c442a97d 100644 --- a/test/lc_univariate.jl +++ b/test/lc_univariate.jl @@ -1,8 +1,10 @@ -@testset "Univariate leading coefficient" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) - p = x^2 * y + x^2 * (z + z^3) + y - 5 - @test lc_univariate(p, x) == y + z + z^3 - @test lc_univariate(p, z) == x^2 - @test lc_univariate(p, y) == 1 + x^2 - @test lc_univariate(x + y, z) == x + y +if GROUP == "All" || GROUP == "Core" + @testset "Univariate leading coefficient" begin + R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + p = x^2 * y + x^2 * (z + z^3) + y - 5 + @test lc_univariate(p, x) == y + z + z^3 + @test lc_univariate(p, z) == x^2 + @test lc_univariate(p, y) == 1 + x^2 + @test lc_univariate(x + y, z) == x + y + end end diff --git a/test/lie_derivative.jl b/test/lie_derivative.jl index 64ea774eb..5969405fc 100644 --- a/test/lie_derivative.jl +++ b/test/lie_derivative.jl @@ -1,25 +1,24 @@ -using Test -using TestSetExtensions +if GROUP == "All" || GROUP == "Core" + @testset "Lie derivative" begin + ode = @ODEmodel( + x1'(t) = a * x1(t) + b * u(t), + x2'(t) = 1 // (1 - x1(t) - x2(t)), + y(t) = x1(t) + ) + @test lie_derivative(x1 + x2, ode) == + ((a * x1 + b * u) * (1 - x1 - x2) + 1) // (1 - x1 - x2) + @test lie_derivative(one(x1), ode) == zero(x1) + @test lie_derivative(zero(x1), ode) == zero(x1) -@testset "Lie derivative" begin - ode = @ODEmodel( - x1'(t) = a * x1(t) + b * u(t), - x2'(t) = 1 // (1 - x1(t) - x2(t)), - y(t) = x1(t) - ) - @test lie_derivative(x1 + x2, ode) == - ((a * x1 + b * u) * (1 - x1 - x2) + 1) // (1 - x1 - x2) - @test lie_derivative(one(x1), ode) == zero(x1) - @test lie_derivative(zero(x1), ode) == zero(x1) + ode = @ODEmodel( + x1'(t) = x2(t) // x1(t) + x2(t) * u(t), + x2'(t) = -1 - x1(t) * u(t), + y(t) = 1 + ) + @test lie_derivative(x1^2 + x2^2 // 1, ode) == zero(parent(ode)) // 1 - ode = @ODEmodel( - x1'(t) = x2(t) // x1(t) + x2(t) * u(t), - x2'(t) = -1 - x1(t) * u(t), - y(t) = 1 - ) - @test lie_derivative(x1^2 + x2^2 // 1, ode) == zero(parent(ode)) // 1 - - ode = @ODEmodel(x1'(t) = 2x1(t) + 3a, y(t) = x1(t)) - @test lie_derivative(5x1^2, ode) == 10x1 * (2x1 + 3a) - @test lie_derivative(a, ode) == zero(a) + ode = @ODEmodel(x1'(t) = 2x1(t) + 3a, y(t) = x1(t)) + @test lie_derivative(5x1^2, ode) == 10x1 * (2x1 + 3a) + @test lie_derivative(a, ode) == zero(a) + end end diff --git a/test/linear_compartment.jl b/test/linear_compartment.jl index f52fad656..7d244f59e 100644 --- a/test/linear_compartment.jl +++ b/test/linear_compartment.jl @@ -1,167 +1,169 @@ -@testset "Identifiability of linear compartment models" begin - test_cases = [] - - push!( - test_cases, - Dict( - :graph => [Array{Int, 1}(), [1], [1]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [1], - :result => Dict((3, 1) => :locally, (2, 1) => :locally), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [Array{Int, 1}(), [1], [1, 2]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [1], - :result => Dict( - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, - (2, 1) => :locally, +if GROUP == "All" || GROUP == "Core" + @testset "Identifiability of linear compartment models" begin + test_cases = [] + + push!( + test_cases, + Dict( + :graph => [Array{Int, 1}(), [1], [1]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [1], + :result => Dict((3, 1) => :locally, (2, 1) => :locally), ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [2], - :result => Dict( - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (1, 2) => :locally, - (3, 2) => :nonidentifiable, - (2, 1) => :globally, - ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [2], - :leaks => Array{Int, 1}(), - :inputs => [2], - :result => Dict( - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (1, 2) => :locally, - (3, 2) => :nonidentifiable, - (2, 1) => :nonidentifiable, + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [Array{Int, 1}(), [1], [1, 2]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [1], + :result => Dict( + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, + (2, 1) => :locally, + ), ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2, 3], [1, 3], [1, 2]], - :outputs => [3], - :leaks => [1], - :inputs => [1, 2], - :result => Dict( - (1, 2) => :globally, - (1, 3) => :globally, - (2, 1) => :globally, - (2, 3) => :globally, - (3, 1) => :globally, - (3, 2) => :globally, - (1, 0) => :globally, + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [2], + :result => Dict( + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (1, 2) => :locally, + (3, 2) => :nonidentifiable, + (2, 1) => :globally, + ), ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2, 3], [1, 3], [1, 2]], - :outputs => [3], - :leaks => Array{Int, 1}(), - :inputs => [1, 2], - :result => Dict( - (1, 2) => :nonidentifiable, - (1, 3) => :globally, - (2, 1) => :nonidentifiable, - (2, 3) => :globally, - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [2], + :leaks => Array{Int, 1}(), + :inputs => [2], + :result => Dict( + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (1, 2) => :locally, + (3, 2) => :nonidentifiable, + (2, 1) => :nonidentifiable, + ), ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [3], - :leaks => [1], - :inputs => [1, 3], - :result => Dict( - (1, 2) => :globally, - (2, 1) => :globally, - (2, 3) => :globally, - (3, 1) => :globally, - (3, 2) => :globally, - (1, 0) => :globally, + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2, 3], [1, 3], [1, 2]], + :outputs => [3], + :leaks => [1], + :inputs => [1, 2], + :result => Dict( + (1, 2) => :globally, + (1, 3) => :globally, + (2, 1) => :globally, + (2, 3) => :globally, + (3, 1) => :globally, + (3, 2) => :globally, + (1, 0) => :globally, + ), ), - ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [3], - :leaks => Array{Int, 1}(), - :inputs => [1, 3], - :result => Dict( - (1, 2) => :nonidentifiable, - (2, 1) => :nonidentifiable, - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2, 3], [1, 3], [1, 2]], + :outputs => [3], + :leaks => Array{Int, 1}(), + :inputs => [1, 2], + :result => Dict( + (1, 2) => :nonidentifiable, + (1, 3) => :globally, + (2, 1) => :nonidentifiable, + (2, 3) => :globally, + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, + ), ), - ), - ) + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [3], + :leaks => [1], + :inputs => [1, 3], + :result => Dict( + (1, 2) => :globally, + (2, 1) => :globally, + (2, 3) => :globally, + (3, 1) => :globally, + (3, 2) => :globally, + (1, 0) => :globally, + ), + ), + ) - for case in test_cases - ode = linear_compartment_model( - case[:graph], - case[:inputs], - case[:outputs], - case[:leaks], + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [3], + :leaks => Array{Int, 1}(), + :inputs => [1, 3], + :result => Dict( + (1, 2) => :nonidentifiable, + (2, 1) => :nonidentifiable, + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, + ), + ), ) - bring = ode.poly_ring - correct = Dict{fmpq_mpoly, Symbol}() - for (e, id) in case[:result] - correct[str_to_var("a_$(e[2])_$(e[1])", bring)] = id + + #-------------------------------------------------------------------------- + + for case in test_cases + ode = linear_compartment_model( + case[:graph], + case[:inputs], + case[:outputs], + case[:leaks], + ) + bring = ode.poly_ring + correct = Dict{fmpq_mpoly, Symbol}() + for (e, id) in case[:result] + correct[str_to_var("a_$(e[2])_$(e[1])", bring)] = id + end + result = assess_identifiability(ode, funcs_to_check = collect(keys(correct))) + @test correct == result end - result = assess_identifiability(ode, funcs_to_check = collect(keys(correct))) - @test correct == result end end diff --git a/test/local_identifiability.jl b/test/local_identifiability.jl index 0601baead..166284c81 100644 --- a/test/local_identifiability.jl +++ b/test/local_identifiability.jl @@ -1,133 +1,135 @@ -@testset "Assessing local identifiability" begin - test_cases = [] +if GROUP == "All" || GROUP == "Core" + @testset "Assessing local identifiability" begin + test_cases = [] - # 2-compartiment model + # 2-compartiment model - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [ - a01, - a21, - a12, - a01 * a12, - a01 + a12 + a21, - (a01 + a12 + a21) // (a01 * a12), - x0, - x1, - ] - correct = [false, false, false, true, true, true, true, false] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [ + a01, + a21, + a12, + a01 * a12, + a01 + a12 + a21, + (a01 + a12 + a21) // (a01 * a12), + x0, + x1, + ] + correct = [false, false, false, true, true, true, true, false] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), - x1'(t) = c * x1(t) + d * x0(t) * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [a, b, c, d, x0, x1] - correct = [true, false, true, true, true, false] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), + x1'(t) = c * x1(t) + d * x0(t) * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [a, b, c, d, x0, x1] + correct = [true, false, true, true, true, false] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), - I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), - W'(t) = xi * (I(t) - W(t)), - R'(t) = gam * I(t) - (mu + a) * R(t), - y(t) = k * I(t) - ) - funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k, S, I, W, R] - correct = [true for _ in funcs_to_test] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), + I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), + W'(t) = xi * (I(t) - W(t)), + R'(t) = gam * I(t) - (mu + a) * R(t), + y(t) = k * I(t) + ) + funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k, S, I, W, R] + correct = [true for _ in funcs_to_test] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) - ) - funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] - correct = OrderedDict([ - b => true, - c => true, - alpha => false, - beta => true, - delta => true, - gama => false, - beta + delta => true, - beta * delta => true, - ]) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) + ) + funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] + correct = OrderedDict([ + b => true, + c => true, + alpha => false, + beta => true, + delta => true, + gama => false, + beta + delta => true, + beta * delta => true, + ]) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = a1 + a2 + a3 * a4, - x2'(t) = a4 * a5 + a6 + a7 - 8 * a8, - y(t) = x1(t) * x2(t) - ) - funcs_to_test = [a1, a2, a3, a4, a5, a6, a7, a8] - correct = OrderedDict(a => false for a in funcs_to_test) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = a1 + a2 + a3 * a4, + x2'(t) = a4 * a5 + a6 + a7 - 8 * a8, + y(t) = x1(t) * x2(t) + ) + funcs_to_test = [a1, a2, a3, a4, a5, a6, a7, a8] + correct = OrderedDict(a => false for a in funcs_to_test) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = 0, - x2'(t) = 0, - x3'(t) = x3(t), - y(t) = (x1(t) + x2(t))^2, - y2(t) = x3(t) - ) - funcs_to_test = [x1, x2, x1 + x2] - correct = OrderedDict(x1 => false, x2 => false, x1 + x2 => true) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = 0, + x2'(t) = 0, + x3'(t) = x3(t), + y(t) = (x1(t) + x2(t))^2, + y2(t) = x3(t) + ) + funcs_to_test = [x1, x2, x1 + x2] + correct = OrderedDict(x1 => false, x2 => false, x1 + x2 => true) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - for case in test_cases - trbasis = [] - ode = case[:ode] - result = assess_local_identifiability( - ode, - funcs_to_check = case[:funcs], - trbasis = trbasis, - ) - @test result == case[:correct] - for (i, p) in enumerate(trbasis) - res_for_p = assess_local_identifiability(ode, funcs_to_check = [p]) - @test !first(values(res_for_p)) - ode = add_outputs(ode, Dict("YYY$i" => p)) + for case in test_cases + trbasis = [] + ode = case[:ode] + result = assess_local_identifiability( + ode, + funcs_to_check = case[:funcs], + trbasis = trbasis, + ) + @test result == case[:correct] + for (i, p) in enumerate(trbasis) + res_for_p = assess_local_identifiability(ode, funcs_to_check = [p]) + @test !first(values(res_for_p)) + ode = add_outputs(ode, Dict("YYY$i" => p)) + end + @test all(values(assess_local_identifiability(ode))) end - @test all(values(assess_local_identifiability(ode))) end end diff --git a/test/local_identifiability_discrete_aux.jl b/test/local_identifiability_discrete_aux.jl index 2a172b1a2..119816146 100644 --- a/test/local_identifiability_discrete_aux.jl +++ b/test/local_identifiability_discrete_aux.jl @@ -1,81 +1,87 @@ -@testset "Discrete local identifiability, internal function" begin - cases = [] +if GROUP == "All" || GROUP == "Core" + @testset "Discrete local identifiability, internal function" begin + cases = [] - dds = @ODEmodel(a'(t) = (b + c) * a(t) + 1, y(t) = a(t)) + dds = @ODEmodel(a'(t) = (b + c) * a(t) + 1, y(t) = a(t)) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :none, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :none, + ), + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :all, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :all, + ), + ) - #--------------------- + #--------------------- - dds = @ODEmodel(a'(t) = b(t) * a(t) + c, b'(t) = d * a(t), y(t) = b(t)) + dds = @ODEmodel(a'(t) = b(t) * a(t) + c, b'(t) = d * a(t), y(t) = b(t)) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict( - b => true, - a => false, - c => false, - d => false, - d * a => true, - d * c => true, + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict( + b => true, + a => false, + c => false, + d => false, + d * a => true, + d * c => true, + ), + :known => :none, ), - :known => :none, - ), - ) + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(b => true, a => true, c => true, d => true), - :known => [a], - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(b => true, a => true, c => true, d => true), + :known => [a], + ), + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(b => true, a => true, c => true, d => true), - :known => :all, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(b => true, a => true, c => true, d => true), + :known => :all, + ), + ) - # ------------------- + # ------------------- - # 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)) + # 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, - OrderedDict(:dds => dds, :res => Dict(theta => true, x => true), :known => :none), - ) + push!( + cases, + OrderedDict( + :dds => dds, + :res => Dict(theta => true, x => true), + :known => :none, + ), + ) - # ------------------- + # ------------------- - for c in cases - @test _assess_local_identifiability_discrete_aux( - c[:dds], - collect(keys(c[:res])), - c[:known], - ) == c[:res] + for c in cases + @test _assess_local_identifiability_discrete_aux( + c[:dds], + collect(keys(c[:res])), + c[:known], + ) == c[:res] + end end end diff --git a/test/local_identifiability_me.jl b/test/local_identifiability_me.jl index 01365120c..769402e25 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -1,221 +1,230 @@ -# Copyright (c) 2021, R. Dong, C. Goodbarke, H. Harrington, G. Pogudin -# Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon - -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -function _linear_compartment_model(graph, sinks) - """ - Input: - - graph - graph of the model network represented via adjacency lists - - sinks - the indices of nodes having a sink - Output: the corresponding ODE object where each parameter a_ij is replaced - with a_ij + b_ij * x_0, where x_0 is a constant input encoded as a constant output - """ - n = length(graph) - x_vars_names = ["x$i" for i in 0:n] - edges_vars_names = Array{String, 1}() - for i in 1:n - for j in graph[i] - push!(edges_vars_names, "a_$(i)_$(j)") - push!(edges_vars_names, "b_$(i)_$(j)") - end - end - for s in sinks - push!(edges_vars_names, "a_$(s)_0") - push!(edges_vars_names, "b_$(s)_0") - end - R, vars = - Nemo.PolynomialRing(Nemo.QQ, vcat(x_vars_names, edges_vars_names, ["y1", "y2"])) - x_vars = vars[2:(n + 1)] - x0 = vars[1] - equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}( - x => R(0) for x in x_vars - ) - equations[x0] = R(0) - for i in 1:n - for j in graph[i] - rate = str_to_var("a_$(i)_$(j)", R) + str_to_var("b_$(i)_$(j)", R) * x0 - - if i != j - equations[x_vars[j]] += x_vars[i] * rate - equations[x_vars[i]] -= x_vars[i] * rate - else - equations[x_vars[i]] -= x_vars[i] * rate +if GROUP == "All" || GROUP == "Core" + # Copyright (c) 2021, R. Dong, C. Goodbarke, H. Harrington, G. Pogudin + # Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon + + # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + function _linear_compartment_model(graph, sinks) + """ + Input: + - graph - graph of the model network represented via adjacency lists + - sinks - the indices of nodes having a sink + Output: the corresponding ODE object where each parameter a_ij is replaced + with a_ij + b_ij * x_0, where x_0 is a constant input encoded as a constant output + """ + n = length(graph) + x_vars_names = ["x$i" for i in 0:n] + edges_vars_names = Array{String, 1}() + for i in 1:n + for j in graph[i] + push!(edges_vars_names, "a_$(i)_$(j)") + push!(edges_vars_names, "b_$(i)_$(j)") end end - if i in sinks - rate = str_to_var("a_$(i)_0", R) + str_to_var("b_$(i)_0", R) * x0 - equations[x_vars[i]] += -x_vars[i] * rate + for s in sinks + push!(edges_vars_names, "a_$(s)_0") + push!(edges_vars_names, "b_$(s)_0") end + R, vars = + Nemo.PolynomialRing(Nemo.QQ, vcat(x_vars_names, edges_vars_names, ["y1", "y2"])) + x_vars = vars[2:(n + 1)] + x0 = vars[1] + equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}( + x => R(0) for x in x_vars + ) + equations[x0] = R(0) + for i in 1:n + for j in graph[i] + rate = str_to_var("a_$(i)_$(j)", R) + str_to_var("b_$(i)_$(j)", R) * x0 + + if i != j + equations[x_vars[j]] += x_vars[i] * rate + equations[x_vars[i]] -= x_vars[i] * rate + else + equations[x_vars[i]] -= x_vars[i] * rate + end + end + if i in sinks + rate = str_to_var("a_$(i)_0", R) + str_to_var("b_$(i)_0", R) * x0 + equations[x_vars[i]] += -x_vars[i] * rate + end + end + return ODE{fmpq_mpoly}( + equations, + Dict(vars[end] => x_vars[1], vars[end - 1] => x0), + Array{fmpq_mpoly, 1}(), + ) end - return ODE{fmpq_mpoly}( - equations, - Dict(vars[end] => x_vars[1], vars[end - 1] => x0), - Array{fmpq_mpoly, 1}(), - ) -end -#------------------------------------------------------------------------------ - -function bicycle(n) - """ - Generates a bidirected cycle of length n - """ - graph = [] - for i in 1:n - prev = (i == 1) ? n : (i - 1) - next = (i == n) ? 1 : i + 1 - push!(graph, [prev, next]) + #------------------------------------------------------------------------------ + + function bicycle(n) + """ + Generates a bidirected cycle of length n + """ + graph = [] + for i in 1:n + prev = (i == 1) ? n : (i - 1) + next = (i == n) ? 1 : i + 1 + push!(graph, [prev, next]) + end + return graph end - return graph -end -#------------------------------------------------------------------------------ + #------------------------------------------------------------------------------ -function cycle(n) - """ - Single directed cycle - """ - graph = [[(i == n) ? 1 : (i + 1)] for i in 1:n] - return graph -end + function cycle(n) + """ + Single directed cycle + """ + graph = [[(i == n) ? 1 : (i + 1)] for i in 1:n] + return graph + end -#------------------------------------------------------------------------------ + #------------------------------------------------------------------------------ -function catenary(n) - """ - Bidirected chain from 1 to n - """ - graph = [[] for i in 1:n] - for i in 1:n - if i != 1 - push!(graph[i], i - 1) - end - if i != n - push!(graph[i], i + 1) + function catenary(n) + """ + Bidirected chain from 1 to n + """ + graph = [[] for i in 1:n] + for i in 1:n + if i != 1 + push!(graph[i], i - 1) + end + if i != n + push!(graph[i], i + 1) + end end + return graph end - return graph -end -#------------------------------------------------------------------------------ + #------------------------------------------------------------------------------ -function mammilary(n) - """ - Bidirected 'star' with center at 1 and rays to 2, ..., n - """ - graph = [] - push!(graph, [i for i in 2:n]) - for i in 2:n - push!(graph, [1]) + function mammilary(n) + """ + Bidirected 'star' with center at 1 and rays to 2, ..., n + """ + graph = [] + push!(graph, [i for i in 2:n]) + for i in 2:n + push!(graph, [1]) + end + return graph end - return graph -end -#------------------------------------------------------------------------------ + #------------------------------------------------------------------------------ + + ############################################################################### + + @testset "Assessing local identifiability (multiexperiment)" begin + + # checking bounds + + test_cases = [ + Dict(:name => "Cyclic", :graph => cycle, :bound => [3]), + Dict(:name => "Catenary", :graph => catenary, :bound => [4, 5]), + Dict(:name => "Mammilary", :graph => mammilary, :bound => [4, 5]), + ] + + n_min = 3 + n_max = 8 + for case in test_cases + for n in n_min:n_max + model = _linear_compartment_model(case[:graph](n), [1]) + println(case[:name] * ", n = $n") + @time result = assess_local_identifiability(model, type = :ME) + correct = undef + if n - n_min + 1 > length(case[:bound]) + correct = case[:bound][end] + else + correct = case[:bound][n - n_min + 1] + end + @test correct == result[2] + end + end -############################################################################### + # checking bounds and results -@testset "Assessing local identifiability (multiexperiment)" begin + test_cases = [] - # checking bounds + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = + [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] + correct = [false, false, false, true, true, true] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => (OrderedDict(funcs_to_test .=> correct), 1), + ), + ) - test_cases = [ - Dict(:name => "Cyclic", :graph => cycle, :bound => [3]), - Dict(:name => "Catenary", :graph => catenary, :bound => [4, 5]), - Dict(:name => "Mammilary", :graph => mammilary, :bound => [4, 5]), - ] + #-------------------------------------------------------------------------- + + # Example 7.7 from https://arxiv.org/pdf/2011.10868.pdf + ode = + @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) + funcs_to_test = [mu1, mu2] + correct = [true, true] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => (OrderedDict(funcs_to_test .=> correct), 2), + ), + ) - n_min = 3 - n_max = 8 - for case in test_cases - for n in n_min:n_max - model = _linear_compartment_model(case[:graph](n), [1]) - println(case[:name] * ", n = $n") - @time result = assess_local_identifiability(model, type = :ME) - correct = undef - if n - n_min + 1 > length(case[:bound]) - correct = case[:bound][end] - else - correct = case[:bound][n - n_min + 1] - end - @test correct == result[2] - end - end + #-------------------------------------------------------------------------- + + # Example 7.8 from https://arxiv.org/pdf/2011.10868.pdf + ode = @ODEmodel( + S'(t) = -b * S(t) * I(t) / N, + E'(t) = b * S(t) * I(t) / N - nu * E(t), + I'(t) = nu * E(t) - a * I(t), + c'(t) = 0, + y1(t) = c(t) * I(t) + d * E(t), + y2(t) = c(t), + y3(t) = N + ) + funcs_to_test = [b, nu, d, a] + correct = OrderedDict([b => true, nu => true, d => true, a => true]) + push!( + test_cases, + Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1)), + ) - # checking bounds and results - - test_cases = [] - - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = - [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] - correct = [false, false, false, true, true, true] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => (OrderedDict(funcs_to_test .=> correct), 1), - ), - ) - - #-------------------------------------------------------------------------- - - # Example 7.7 from https://arxiv.org/pdf/2011.10868.pdf - ode = @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) - funcs_to_test = [mu1, mu2] - correct = [true, true] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => (OrderedDict(funcs_to_test .=> correct), 2), - ), - ) - - #-------------------------------------------------------------------------- - - # Example 7.8 from https://arxiv.org/pdf/2011.10868.pdf - ode = @ODEmodel( - S'(t) = -b * S(t) * I(t) / N, - E'(t) = b * S(t) * I(t) / N - nu * E(t), - I'(t) = nu * E(t) - a * I(t), - c'(t) = 0, - y1(t) = c(t) * I(t) + d * E(t), - y2(t) = c(t), - y3(t) = N - ) - funcs_to_test = [b, nu, d, a] - correct = OrderedDict([b => true, nu => true, d => true, a => true]) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1))) - - #-------------------------------------------------------------------------- - - # 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 = OrderedDict([a => false]) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0))) - - #-------------------------------------------------------------------------- - - for case in test_cases - result = assess_local_identifiability( - case[:ode], - funcs_to_check = case[:funcs], - prob_threshold = 0.932, - type = :ME, + #-------------------------------------------------------------------------- + + # 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 = OrderedDict([a => false]) + push!( + test_cases, + Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0)), ) - @test result == case[:correct] + + #-------------------------------------------------------------------------- + + for case in test_cases + result = assess_local_identifiability( + case[:ode], + funcs_to_check = case[:funcs], + prob_threshold = 0.932, + type = :ME, + ) + @test result == case[:correct] + end end end diff --git a/test/logging.jl b/test/logging.jl index b4c5e9fb0..054a45960 100644 --- a/test/logging.jl +++ b/test/logging.jl @@ -1,19 +1,21 @@ -# An attempt to test logging -using Logging +if GROUP == "All" || GROUP == "Core" + # An attempt to test logging + using Logging -@testset "Logging" begin - ode = StructuralIdentifiability.@ODEmodel(x'(t) = x^42, y(t) = x) + @testset "Logging" begin + ode = StructuralIdentifiability.@ODEmodel(x'(t) = x^42, y(t) = x) - # Some logs - @test_logs StructuralIdentifiability.assess_identifiability(ode) - # No logs - @test_logs min_level = Logging.Warn StructuralIdentifiability.assess_identifiability( - ode, - loglevel = Logging.Warn, - ) - # Many logs - @test_logs StructuralIdentifiability.assess_identifiability( - ode, - loglevel = Logging.Debug, - ) + # Some logs + @test_logs StructuralIdentifiability.assess_identifiability(ode) + # No logs + @test_logs min_level = Logging.Warn StructuralIdentifiability.assess_identifiability( + ode, + loglevel = Logging.Warn, + ) + # Many logs + @test_logs StructuralIdentifiability.assess_identifiability( + ode, + loglevel = Logging.Debug, + ) + end end diff --git a/test/monomial_compress.jl b/test/monomial_compress.jl index 74eed4c4d..f7e378b9a 100644 --- a/test/monomial_compress.jl +++ b/test/monomial_compress.jl @@ -1,18 +1,20 @@ -@testset "Monomial compression test" begin - R, (v1, v2, v3, v4) = Nemo.PolynomialRing(Nemo.QQ, ["v1", "v2", "v3", "v4"]) - tests = [ - v1 + v2 - 2, - v4 + v4 * v1 - 3 * v2 * v4, - (v1 + v2 + v2^2) * (v3 + v4), - (v1 + v2^2) * (v3^3 + v4) - 7 * (v1 - 3 - v2) * (v3 - v4^2 - v3^2), - ] +if GROUP == "All" || GROUP == "Core" + @testset "Monomial compression test" begin + R, (v1, v2, v3, v4) = Nemo.PolynomialRing(Nemo.QQ, ["v1", "v2", "v3", "v4"]) + tests = [ + v1 + v2 - 2, + v4 + v4 * v1 - 3 * v2 * v4, + (v1 + v2 + v2^2) * (v3 + v4), + (v1 + v2^2) * (v3^3 + v4) - 7 * (v1 - 3 - v2) * (v3 - v4^2 - v3^2), + ] - for t in tests - a, b = StructuralIdentifiability.monomial_compress(t, [v1, v2]) - s = 0 - for (x, y) in zip(a, b) - s += parent_ring_change(x, parent(t)) * parent_ring_change(y, parent(t)) + for t in tests + a, b = StructuralIdentifiability.monomial_compress(t, [v1, v2]) + s = 0 + for (x, y) in zip(a, b) + s += parent_ring_change(x, parent(t)) * parent_ring_change(y, parent(t)) + end + @test s == t end - @test s == t end end diff --git a/test/ode.jl b/test/ode.jl index f8035a7ee..96e40ec5b 100644 --- a/test/ode.jl +++ b/test/ode.jl @@ -1,18 +1,20 @@ -@testset "ODE struct" begin - # adding outputs - ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t) + a) - ode = StructuralIdentifiability.add_outputs(ode, Dict("y" => a * x)) - @test StructuralIdentifiability.AbstractAlgebra.nvars(parent(ode)) == 3 +if GROUP == "All" || GROUP == "Core" + @testset "ODE struct" begin + # adding outputs + ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t) + a) + ode = StructuralIdentifiability.add_outputs(ode, Dict("y" => a * x)) + @test StructuralIdentifiability.AbstractAlgebra.nvars(parent(ode)) == 3 - # two or more equations on the same variable - @test_throws DomainError StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + a, - x1'(t) = x1(t) + b, - y(t) = x1 - ) - @test_throws DomainError StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t), - y(t) = x1, - y(t) = x1 - ) + # two or more equations on the same variable + @test_throws DomainError StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + a, + x1'(t) = x1(t) + b, + y(t) = x1 + ) + @test_throws DomainError StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t), + y(t) = x1, + y(t) = x1 + ) + end end diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index 654ad485c..09145f6a6 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -1,122 +1,125 @@ -# using Profile - -function rand_poly(deg, vars) - result = 0 - indices = vcat(collect(1:length(vars)), collect(1:length(vars))) - monomials = [] - for d in 0:deg - for subs in StructuralIdentifiability.IterTools.subsets(indices, d) - push!(monomials, subs) +if GROUP == "All" || GROUP == "Core" + function rand_poly(deg, vars) + result = 0 + indices = vcat(collect(1:length(vars)), collect(1:length(vars))) + monomials = [] + for d in 0:deg + for subs in StructuralIdentifiability.IterTools.subsets(indices, d) + push!(monomials, subs) + end end - end - for subs in monomials - monom = rand(-50:50) - for v_ind in subs - monom *= vars[v_ind] + for subs in monomials + monom = rand(-50:50) + for v_ind in subs + monom *= vars[v_ind] + end + result += monom end - result += monom - end - return result -end + return result + end -@testset "Power series solution for an ODE system" begin - R, (x, x_dot) = Nemo.PolynomialRing(Nemo.QQ, ["x", "x_dot"]) - exp_t = ps_ode_solution( - [x_dot - x], - Dict{fmpq_mpoly, fmpq}(x => 1), - Dict{fmpq_mpoly, Array{fmpq, 1}}(), - 20, - )[x] - @test valuation(ps_diff(exp_t) - exp_t) == 19 + @testset "Power series solution for an ODE system" begin + R, (x, x_dot) = Nemo.PolynomialRing(Nemo.QQ, ["x", "x_dot"]) + exp_t = ps_ode_solution( + [x_dot - x], + Dict{fmpq_mpoly, fmpq}(x => 1), + Dict{fmpq_mpoly, Array{fmpq, 1}}(), + 20, + )[x] + @test valuation(ps_diff(exp_t) - exp_t) == 19 - R, (x, y, x_dot, y_dot, u) = - Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) - prec = 100 - eqs = [x_dot - x + 3 * x * y - u, y_dot + 2 * y - 4 * x * y] - u_coeff = [rand(1:5) for i in 1:prec] - sol = ps_ode_solution(eqs, Dict(x => 0, y => -2), Dict(u => u_coeff), prec) - @test map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) == [prec - 1, prec - 1] + R, (x, y, x_dot, y_dot, u) = + Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) + prec = 100 + eqs = [x_dot - x + 3 * x * y - u, y_dot + 2 * y - 4 * x * y] + u_coeff = [rand(1:5) for i in 1:prec] + sol = ps_ode_solution(eqs, Dict(x => 0, y => -2), Dict(u => u_coeff), prec) + @test map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) == [prec - 1, prec - 1] - F = Nemo.Native.GF(2^31 - 1) - prec = 100 + F = Nemo.Native.GF(2^31 - 1) + prec = 100 - # Testing the function ps_ode_solution by itself - for i in 1:30 - # Setting up the ring - NUMX = 5 - NUMU = 3 - varnames = vcat( - ["x_$(i)_dot" for i in 1:NUMX], - ["x_$i" for i in 1:NUMX], - ["u_$i" for i in 1:NUMU], - ) - R, vars = Nemo.PolynomialRing(F, varnames) + # Testing the function ps_ode_solution by itself + for i in 1:30 + # Setting up the ring + NUMX = 5 + NUMU = 3 + varnames = vcat( + ["x_$(i)_dot" for i in 1:NUMX], + ["x_$i" for i in 1:NUMX], + ["u_$i" for i in 1:NUMU], + ) + R, vars = Nemo.PolynomialRing(F, varnames) - # Generating the initial conditions and inputs - ic = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMX) - inputs = - Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(2 * NUMX + 1):end]) - # a dictionary for evaluation at zero (to avoid singularities) - eval_at_zero = merge(ic, Dict(u => val[1] for (u, val) in inputs)) + # Generating the initial conditions and inputs + ic = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMX) + inputs = + Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(2 * NUMX + 1):end]) + # a dictionary for evaluation at zero (to avoid singularities) + eval_at_zero = merge(ic, Dict(u => val[1] for (u, val) in inputs)) - # Generating denominators not vanishing at t = 0 - denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] - while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) + # Generating denominators not vanishing at t = 0 denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] - end + while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) + denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] + end - eqs = - [denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for i in 1:NUMX] - @time sol = ps_ode_solution(eqs, ic, inputs, prec) - evals = map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) - for e in evals - @test e >= prec - 1 + eqs = [ + denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for + i in 1:NUMX + ] + @time sol = ps_ode_solution(eqs, ic, inputs, prec) + evals = map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) + for e in evals + @test e >= prec - 1 + end end - end - # Testing ps_ode_solution in conjuntion with the ODE class - for i in 1:30 - # Setting up the ring - NUMX = 3 - NUMP = 3 - NUMU = 2 - varnames = vcat( - ["x_$i" for i in 1:NUMX], - ["p_$i" for i in 1:NUMP], - ["u_$i" for i in 1:NUMU], - ) - R, vars = Nemo.PolynomialRing(F, varnames) - PType = gfp_mpoly - TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} + # Testing ps_ode_solution in conjuntion with the ODE class + for i in 1:30 + # Setting up the ring + NUMX = 3 + NUMP = 3 + NUMU = 2 + varnames = vcat( + ["x_$i" for i in 1:NUMX], + ["p_$i" for i in 1:NUMP], + ["u_$i" for i in 1:NUMU], + ) + R, vars = Nemo.PolynomialRing(F, varnames) + PType = gfp_mpoly + TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} - # Generating the intial conditions etc - ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) - param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) - inputs = - Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end]) - eval_at_zero = merge(ic, param_vals, Dict(u => val[1] for (u, val) in inputs)) + # Generating the intial conditions etc + ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) + param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) + inputs = Dict( + u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end] + ) + eval_at_zero = merge(ic, param_vals, Dict(u => val[1] for (u, val) in inputs)) - # Generating denominators not vanishing at t = 0 - denominators = [rand_poly(1, vars) for i in 1:NUMX] - while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) + # Generating denominators not vanishing at t = 0 denominators = [rand_poly(1, vars) for i in 1:NUMX] - end + while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) + denominators = [rand_poly(1, vars) for i in 1:NUMX] + end - eqs = TDict(vars[i] => rand_poly(2, vars) // denominators[i] for i in 1:NUMX) - ode = ODE{PType}(eqs, TDict(), vars[(NUMX + NUMP + 1):end]) - @time sol = power_series_solution(ode, param_vals, ic, inputs, prec) - ps_ring = parent(collect(values(sol))[1]) - for (p, val) in param_vals - sol[p] = ps_ring(val) - end - eval_point = [sol[v] for v in vars] - for (xd, f) in eqs - num, den = unpack_fraction(f) - lhs = divexact(evaluate(num, eval_point), evaluate(den, eval_point)) - rhs = ps_diff(sol[xd]) - @test valuation(lhs - rhs) >= prec - 1 + eqs = TDict(vars[i] => rand_poly(2, vars) // denominators[i] for i in 1:NUMX) + ode = ODE{PType}(eqs, TDict(), vars[(NUMX + NUMP + 1):end]) + @time sol = power_series_solution(ode, param_vals, ic, inputs, prec) + ps_ring = parent(collect(values(sol))[1]) + for (p, val) in param_vals + sol[p] = ps_ring(val) + end + eval_point = [sol[v] for v in vars] + for (xd, f) in eqs + num, den = unpack_fraction(f) + lhs = divexact(evaluate(num, eval_point), evaluate(den, eval_point)) + rhs = ps_diff(sol[xd]) + @test valuation(lhs - rhs) >= prec - 1 + end end end end diff --git a/test/paradigm_shift.jl b/test/paradigm_shift.jl index f59b0b7da..ed1c009f9 100644 --- a/test/paradigm_shift.jl +++ b/test/paradigm_shift.jl @@ -1,266 +1,270 @@ -≡(A, B) = A ⊆ B && B ⊆ A +if GROUP == "All" || GROUP == "Core" + ≡(A, B) = A ⊆ B && B ⊆ A -function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relations) - # NOTE: should this be strengthened to == ? - # @test map(string, old_ode.u_vars) ≡ map(string, new_ode.u_vars) - # @test map(string, old_ode.y_vars) ≡ map(string, new_ode.y_vars) - @test length(old_ode.y_vars) == length(new_ode.y_vars) - @test length(old_ode.u_vars) == length(new_ode.u_vars) - @test base_ring(parent(first(values(var_mapping)))) == parent(old_ode) + function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relations) + # NOTE: should this be strengthened to == ? + # @test map(string, old_ode.u_vars) ≡ map(string, new_ode.u_vars) + # @test map(string, old_ode.y_vars) ≡ map(string, new_ode.y_vars) + @test length(old_ode.y_vars) == length(new_ode.y_vars) + @test length(old_ode.u_vars) == length(new_ode.u_vars) + @test base_ring(parent(first(values(var_mapping)))) == parent(old_ode) - # We check that the PS solutions of the new_ode coincide with the solutions - # of the old_ode projected onto the new variables - ord = 5 - # NOTE: there may be an unlucky specialization point, and newton iteration - # will fail, and tests will fail as a result - bound = 100 - old_params = old_ode.parameters - old_vars = old_ode.x_vars - old_inputs = old_ode.u_vars - param_spec_point = map(Nemo.QQ, rand(1:bound, length(old_params))) - old_param_spec = Dict(old_params .=> param_spec_point) - ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) - old_var_ic = Dict(old_vars .=> ic_point) - input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) - old_input_ts = Dict{eltype(old_vars), Vector{Nemo.fmpq}}(old_inputs .=> input_ts) + # We check that the PS solutions of the new_ode coincide with the solutions + # of the old_ode projected onto the new variables + ord = 5 + # NOTE: there may be an unlucky specialization point, and newton iteration + # will fail, and tests will fail as a result + bound = 100 + old_params = old_ode.parameters + old_vars = old_ode.x_vars + old_inputs = old_ode.u_vars + param_spec_point = map(Nemo.QQ, rand(1:bound, length(old_params))) + old_param_spec = Dict(old_params .=> param_spec_point) + ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) + old_var_ic = Dict(old_vars .=> ic_point) + input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) + old_input_ts = Dict{eltype(old_vars), Vector{Nemo.fmpq}}(old_inputs .=> input_ts) - old_solutions = StructuralIdentifiability.power_series_solution( - old_ode, - old_param_spec, - old_var_ic, - old_input_ts, - ord, - ) - - new_params = new_ode.parameters - new_vars = new_ode.x_vars - new_inputs = new_ode.u_vars - new_param_spec = Dict( - new_param => StructuralIdentifiability.eval_at_dict( - var_mapping[new_param], + old_solutions = StructuralIdentifiability.power_series_solution( + old_ode, old_param_spec, - ) for new_param in new_params - ) - new_var_ic = Dict( - new_var => StructuralIdentifiability.eval_at_dict( - var_mapping[new_var], - merge(old_param_spec, old_var_ic), - ) for new_var in new_vars - ) - new_input_ts = Dict{eltype(new_vars), Vector{Nemo.fmpq}}( - new_input => old_input_ts[numerator(var_mapping[new_input])] for - new_input in new_inputs - ) + old_var_ic, + old_input_ts, + ord, + ) - new_solutions = StructuralIdentifiability.power_series_solution( - new_ode, - new_param_spec, - new_var_ic, - new_input_ts, - ord, - ) + new_params = new_ode.parameters + new_vars = new_ode.x_vars + new_inputs = new_ode.u_vars + new_param_spec = Dict( + new_param => StructuralIdentifiability.eval_at_dict( + var_mapping[new_param], + old_param_spec, + ) for new_param in new_params + ) + new_var_ic = Dict( + new_var => StructuralIdentifiability.eval_at_dict( + var_mapping[new_var], + merge(old_param_spec, old_var_ic), + ) for new_var in new_vars + ) + new_input_ts = Dict{eltype(new_vars), Vector{Nemo.fmpq}}( + new_input => old_input_ts[numerator(var_mapping[new_input])] for + new_input in new_inputs + ) - # NOTE: test that y's are mapped one to one! - @test map(string, [var_mapping[output] for output in new_ode.y_vars]) ≡ map(string, old_ode.y_vars) + new_solutions = StructuralIdentifiability.power_series_solution( + new_ode, + new_param_spec, + new_var_ic, + new_input_ts, + ord, + ) - # Test IO behavior - for var in vcat(new_ode.y_vars) - new_solution = new_solutions[var] - old_solution = old_solutions[numerator(var_mapping[var])] - @test new_solution == old_solution - end + # NOTE: test that y's are mapped one to one! + @test map(string, [var_mapping[output] for output in new_ode.y_vars]) ≡ map(string, old_ode.y_vars) - # Test state dynamics - projected_solutions = Dict( - var => StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) - for var in new_ode.x_vars - ) - for var in vcat(new_ode.x_vars) - new_solution = new_solutions[var] - prj_solution = projected_solutions[var] - @test new_solution == prj_solution - end + # Test IO behavior + for var in vcat(new_ode.y_vars) + new_solution = new_solutions[var] + old_solution = old_solutions[numerator(var_mapping[var])] + @test new_solution == old_solution + end - # Test relations - for relation in implicit_relations - @test iszero(StructuralIdentifiability.eval_at_dict(relation, var_mapping)) - end + # Test state dynamics + projected_solutions = Dict( + var => + StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) + for var in new_ode.x_vars + ) + for var in vcat(new_ode.x_vars) + new_solution = new_solutions[var] + prj_solution = projected_solutions[var] + @test new_solution == prj_solution + end - return nothing -end + # Test relations + for relation in implicit_relations + @test iszero(StructuralIdentifiability.eval_at_dict(relation, var_mapping)) + end -cases = [ - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + a + u1(t), - x2'(t) = x2(t) + b + u2(t), - y1(t) = x1(t), - y2(t) = x2(t), + return nothing + end + + cases = [ + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + a + u1(t), + x2'(t) = x2(t) + b + u2(t), + y1(t) = x1(t), + y2(t) = x2(t), + ), ), - ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1, - x2'(t) = b * x2, - y(t) = x1 + x2 + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1, + x2'(t) = b * x2, + y(t) = x1 + x2 + ), ), - ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = (a + b) * x1 // x2 + a, - x2'(t) = x1 // (a + c) + c, - y(t) = x1 + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = (a + b) * x1 // x2 + a, + x2'(t) = x1 // (a + c) + c, + y(t) = x1 + ), ), - ), - ( - ode = StructuralIdentifiability.@ODEmodel( - T1'(t) = a * T1 - b * T1 * T2 + u(t), - T2'(t) = -c * T2 + d * T1 * T2, - y(t) = T1 + ( + ode = StructuralIdentifiability.@ODEmodel( + T1'(t) = a * T1 - b * T1 * T2 + u(t), + T2'(t) = -c * T2 + d * T1 * T2, + y(t) = T1 + ), ), - ), - ( - #= - Identifiable functions: - x1, Θ * x2 + ( + #= + Identifiable functions: + x1, Θ * x2 - Under - T1 = x1, - T2 = Θ * x2 + Under + T1 = x1, + T2 = Θ * x2 - Reparametrizes into: - T1' = T1 + T2, - T2' = 0, - y = T1 - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + Θ * x2(t), - x2'(t) = 0, - y(t) = x1(t) + Reparametrizes into: + T1' = T1 + T2, + T2' = 0, + y = T1 + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + Θ * x2(t), + x2'(t) = 0, + y(t) = x1(t) + ), ), - ), - ( - #= - Identifiable functions: - x1, α * x2, α * x3, α * C + ( + #= + Identifiable functions: + x1, α * x2, α * x3, α * C - Under - T1 = α * x2, - T2 = α * x3, - T3 = x1, - T4 = α * C + Under + T1 = α * x2, + T2 = α * x3, + T3 = x1, + T4 = α * C - Reparametrizes into: - T3'(t) = T1(t) - T2'(t) = T4 - T1'(t) = T2(t) - y(t) = T3(t) - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * x2(t), - x2'(t) = x3(t), - x3'(t) = C, - y(t) = x1(t) + Reparametrizes into: + T3'(t) = T1(t) + T2'(t) = T4 + T1'(t) = T2(t) + y(t) = T3(t) + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * x2(t), + x2'(t) = x3(t), + x3'(t) = C, + y(t) = x1(t) + ), ), - ), - ( - #= - Identifiable functions: - x1^2 + x2^2, α + ( + #= + Identifiable functions: + x1^2 + x2^2, α - Under - T1 = x1^2 + x2^2, - T2 = α + Under + T1 = x1^2 + x2^2, + T2 = α - Reparametrizes into: - T1' = 2*T1*T2 - y = 1//2*T1 - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * (x1 - x2), - x2'(t) = α * (x1 + x2), - y(t) = (x1^2 + x2^2) // 2, + Reparametrizes into: + T1' = 2*T1*T2 + y = 1//2*T1 + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * (x1 - x2), + x2'(t) = α * (x1 + x2), + y(t) = (x1^2 + x2^2) // 2, + ), ), - ), - ( - # Pivastatin - # Reparametrizes into a 4-dimensional nonlinear model with no algebraic - # relations -- how is this even legal?? - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), - x2'(t) = k1 * x1(t) * (T0 - x2(t)) - (r1 + k2) * x2(t), - x3'(t) = r3 * x1(t) - (k3 + k4) * x3(t) + k2 * x2(t), - y1(t) = k * (x2(t) + x3(t)) + ( + # Pivastatin + # Reparametrizes into a 4-dimensional nonlinear model with no algebraic + # relations -- how is this even legal?? + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = + k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), + x2'(t) = k1 * x1(t) * (T0 - x2(t)) - (r1 + k2) * x2(t), + x3'(t) = r3 * x1(t) - (k3 + k4) * x3(t) + k2 * x2(t), + y1(t) = k * (x2(t) + x3(t)) + ), ), - ), - ( - # Transfection_4State - ode = StructuralIdentifiability.@ODEmodel( - mRNA'(t) = -d1 * mRNA(t) - d2 * mRNA(t) * enz(t), - GFP'(t) = kTL * mRNA(t) - b * GFP(t), - enz'(t) = d3 * mRNAenz(t) - d2 * mRNA(t) * enz(t), - mRNAenz'(t) = -d3 * mRNAenz(t) + d2 * mRNA(t) * enz(t), - y1(t) = GFP(t) + ( + # Transfection_4State + ode = StructuralIdentifiability.@ODEmodel( + mRNA'(t) = -d1 * mRNA(t) - d2 * mRNA(t) * enz(t), + GFP'(t) = kTL * mRNA(t) - b * GFP(t), + enz'(t) = d3 * mRNAenz(t) - d2 * mRNA(t) * enz(t), + mRNAenz'(t) = -d3 * mRNAenz(t) + d2 * mRNA(t) * enz(t), + y1(t) = GFP(t) + ), ), - ), - ( - # LLW1987_io - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -p1 * x1(t) + p2 * u(t), - x2'(t) = -p3 * x2(t) + p4 * u(t), - x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), - y1(t) = x3(t) + ( + # LLW1987_io + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -p1 * x1(t) + p2 * u(t), + x2'(t) = -p3 * x2(t) + p4 * u(t), + x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), + y1(t) = x3(t) + ), ), - ), - ( - # Take care of monomial orders in GB! - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1 + x2^2 + a^2, - x2'(t) = x2 + a * d^3, - y(t) = x1 + ( + # Take care of monomial orders in GB! + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1 + x2^2 + a^2, + x2'(t) = x2 + a * d^3, + y(t) = x1 + ), ), - ), - ( - # SLIQR system - ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -b * In(t) * S(t) * Ninv - u(t) * S(t) * Ninv, - L'(t) = b * In(t) * S(t) * Ninv - a * L(t), - In'(t) = a * L(t) - g * In(t) + s * Q(t), - Q'(t) = (1 - e) * g * In(t) - s * Q(t), - y(t) = In(t) * Ninv + ( + # SLIQR system + ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -b * In(t) * S(t) * Ninv - u(t) * S(t) * Ninv, + L'(t) = b * In(t) * S(t) * Ninv - a * L(t), + In'(t) = a * L(t) - g * In(t) + s * Q(t), + Q'(t) = (1 - e) * g * In(t) - s * Q(t), + y(t) = In(t) * Ninv + ), ), - ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1 + b * x2 + u(t), - x2'(t) = b * x1 + c * x2, - y(t) = x1 + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1 + b * x2 + u(t), + x2'(t) = b * x1 + c * x2, + y(t) = x1 + ), ), - ), -] - -@testset "Global reparametrizations" begin - # Test that variables are mapped properly - ode = cases[1].ode - (new_ode, new_vars, implicit_relations) = - StructuralIdentifiability.reparametrize_global(ode) - @test length(new_vars) == length(gens(parent(new_ode))) == 8 - @test length(new_ode.x_vars) == 2 - # @test map(string, new_ode.y_vars) ≡ map(string, ode.y_vars) - # @test map(string, new_ode.u_vars) ≡ map(string, ode.u_vars) - @test length(new_ode.parameters) == 2 - @test isempty(implicit_relations) + ] - # Test that relations are functional - ode = cases[2].ode - (new_ode, new_vars, implicit_relations) = - StructuralIdentifiability.reparametrize_global(ode) - @test length(implicit_relations) == 1 + @testset "Global reparametrizations" begin + # Test that variables are mapped properly + ode = cases[1].ode + (new_ode, new_vars, implicit_relations) = + StructuralIdentifiability.reparametrize_global(ode) + @test length(new_vars) == length(gens(parent(new_ode))) == 8 + @test length(new_ode.x_vars) == 2 + # @test map(string, new_ode.y_vars) ≡ map(string, ode.y_vars) + # @test map(string, new_ode.u_vars) ≡ map(string, ode.u_vars) + @test length(new_ode.parameters) == 2 + @test isempty(implicit_relations) - for case in cases - ode = case.ode + # Test that relations are functional + ode = cases[2].ode (new_ode, new_vars, implicit_relations) = StructuralIdentifiability.reparametrize_global(ode) - test_reparametrization(ode, new_ode, new_vars, implicit_relations) + @test length(implicit_relations) == 1 + + for case in cases + ode = case.ode + (new_ode, new_vars, implicit_relations) = + StructuralIdentifiability.reparametrize_global(ode) + test_reparametrization(ode, new_ode, new_vars, implicit_relations) + end end end diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index 96b78ff9f..97fd4ac07 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -1,49 +1,55 @@ -@testset "Parent ring change" begin - for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] - R, (z, x, y) = PolynomialRing(QQ, ["z", "x", "y"], ordering = :degrevlex) - R_, (y_, x_) = PolynomialRing(QQ, ["y", "x"], ordering = :lex) - R__, (x__, t__, y__, z__) = - PolynomialRing(QQ, ["x", "t", "y", "z"], ordering = :deglex) +if GROUP == "All" || GROUP == "Core" + @testset "Parent ring change" begin + for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] + R, (z, x, y) = PolynomialRing(QQ, ["z", "x", "y"], ordering = :degrevlex) + R_, (y_, x_) = PolynomialRing(QQ, ["y", "x"], ordering = :lex) + R__, (x__, t__, y__, z__) = + PolynomialRing(QQ, ["x", "t", "y", "z"], ordering = :deglex) - f = 2x + 3y + x^7 * y - @test f == StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) - @test f == StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) + f = 2x + 3y + x^7 * y + @test f == + StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) + @test f == + StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) - f_ = StructuralIdentifiability.parent_ring_change(f, R_, matching = :byname) - f__ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) - @test f_ == 2x_ + 3y_ + x_^7 * y_ - @test f__ == 2x__ + 3y__ + x__^7 * y__ - @test f == StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) - @test f == StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) + f_ = StructuralIdentifiability.parent_ring_change(f, R_, matching = :byname) + f__ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) + @test f_ == 2x_ + 3y_ + x_^7 * y_ + @test f__ == 2x__ + 3y__ + x__^7 * y__ + @test f == + StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) + @test f == + StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) - @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( - x + z, - R_, - matching = :byname, - ) + @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( + x + z, + R_, + matching = :byname, + ) - f = 3y - f_ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) - @test f_ == 3y__ + f = 3y + f_ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) + @test f_ == 3y__ - f__ = 2x__ + 5t__^3 + 3y__^2 - f = StructuralIdentifiability.parent_ring_change(f__, R, matching = :byindex) - @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( - f__, - R_, - matching = :byindex, - ) - @test f == 2z + 5x^3 + 3y^2 - @test f__ == - StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) + f__ = 2x__ + 5t__^3 + 3y__^2 + f = StructuralIdentifiability.parent_ring_change(f__, R, matching = :byindex) + @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( + f__, + R_, + matching = :byindex, + ) + @test f == 2z + 5x^3 + 3y^2 + @test f__ == + StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) - R, (x,) = PolynomialRing(field, ["x"]) - R2, (x2,) = PolynomialRing(Nemo.FractionField(R), ["x"]) - R3, (x3,) = PolynomialRing(field, ["x"]) - f = x3^2 - f_ = StructuralIdentifiability.parent_ring_change(f, R2) - @test parent(f) == R3 - @test parent(f_) == R2 - @test repr(f_) == "x^2" + R, (x,) = PolynomialRing(field, ["x"]) + R2, (x2,) = PolynomialRing(Nemo.FractionField(R), ["x"]) + R3, (x3,) = PolynomialRing(field, ["x"]) + f = x3^2 + f_ = StructuralIdentifiability.parent_ring_change(f, R2) + @test parent(f) == R3 + @test parent(f_) == R2 + @test repr(f_) == "x^2" + end end end diff --git a/test/pb_representation.jl b/test/pb_representation.jl index 35a92f872..ad89fcf83 100644 --- a/test/pb_representation.jl +++ b/test/pb_representation.jl @@ -1,17 +1,24 @@ -@testset "PB-representation - creation" begin - ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - @test pbr.profile == Dict("y(t)" => 2) - @test pbr.u_names == Array{String, 1}() - @test pbr.y_names == ["y(t)"] - @test pbr.param_names == ["a"] +if GROUP == "All" || GROUP == "Core" + @testset "PB-representation - creation" begin + ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + @test pbr.profile == Dict("y(t)" => 2) + @test pbr.u_names == Array{String, 1}() + @test pbr.y_names == ["y(t)"] + @test pbr.param_names == ["a"] - ode = @ODEmodel(x1'(t) = x1(t), x2'(t) = a * x2(t), y(t) = x1(t), y2(t) = x2(t) + u(t)) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - @test pbr.profile == Dict("y(t)" => 1, "y2(t)" => 1) - @test pbr.u_names == ["u(t)"] - @test pbr.y_names == ["y(t)", "y2(t)"] - @test pbr.param_names == ["a"] + ode = @ODEmodel( + x1'(t) = x1(t), + x2'(t) = a * x2(t), + y(t) = x1(t), + y2(t) = x2(t) + u(t) + ) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + @test pbr.profile == Dict("y(t)" => 1, "y2(t)" => 1) + @test pbr.u_names == ["u(t)"] + @test pbr.y_names == ["y(t)", "y2(t)"] + @test pbr.param_names == ["a"] + end end diff --git a/test/ps_diff.jl b/test/ps_diff.jl index 99f56801a..c0c1795dc 100644 --- a/test/ps_diff.jl +++ b/test/ps_diff.jl @@ -1,13 +1,16 @@ -@testset "Power Series Differentiation" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Power Series Differentiation" begin + T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) - @test ps_diff(zero(T)) == zero(T) + @test ps_diff(zero(T)) == zero(T) - @test ps_diff(one(T)) == zero(T) + @test ps_diff(one(T)) == zero(T) - @test ps_diff(gen(T)) == one(T) + @test ps_diff(gen(T)) == one(T) - @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 + @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == + 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 - @test ps_diff((1 + t)^1000) == truncate(1000 * (1 + t)^999, 299) + @test ps_diff((1 + t)^1000) == truncate(1000 * (1 + t)^999, 299) + end end diff --git a/test/ps_integrate.jl b/test/ps_integrate.jl index 53b1968ef..cca7e34f3 100644 --- a/test/ps_integrate.jl +++ b/test/ps_integrate.jl @@ -1,15 +1,17 @@ -@testset "Power series integration" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Power series integration" begin + T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) - @test ps_integrate(zero(T)) == zero(T) + @test ps_integrate(zero(T)) == zero(T) - @test ps_integrate(one(T)) == gen(T) + @test ps_integrate(one(T)) == gen(T) - @test ps_integrate(gen(T)) == gen(T)^2 * (1 // 2) + @test ps_integrate(gen(T)) == gen(T)^2 * (1 // 2) - @test ps_integrate(t^3 - t^4 * (1 // 8) + 5 * t^10) == - t^4 * (1 // 4) - t^5 * (1 // 40) + t^11 * (5 // 11) + @test ps_integrate(t^3 - t^4 * (1 // 8) + 5 * t^10) == + t^4 * (1 // 4) - t^5 * (1 // 40) + t^11 * (5 // 11) - @test ps_integrate((1 + t)^1000) == - truncate((1 + t)^1001 * (1 // 1001) - 1 // 1001, 300) + @test ps_integrate((1 + t)^1000) == + truncate((1 + t)^1001 * (1 // 1001) - 1 // 1001, 300) + end end diff --git a/test/ps_inverse.jl b/test/ps_inverse.jl index 63acafad9..282e9bf07 100644 --- a/test/ps_inverse.jl +++ b/test/ps_inverse.jl @@ -1,21 +1,28 @@ -@testset "Power series matrix inverse" begin - T, t = Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 50, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Power series matrix inverse" begin + T, t = Nemo.PowerSeriesRing( + Nemo.Native.GF(2^31 - 1), + 50, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - S = Nemo.MatrixSpace(T, d, d) - for case in 1:20 - M = S([random_ps(T) for i in 1:d, j in 1:d]) - while isequal( - StructuralIdentifiability.LinearAlgebra.det( - StructuralIdentifiability.ps_matrix_const_term(M), - ), - 0, - ) + for d in 1:5 + S = Nemo.MatrixSpace(T, d, d) + for case in 1:20 M = S([random_ps(T) for i in 1:d, j in 1:d]) + while isequal( + StructuralIdentifiability.LinearAlgebra.det( + StructuralIdentifiability.ps_matrix_const_term(M), + ), + 0, + ) + M = S([random_ps(T) for i in 1:d, j in 1:d]) + end + invM = ps_matrix_inv(M) + prod = invM * M + @test prod == one(S) end - invM = ps_matrix_inv(M) - prod = invM * M - @test prod == one(S) end end end diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index c9925fef2..be2aebaf7 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,19 +1,25 @@ -@testset "Homogeneous linear differential equations" begin - T, t = - Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Homogeneous linear differential equations" begin + T, t = Nemo.PowerSeriesRing( + Nemo.Native.GF(2^31 - 1), + 300, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - for c in 1:5 - S = Nemo.MatrixSpace(T, d, d) - A = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) - Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) - while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 + for d in 1:5 + for c in 1:5 + S = Nemo.MatrixSpace(T, d, d) + A = random_ps_matrix(T, S) + Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) + while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 + Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) + end + @time sol, sol_inv = ps_matrix_homlinear_de(A, Y0) + to_be_zero = map(ps_diff, sol) - A * sol + @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) end - @time sol, sol_inv = ps_matrix_homlinear_de(A, Y0) - to_be_zero = map(ps_diff, sol) - A * sol - @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) end end end diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index c18fedf44..31d5a2822 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,17 +1,23 @@ -@testset "Linear differential equations" begin - T, t = - Nemo.PowerSeriesRing(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Linear differential equations" begin + T, t = Nemo.PowerSeriesRing( + Nemo.Native.GF(2^31 - 1), + 300, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - for c in 1:5 - S = Nemo.MatrixSpace(T, d, d) - A = random_ps_matrix(T, S) - B = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) - Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) - @time sol = ps_matrix_linear_de(A, B, Y0) - to_be_zero = map(ps_diff, sol) - A * sol - B - @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) + for d in 1:5 + for c in 1:5 + S = Nemo.MatrixSpace(T, d, d) + A = random_ps_matrix(T, S) + B = random_ps_matrix(T, S) + Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) + Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) + @time sol = ps_matrix_linear_de(A, B, Y0) + to_be_zero = map(ps_diff, sol) - A * sol - B + @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) + end end end end diff --git a/test/ps_matrix_log.jl b/test/ps_matrix_log.jl index 5aa089c3f..b4a27aa6d 100644 --- a/test/ps_matrix_log.jl +++ b/test/ps_matrix_log.jl @@ -1,42 +1,44 @@ -@testset "Logarith of power series matrices" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) +if GROUP == "All" || GROUP == "Core" + @testset "Logarith of power series matrices" begin + T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) - for d in 1:5 - diag_elements = [1 + t * random_ps(T) for i in 1:d] - S = Nemo.MatrixSpace(T, d, d) - M = S([(i == j ? diag_elements[i] : T(0)) for i in 1:d, j in 1:d]) - result = ps_matrix_log(M) - correct = S([(i == j ? log(diag_elements[i]) : T(0)) for i in 1:d, j in 1:d]) - @test result == correct - end + for d in 1:5 + diag_elements = [1 + t * random_ps(T) for i in 1:d] + S = Nemo.MatrixSpace(T, d, d) + M = S([(i == j ? diag_elements[i] : T(0)) for i in 1:d, j in 1:d]) + result = ps_matrix_log(M) + correct = S([(i == j ? log(diag_elements[i]) : T(0)) for i in 1:d, j in 1:d]) + @test result == correct + end - for c in 1:5 - f = random_ps(T) * t - S = Nemo.MatrixSpace(T, 2, 2) - m = S([ - T(1) f - T(0) T(1) - ]) - correct = S([ - T(0) f - T(0) T(0) - ]) - @test ps_matrix_log(m) == correct - end + for c in 1:5 + f = random_ps(T) * t + S = Nemo.MatrixSpace(T, 2, 2) + m = S([ + T(1) f + T(0) T(1) + ]) + correct = S([ + T(0) f + T(0) T(0) + ]) + @test ps_matrix_log(m) == correct + end - for c in 1:5 - f, g = random_ps(T) * t, random_ps(T) * t - S = Nemo.MatrixSpace(T, 3, 3) - m = S([ - T(1) f g - T(0) T(1) f - T(0) T(0) T(1) - ]) - correct = S([ - T(0) f (-1 // 2) * f^2+g - T(0) T(0) f - T(0) T(0) T(0) - ]) - @test ps_matrix_log(m) == correct + for c in 1:5 + f, g = random_ps(T) * t, random_ps(T) * t + S = Nemo.MatrixSpace(T, 3, 3) + m = S([ + T(1) f g + T(0) T(1) f + T(0) T(0) T(1) + ]) + correct = S([ + T(0) f (-1 // 2) * f^2+g + T(0) T(0) f + T(0) T(0) T(0) + ]) + @test ps_matrix_log(m) == correct + end end end diff --git a/test/pseudodivision.jl b/test/pseudodivision.jl index 7c9c5d542..9fdf157a5 100644 --- a/test/pseudodivision.jl +++ b/test/pseudodivision.jl @@ -1,6 +1,8 @@ -@testset "Pseudodivision" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) +if GROUP == "All" || GROUP == "Core" + @testset "Pseudodivision" begin + R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) - @test iszero(pseudodivision(x^2 - y^2, x - y, x)) - @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x + @test iszero(pseudodivision(x^2 - y^2, x - y, x)) + @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x + end end diff --git a/test/reduce_ode_mod_p.jl b/test/reduce_ode_mod_p.jl index b81b57bb0..55fff7b4a 100644 --- a/test/reduce_ode_mod_p.jl +++ b/test/reduce_ode_mod_p.jl @@ -1,14 +1,16 @@ -@testset "Reducing ODE mod p" begin - ode = @ODEmodel( - x'(t) = 1 // 2 * x(t) - 5 * y(t), - y'(t) = (3 // 5 * x(t) + 17 * y(t)) // (y(t) - 1), - z(t) = y(t) - ) +if GROUP == "All" || GROUP == "Core" + @testset "Reducing ODE mod p" begin + ode = @ODEmodel( + x'(t) = 1 // 2 * x(t) - 5 * y(t), + y'(t) = (3 // 5 * x(t) + 17 * y(t)) // (y(t) - 1), + z(t) = y(t) + ) - ode_red = reduce_ode_mod_p(ode, 17) + ode_red = reduce_ode_mod_p(ode, 17) - x = str_to_var("x(t)", ode_red.poly_ring) - y = str_to_var("y(t)", ode_red.poly_ring) - @test ode_red.x_equations[x] == 9 * x + 12 * y - @test ode_red.x_equations[y] == 4 * x // (y + 16) + x = str_to_var("x(t)", ode_red.poly_ring) + y = str_to_var("y(t)", ode_red.poly_ring) + @test ode_red.x_equations[x] == 9 * x + 12 * y + @test ode_red.x_equations[y] == 4 * x // (y + 16) + end end diff --git a/test/sequence_solution.jl b/test/sequence_solution.jl index 5a21b7956..128a5c7df 100644 --- a/test/sequence_solution.jl +++ b/test/sequence_solution.jl @@ -1,25 +1,27 @@ -@testset "Sequence solutions in the discrete case" begin - # Fibonacci example - ode = @ODEmodel(f1'(t) = f1(t) + f0(t), f0'(t) = f1(t), y(t) = f1(t)) +if GROUP == "All" || GROUP == "Core" + @testset "Sequence solutions in the discrete case" begin + # Fibonacci example + ode = @ODEmodel(f1'(t) = f1(t) + f0(t), f0'(t) = f1(t), y(t) = f1(t)) - sol = sequence_solution( - ode, - Dict{fmpq_mpoly, Int}(), - Dict(f0 => 1, f1 => 1), - Dict{fmpq_mpoly, Array{Int, 1}}(), - 10, - ) - @test sol[f1] == [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - @test sol[f0] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + sol = sequence_solution( + ode, + Dict{fmpq_mpoly, Int}(), + Dict(f0 => 1, f1 => 1), + Dict{fmpq_mpoly, Array{Int, 1}}(), + 10, + ) + @test sol[f1] == [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + @test sol[f0] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - # Sum of powers - ode = @ODEmodel(a'(t) = 2 * a(t) + b * u(t), y(t) = a(t)) - sol = sequence_solution( - ode, - Dict(b => 3), - Dict(a => 1), - Dict(u => [3^i for i in 0:9]), - 10, - ) - @test sol[a] == [-2^i + 3^i for i in 1:10] + # Sum of powers + ode = @ODEmodel(a'(t) = 2 * a(t) + b * u(t), y(t) = a(t)) + sol = sequence_solution( + ode, + Dict(b => 3), + Dict(a => 1), + Dict(u => [3^i for i in 0:9]), + 10, + ) + @test sol[a] == [-2^i + 3^i for i in 1:10] + end end diff --git a/test/set_parameter_values.jl b/test/set_parameter_values.jl index 046e4a817..5c2d26cac 100644 --- a/test/set_parameter_values.jl +++ b/test/set_parameter_values.jl @@ -1,19 +1,21 @@ -@testset "Set parameter values" begin - sys = @ODEmodel(x'(t) = a * x(t) + a^2, y(t) = x(t)) +if GROUP == "All" || GROUP == "Core" + @testset "Set parameter values" begin + sys = @ODEmodel(x'(t) = a * x(t) + a^2, y(t) = x(t)) - new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 2))) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(5, 4) + new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 2))) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(5, 4) - new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 20))) - @test evaluate(first(values(new_sys.x_equations)), [5, 5]) == Nemo.QQ(101, 400) + new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 20))) + @test evaluate(first(values(new_sys.x_equations)), [5, 5]) == Nemo.QQ(101, 400) - new_sys = set_parameter_values(sys, Dict(a => 0.1)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(21, 100) + new_sys = set_parameter_values(sys, Dict(a => 0.1)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(21, 100) - new_sys = set_parameter_values(sys, Dict(a => 2.1)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(861, 100) + new_sys = set_parameter_values(sys, Dict(a => 2.1)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(861, 100) - sys = @ODEmodel(x'(t) = a + b + c, y(t) = x(t)) - new_sys = set_parameter_values(sys, Dict(a => 2.1, b => 5, c => 3 // 10)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(74, 10) + sys = @ODEmodel(x'(t) = a + b + c, y(t) = x(t)) + new_sys = set_parameter_values(sys, Dict(a => 2.1, b => 5, c => 3 // 10)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(74, 10) + end end diff --git a/test/state_generators.jl b/test/state_generators.jl index b9ece3e48..9072f0de4 100644 --- a/test/state_generators.jl +++ b/test/state_generators.jl @@ -1,32 +1,34 @@ -@testset "Generators of observable states" begin - ode = @ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) - ) +if GROUP == "All" || GROUP == "Core" + @testset "Generators of observable states" begin + ode = @ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) + ) - sg = states_generators(ode, find_ioequations(ode)) - @test sg == [ - x1 // 1, - (a * x1 - b * x1 * x2) // 1, - ((a - b * x2) * (a * x1 - b * x1 * x2) - b * x1 * (-c * x2 + d * x1 * x2)) // 1, - ] + sg = states_generators(ode, find_ioequations(ode)) + @test sg == [ + x1 // 1, + (a * x1 - b * x1 * x2) // 1, + ((a - b * x2) * (a * x1 - b * x1 * x2) - b * x1 * (-c * x2 + d * x1 * x2)) // 1, + ] - ode = @ODEmodel( - x1'(t) = x2(t), - x2'(t) = x3(t), - x3'(t) = x4(t), - x4'(t) = 1 // x2(t), - y(t) = x1(t) - ) - sg = states_generators(ode, find_ioequations(ode)) - @test sg == [x1 // 1, x2 // 1, x3 // 1, x4 // 1, x2 // 1] + ode = @ODEmodel( + x1'(t) = x2(t), + x2'(t) = x3(t), + x3'(t) = x4(t), + x4'(t) = 1 // x2(t), + y(t) = x1(t) + ) + sg = states_generators(ode, find_ioequations(ode)) + @test sg == [x1 // 1, x2 // 1, x3 // 1, x4 // 1, x2 // 1] - ode = @ODEmodel( - x1'(t) = x2(t), - x2'(t) = (x1(t) + a * x2(t) * u(t)) // (u(t)^2 + x1(t)), - y(t) = x1(t) - ) - sg = states_generators(ode, find_ioequations(ode)) - @test Set(sg) == Set([x1 // 1, x2 // 1, x1 // 1, a * x2 // 1, x1 // 1]) + ode = @ODEmodel( + x1'(t) = x2(t), + x2'(t) = (x1(t) + a * x2(t) * u(t)) // (u(t)^2 + x1(t)), + y(t) = x1(t) + ) + sg = states_generators(ode, find_ioequations(ode)) + @test Set(sg) == Set([x1 // 1, x2 // 1, x1 // 1, a * x2 // 1, x1 // 1]) + end end diff --git a/test/submodels.jl b/test/submodels.jl index 782889cb4..8fcef809f 100644 --- a/test/submodels.jl +++ b/test/submodels.jl @@ -1,146 +1,148 @@ -@testset "Finding submodels" begin - cases = [ - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t)^2, - x2'(t) = x1(t) * x2(t), - y1(t) = x1(t), - y2(t) = x2(t) - ), - :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), - ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t), - x2'(t) = x2(t) + x1(t), - y1(t) = x1(t), - y2(t) = x1(t)^2, - y3(t) = x2(t) - ), - :submodels => Set([(["x1(t)"], ["y1(t)", "y2(t)"], Vector{String}())]), - ), - Dict( - :ode => @ODEmodel( - x1'(t) = 0, - x2'(t) = 0, - x3'(t) = 0, - y1(t) = x1(t) + x2(t), - y2(t) = x2(t) + x3(t), - y3(t) = x1(t) + x3(t) +if GROUP == "All" || GROUP == "Core" + @testset "Finding submodels" begin + cases = [ + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t)^2, + x2'(t) = x1(t) * x2(t), + y1(t) = x1(t), + y2(t) = x2(t) + ), + :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), ), - :submodels => Set([ - (["x1(t)", "x2(t)"], ["y1(t)"], Vector{String}()), - (["x2(t)", "x3(t)"], ["y2(t)"], Vector{String}()), - (["x1(t)", "x3(t)"], ["y3(t)"], Vector{String}()), - ]), - ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t)^2, - x2'(t) = x1(t) / x2(t), - y1(t) = x1(t), - y2(t) = x2(t) + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t), + x2'(t) = x2(t) + x1(t), + y1(t) = x1(t), + y2(t) = x1(t)^2, + y3(t) = x2(t) + ), + :submodels => Set([(["x1(t)"], ["y1(t)", "y2(t)"], Vector{String}())]), ), - :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), - ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t) + a(t) * x2(t)^2, - x2'(t) = x2(t) - b(t) * x1(t) * x2(t), - x3'(t) = x3(t) + x1(t) + x2(t), - y1(t) = d(t) * x1(t), - y2(t) = x3(t) + Dict( + :ode => @ODEmodel( + x1'(t) = 0, + x2'(t) = 0, + x3'(t) = 0, + y1(t) = x1(t) + x2(t), + y2(t) = x2(t) + x3(t), + y3(t) = x1(t) + x3(t) + ), + :submodels => Set([ + (["x1(t)", "x2(t)"], ["y1(t)"], Vector{String}()), + (["x2(t)", "x3(t)"], ["y2(t)"], Vector{String}()), + (["x1(t)", "x3(t)"], ["y3(t)"], Vector{String}()), + ]), ), - :submodels => - Set([(["x1(t)", "x2(t)"], ["y1(t)"], ["a(t)", "b(t)", "d(t)"])]), - ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t), - x2'(t) = x1(t) - c * a(t) * x2(t) * x2(t), - x3'(t) = x3(t) - x1(t) * x2(t) * x3(t), - y1(t) = x1(t), - y2(t) = x1(t) + d(t) * x2(t), - y3(t) = x3(t) + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t)^2, + x2'(t) = x1(t) / x2(t), + y1(t) = x1(t), + y2(t) = x2(t) + ), + :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), ), - :submodels => Set([ - (["x1(t)", "x2(t)"], ["y1(t)", "y2(t)"], ["a(t)", "d(t)"]), - (["x1(t)"], ["y1(t)"], Vector{String}()), - ]), - ), - Dict( - :ode => @ODEmodel( - o1'(t) = r1 * o1(t), - x0'(t) = o1(t) - d * x0(t), - x1'(t) = x0(t) - x1(t), - x2'(t) = x1(t) - x2(t), - x3'(t) = x2(t) - x3(t), - x4'(t) = x3(t) - x4(t), - x5'(t) = x4(t) - x5(t), - o12'(t) = r2 * o12(t), - x02'(t) = o12(t) + x2(t) + x3(t) + x22(t) + x32(t) + x02(t), - x12'(t) = x2(t) + x3(t) + x22(t) + x32(t) + x02(t) - x12(t), - x22'(t) = x12(t) - x22(t), - x32'(t) = x22(t) - x32(t), - x42'(t) = x32(t) - x42(t), - x52'(t) = x42(t) - x52(t), - y0(t) = x0(t) + x02(t), - y1(t) = x1(t) + x12(t), - y2(t) = x2(t) + x22(t), - y3(t) = x3(t) + x32(t), - y4(t) = x4(t) + x42(t) + x5(t) + x52(t), - y5(t) = - x1(t) + x2(t) + x3(t) + x4(t) + x12(t) + x22(t) + x32(t) + x42(t), - y6(t) = x5(t) + x52(t) + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t) + a(t) * x2(t)^2, + x2'(t) = x2(t) - b(t) * x1(t) * x2(t), + x3'(t) = x3(t) + x1(t) + x2(t), + y1(t) = d(t) * x1(t), + y2(t) = x3(t) + ), + :submodels => + Set([(["x1(t)", "x2(t)"], ["y1(t)"], ["a(t)", "b(t)", "d(t)"])]), ), - :submodels => Set([ - ( - [ - "o1(t)", - "x0(t)", - "x1(t)", - "x2(t)", - "x3(t)", - "o12(t)", - "x02(t)", - "x12(t)", - "x22(t)", - "x32(t)", - ], - ["y0(t)", "y1(t)", "y2(t)", "y3(t)"], - Vector{String}(), + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t), + x2'(t) = x1(t) - c * a(t) * x2(t) * x2(t), + x3'(t) = x3(t) - x1(t) * x2(t) * x3(t), + y1(t) = x1(t), + y2(t) = x1(t) + d(t) * x2(t), + y3(t) = x3(t) ), - ( - [ - "o1(t)", - "x0(t)", - "x1(t)", - "x2(t)", - "x3(t)", - "x4(t)", - "o12(t)", - "x02(t)", - "x12(t)", - "x22(t)", - "x32(t)", - "x42(t)", - ], - ["y0(t)", "y1(t)", "y2(t)", "y3(t)", "y5(t)"], - Vector{String}(), + :submodels => Set([ + (["x1(t)", "x2(t)"], ["y1(t)", "y2(t)"], ["a(t)", "d(t)"]), + (["x1(t)"], ["y1(t)"], Vector{String}()), + ]), + ), + Dict( + :ode => @ODEmodel( + o1'(t) = r1 * o1(t), + x0'(t) = o1(t) - d * x0(t), + x1'(t) = x0(t) - x1(t), + x2'(t) = x1(t) - x2(t), + x3'(t) = x2(t) - x3(t), + x4'(t) = x3(t) - x4(t), + x5'(t) = x4(t) - x5(t), + o12'(t) = r2 * o12(t), + x02'(t) = o12(t) + x2(t) + x3(t) + x22(t) + x32(t) + x02(t), + x12'(t) = x2(t) + x3(t) + x22(t) + x32(t) + x02(t) - x12(t), + x22'(t) = x12(t) - x22(t), + x32'(t) = x22(t) - x32(t), + x42'(t) = x32(t) - x42(t), + x52'(t) = x42(t) - x52(t), + y0(t) = x0(t) + x02(t), + y1(t) = x1(t) + x12(t), + y2(t) = x2(t) + x22(t), + y3(t) = x3(t) + x32(t), + y4(t) = x4(t) + x42(t) + x5(t) + x52(t), + y5(t) = + x1(t) + x2(t) + x3(t) + x4(t) + x12(t) + x22(t) + x32(t) + x42(t), + y6(t) = x5(t) + x52(t) ), - ]), - ), - ] + :submodels => Set([ + ( + [ + "o1(t)", + "x0(t)", + "x1(t)", + "x2(t)", + "x3(t)", + "o12(t)", + "x02(t)", + "x12(t)", + "x22(t)", + "x32(t)", + ], + ["y0(t)", "y1(t)", "y2(t)", "y3(t)"], + Vector{String}(), + ), + ( + [ + "o1(t)", + "x0(t)", + "x1(t)", + "x2(t)", + "x3(t)", + "x4(t)", + "o12(t)", + "x02(t)", + "x12(t)", + "x22(t)", + "x32(t)", + "x42(t)", + ], + ["y0(t)", "y1(t)", "y2(t)", "y3(t)", "y5(t)"], + Vector{String}(), + ), + ]), + ), + ] - for c in cases - submodels = find_submodels(c[:ode]) - submodels = Set([ - ( - collect(map(var_to_str, ode.x_vars)), - collect(map(var_to_str, ode.y_vars)), - collect(map(var_to_str, ode.u_vars)), - ) for ode in submodels - ]) - @test submodels == c[:submodels] + for c in cases + submodels = find_submodels(c[:ode]) + submodels = Set([ + ( + collect(map(var_to_str, ode.x_vars)), + collect(map(var_to_str, ode.y_vars)), + collect(map(var_to_str, ode.u_vars)), + ) for ode in submodels + ]) + @test submodels == c[:submodels] + end end end From 5c7a3bb00accf9b05f205783bf0af959cc005ae6 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 10:43:20 +0100 Subject: [PATCH 38/82] fixing 1.6 sgafault --- Project.toml | 16 ++++++++-------- src/StructuralIdentifiability.jl | 12 ------------ 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Project.toml b/Project.toml index 12a9cdcfc..79077063a 100644 --- a/Project.toml +++ b/Project.toml @@ -23,14 +23,6 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" -[weakdeps] -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" - -[extensions] -ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] - [compat] AbstractAlgebra = "0.34.5, 0.35" BenchmarkTools = "1" @@ -55,6 +47,9 @@ TestSetExtensions = "2" TimerOutputs = "0.5" julia = "1.6, 1.7" +[extensions] +ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] + [extras] CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" @@ -65,3 +60,8 @@ TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] test = ["CPUSummary", "Test", "TestSetExtensions"] + +[weakdeps] +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index c3a5fd5ee..6ef80d5b0 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -168,18 +168,6 @@ function _assess_identifiability( return result end -@static if !isdefined(Base, :get_extension) - using Requires -end - -@static if !isdefined(Base, :get_extension) - function __init__() - @require ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" begin - include("../ext/ModelingToolkitExt.jl") - end - end -end - using PrecompileTools include("precompile.jl") From cbb6e94135e361ad437eb2457f90782ff0ec315a Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 11:42:43 +0100 Subject: [PATCH 39/82] enabling tests in the subfolders --- test/runtests.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index 001feacec..9ddf86609 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -85,4 +85,7 @@ const GROUP = get(ENV, "GROUP", "All") @time @testset "All the tests" verbose = true begin @includetests ARGS + include("RationalFunctionFields/RationalFunctionField.jl") + include("RationalFunctionFields/normalforms.jl") + include("extensions//modelingtoolkit.jl") end From 60149d04405cd8f22e22375541ccb3d278c46429 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 11:48:51 +0100 Subject: [PATCH 40/82] moving MTK installation --- test/extensions/modelingtoolkit.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index d02b4959c..cd36c95dc 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,10 +1,10 @@ @static if VERSION >= v"1.10.0" if GROUP == "All" || GROUP == "ModelingToolkitExt" - using Pkg - Pkg.add("ModelingToolkit") - using ModelingToolkit - @testset "Check identifiability of `ODESystem` object" begin + using Pkg + Pkg.add("ModelingToolkit") + using ModelingToolkit + @parameters a01 a21 a12 @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) From 95bcc418d245a2d646d58ab0bc310248331a17bf Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 15:26:34 +0100 Subject: [PATCH 41/82] correct imports in the extension --- ext/ModelingToolkitExt.jl | 32 ++++++++++++++++++++---------- test/extensions/modelingtoolkit.jl | 6 ++++-- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitExt.jl index 15c6777dc..aefe97aa6 100644 --- a/ext/ModelingToolkitExt.jl +++ b/ext/ModelingToolkitExt.jl @@ -1,11 +1,22 @@ module ModelingToolkitExt -if !isdefined(Base, :get_extension) +using DataStructures +using Logging +using Nemo +using Random +using StructuralIdentifiability +using StructuralIdentifiability: str_to_var, parent_ring_change, eval_at_dict +using StructuralIdentifiability: restart_logging, _si_logger, reset_timings, _to +using TimerOutputs + +if isdefined(Base, :get_extension) using ModelingToolkit else using ..ModelingToolkit end +export assess_local_identifiability, assess_identifiability, find_identifiable_functions + # ------------------------------------------------------------------------------ function eval_at_nemo(e::Num, vals::Dict) @@ -246,7 +257,7 @@ The result is correct with probability at least `prob_threshold`. `type` can be either `:SE` (single-experiment identifiability) or `:ME` (multi-experiment identifiability). The return value is a tuple consisting of the array of bools and the number of experiments to be performed. """ -function assess_local_identifiability( +function StructuralIdentifiability.assess_local_identifiability( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], @@ -298,7 +309,7 @@ end funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] if isequal(type, :SE) - result = _assess_local_identifiability( + result = StructuralIdentifiability._assess_local_identifiability( ode, funcs_to_check = funcs_to_check_, prob_threshold = prob_threshold, @@ -309,7 +320,7 @@ end OrderedDict(nemo2mtk[param] => result[param] for param in funcs_to_check_) return out_dict elseif isequal(type, :ME) - result, bd = _assess_local_identifiability( + result, bd = StructuralIdentifiability._assess_local_identifiability( ode, funcs_to_check = funcs_to_check_, prob_threshold = prob_threshold, @@ -355,7 +366,7 @@ function StructuralIdentifiability.assess_identifiability( end end -function StructuralIdentifiability._assess_identifiability( +function _assess_identifiability( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = [], @@ -372,7 +383,7 @@ function StructuralIdentifiability._assess_identifiability( end funcs_to_check_ = [eval_at_nemo(each, conversion) for each in funcs_to_check] - result = _assess_identifiability( + result = StructuralIdentifiability._assess_identifiability( ode, funcs_to_check = funcs_to_check_, prob_threshold = prob_threshold, @@ -424,7 +435,7 @@ function StructuralIdentifiability.assess_local_identifiability( end end -function StructuralIdentifiability._assess_local_identifiability( +function _assess_local_identifiability( dds::ModelingToolkit.DiscreteSystem; measured_quantities = Array{ModelingToolkit.Equation}[], funcs_to_check = Array{}[], @@ -470,7 +481,7 @@ function StructuralIdentifiability._assess_local_identifiability( funcs_to_check_ = [eval_at_nemo(x, conversion) for x in funcs_to_check] known_ic_ = [eval_at_nemo(x, conversion) for x in known_ic] - result = _assess_local_identifiability_discrete_aux( + result = StructuralIdentifiability._assess_local_identifiability_discrete_aux( dds_aux, funcs_to_check_, known_ic_, @@ -480,6 +491,7 @@ function StructuralIdentifiability._assess_local_identifiability( 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 !" + t = SymbolicUtils.Sym{Real}(:t) out_dict = OrderedDict(substitute(k, Dict(t => 0)) => v for (k, v) in out_dict) end return out_dict @@ -523,7 +535,7 @@ find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) a01 + a12 + a21 ``` """ -function find_identifiable_functions( +function StructuralIdentifiability.find_identifiable_functions( ode::ModelingToolkit.ODESystem; measured_quantities = Array{ModelingToolkit.Equation}[], prob_threshold::Float64 = 0.99, @@ -562,7 +574,7 @@ function _find_identifiable_functions( measured_quantities = get_measured_quantities(ode) end ode, conversion = mtk_to_si(ode, measured_quantities) - result = _find_identifiable_functions( + result = StructuralIdentifiability._find_identifiable_functions( ode, simplify = simplify, prob_threshold = prob_threshold, diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index cd36c95dc..bfafa363f 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,9 +1,11 @@ @static if VERSION >= v"1.10.0" if GROUP == "All" || GROUP == "ModelingToolkitExt" @testset "Check identifiability of `ODESystem` object" begin - using Pkg - Pkg.add("ModelingToolkit") + #using Pkg + #Pkg.add("ModelingToolkit") + #Pkg.add("Symbolics") using ModelingToolkit + using Symbolics @parameters a01 a21 a12 @variables t x0(t) x1(t) y1(t) [output = true] From 181804ecd568e74aae76e056f1329d9a146fca65 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 15:34:47 +0100 Subject: [PATCH 42/82] fixing MTK import in tests --- test/runtests.jl | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 9ddf86609..c7b9b3224 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -59,6 +59,18 @@ using StructuralIdentifiability: states_generators, RationalFunctionField +const GROUP = get(ENV, "GROUP", "All") + +@static if VERSION >= v"1.10.0" + if GROUP == "All" || GROUP == "ModelingToolkitExt" + using Pkg + Pkg.add("ModelingToolkit") + Pkg.add("Symbolics") + using ModelingToolkit + using Symbolics + end +end + function random_ps(ps_ring, range = 1000) result = zero(ps_ring) t = gen(ps_ring) @@ -76,8 +88,6 @@ function random_ps_matrix(ps_ring, matrix_space) return result end -const GROUP = get(ENV, "GROUP", "All") - @info "Testing started" @test isempty(Test.detect_ambiguities(StructuralIdentifiability)) From c929c11ef187c885507d9d1223d54a34d2044b62 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 15:45:33 +0100 Subject: [PATCH 43/82] pkg into the deps for testing --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 79077063a..44d9801fa 100644 --- a/Project.toml +++ b/Project.toml @@ -59,7 +59,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] -test = ["CPUSummary", "Test", "TestSetExtensions"] +test = ["CPUSummary", "Pkg", "Test", "TestSetExtensions"] [weakdeps] ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" From ccaffc6357ec89c11d5d931f621413a9299a2dc6 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 15:53:49 +0100 Subject: [PATCH 44/82] adding Pkg properly --- Project.toml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Project.toml b/Project.toml index 44d9801fa..e40bffc0b 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -23,6 +24,14 @@ Requires = "ae029012-a4dd-5104-9daa-d747884805df" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" +[weakdeps] +ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" + +[extensions] +ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] + [compat] AbstractAlgebra = "0.34.5, 0.35" BenchmarkTools = "1" @@ -47,9 +56,6 @@ TestSetExtensions = "2" TimerOutputs = "0.5" julia = "1.6, 1.7" -[extensions] -ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] - [extras] CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" @@ -60,8 +66,3 @@ TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] test = ["CPUSummary", "Pkg", "Test", "TestSetExtensions"] - -[weakdeps] -ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" -SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" -Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" From 89b7b1bf2cdaf5acee9145a93adeae63ddbc1f15 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 19 Jan 2024 15:56:07 +0100 Subject: [PATCH 45/82] tretiy raz zabrosil starik Pkg.jl --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e40bffc0b..d689bd411 100644 --- a/Project.toml +++ b/Project.toml @@ -16,7 +16,6 @@ Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -59,6 +58,7 @@ julia = "1.6, 1.7" [extras] CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" From 63abf26d420716d461c72f4b2318a5abc122932a Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Fri, 19 Jan 2024 21:26:39 +0100 Subject: [PATCH 46/82] lazy include --- test/runtests.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index c7b9b3224..970a2e371 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -97,5 +97,9 @@ end @includetests ARGS include("RationalFunctionFields/RationalFunctionField.jl") include("RationalFunctionFields/normalforms.jl") - include("extensions//modelingtoolkit.jl") + @static if VERSION >= v"1.10.0" + if GROUP == "All" || GROUP == "ModelingToolkitExt" + include("extensions//modelingtoolkit.jl") + end + end end From 46d221fc10ad455154ad162d30fe97ccafdcd4b2 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Fri, 19 Jan 2024 22:26:32 +0100 Subject: [PATCH 47/82] bump symbolics --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index d689bd411..e36a4513b 100644 --- a/Project.toml +++ b/Project.toml @@ -50,7 +50,7 @@ Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "1, 2" SymbolicUtils = "1.2, 1.3, 1.4, 1.5" -Symbolics = "5.5" +Symbolics = "5.16" TestSetExtensions = "2" TimerOutputs = "0.5" julia = "1.6, 1.7" From 8d6820a5f99dfad13e891f0344a6087bff2b6c0d Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Fri, 19 Jan 2024 23:39:25 +0100 Subject: [PATCH 48/82] bump SpecialFunctions --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e36a4513b..3b3f7a62a 100644 --- a/Project.toml +++ b/Project.toml @@ -48,7 +48,7 @@ ParamPunPam = "0.3.1" PrecompileTools = "1.1, 1.2" Primes = "0.5" Random = "1.6, 1.7" -SpecialFunctions = "1, 2" +SpecialFunctions = "2" SymbolicUtils = "1.2, 1.3, 1.4, 1.5" Symbolics = "5.16" TestSetExtensions = "2" From a56461d66905269d773fbaddb4ed2621b84d4b26 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 20 Jan 2024 12:16:43 +0100 Subject: [PATCH 49/82] bump MTK (still 0.01 gap between the latest version - diversity) --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3b3f7a62a..a4672d6a1 100644 --- a/Project.toml +++ b/Project.toml @@ -42,7 +42,7 @@ IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" -ModelingToolkit = "8.51" +ModelingToolkit = "8.74" Nemo = "0.38.3, 0.39" ParamPunPam = "0.3.1" PrecompileTools = "1.1, 1.2" From cdc9d43b1e88c67b3aca80e36751ac552eaae5c6 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 20 Jan 2024 15:02:41 +0100 Subject: [PATCH 50/82] bump PrecompileToold --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a4672d6a1..8d6cd2755 100644 --- a/Project.toml +++ b/Project.toml @@ -45,7 +45,7 @@ MacroTools = "0.5" ModelingToolkit = "8.74" Nemo = "0.38.3, 0.39" ParamPunPam = "0.3.1" -PrecompileTools = "1.1, 1.2" +PrecompileTools = "1.2" Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "2" From afb9b211b6df620fd75c7c14603edaca82ae25d5 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 20 Jan 2024 15:09:19 +0100 Subject: [PATCH 51/82] bump SymbolicUtils --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8d6cd2755..8513a0819 100644 --- a/Project.toml +++ b/Project.toml @@ -49,7 +49,7 @@ PrecompileTools = "1.2" Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "2" -SymbolicUtils = "1.2, 1.3, 1.4, 1.5" +SymbolicUtils = "1.4, 1.5" Symbolics = "5.16" TestSetExtensions = "2" TimerOutputs = "0.5" From 0f79ab91d6ac675a08e0c4ec9ae25c72b60ddb07 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 21 Jan 2024 12:36:57 +0100 Subject: [PATCH 52/82] fixing Nemo and AA depreciation warnings --- benchmarking/IdentifiableFunctions/param.jl | 22 +++---- ext/ModelingToolkitExt.jl | 4 +- src/ODE.jl | 68 +++++++++++---------- src/ODEexport.jl | 4 +- src/RationalFunctionFields/IdealMQS.jl | 12 ++-- src/StructuralIdentifiability.jl | 6 +- src/discrete.jl | 16 ++--- src/elimination.jl | 19 +++--- src/global_identifiability.jl | 18 +++--- src/identifiable_functions.jl | 6 +- src/io_equation.jl | 17 ++++-- src/lincomp.jl | 20 +++--- src/local_identifiability.jl | 16 ++--- src/parametrizations.jl | 11 ++-- src/pb_representation.jl | 22 +++---- src/power_series_utils.jl | 40 ++++++------ src/primality_check.jl | 18 +++--- src/states.jl | 10 +-- src/submodels.jl | 58 +++++++++--------- src/util.jl | 49 ++++++++------- src/wronskian.jl | 11 ++-- test/RationalFunctionFields/normalforms.jl | 2 +- test/check_field_membership.jl | 2 +- test/check_primality_zerodim.jl | 2 +- test/common_ring.jl | 4 +- test/constructive_membership.jl | 12 ++-- test/diff_sequence_solution.jl | 4 +- test/differentiate_output.jl | 16 ++--- test/diffreduction.jl | 8 +-- test/extract_coefficients.jl | 14 ++--- test/find_leader.jl | 2 +- test/io_cases.jl | 6 +- test/lc_univariate.jl | 2 +- test/linear_compartment.jl | 2 +- test/local_identifiability_me.jl | 17 +++--- test/monomial_compress.jl | 2 +- test/ode_ps_solution.jl | 12 ++-- test/paradigm_shift.jl | 5 +- test/parent_ring_change.jl | 12 ++-- test/pseudodivision.jl | 2 +- test/sequence_solution.jl | 4 +- 41 files changed, 302 insertions(+), 275 deletions(-) diff --git a/benchmarking/IdentifiableFunctions/param.jl b/benchmarking/IdentifiableFunctions/param.jl index 44b2f54fa..1a4fd93f5 100644 --- a/benchmarking/IdentifiableFunctions/param.jl +++ b/benchmarking/IdentifiableFunctions/param.jl @@ -31,7 +31,7 @@ function check_constructive_field_membership(generators::AbstractVector, to_be_r $(join(map(x -> string(x[1]) * " -> " * string(x[2]), zip(fracs_gen, tag_strings)), "\t\n")) """ var_strings = vcat(sat_string, map(string, gens(ring)), tag_strings) - ring_tag, xs_tag = PolynomialRing(K, var_strings, ordering = Nemo.ordering(ring)) + ring_tag, xs_tag = polynomial_ring(K, var_strings, ordering = Nemo.ordering(ring)) orig_vars = xs_tag[2:(nvars(ring) + 1)] tag_vars = xs_tag[(nvars(ring) + 2):end] sat_var = xs_tag[1] @@ -121,12 +121,12 @@ rem_tags, tag_to_gen = check_constructive_field_membership(fracs_generators, to_ #= ┌ Info: │ rem_tags = -│ 3-element Vector{AbstractAlgebra.Generic.Frac{fmpq_mpoly}}: +│ 3-element Vector{AbstractAlgebra.Generic.Frac{QQMPolyRingElem}}: │ T1^2 │ -5*T1 + T2 │ T2//T1^10 │ tag_to_gen = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 2 entries: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 2 entries: │ T1 => a^2 └ T2 => (a + b)//b =# @@ -205,10 +205,10 @@ new_vector_field, new_outputs, new_vars = │ Dict{Any, Any} with 1 entry: │ T1 => 2*T1 + T2 │ new_outputs = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 1 entry: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 1 entry: │ y => T1 │ new_vars = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 2 entries: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 2 entries: │ T2 => a + b └ T1 => x2 + x1 =# @@ -230,7 +230,7 @@ id_funcs = StructuralIdentifiability.find_identifiable_functions( #= ┌ Info: │ id_funcs = -│ 2-element Vector{AbstractAlgebra.Generic.Frac{fmpq_mpoly}}: +│ 2-element Vector{AbstractAlgebra.Generic.Frac{QQMPolyRingElem}}: │ x2*x1 └ a + b =# @@ -246,10 +246,10 @@ new_vector_field, new_outputs, new_vars = reparametrize_with_respect_to(ode, new │ Dict{Any, Any} with 1 entry: │ T1 => T1*T2 │ new_outputs = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 1 entry: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 1 entry: │ y => T1 │ new_vars = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 2 entries: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 2 entries: │ T2 => a + b └ T1 => x2*x1 =# @@ -271,7 +271,7 @@ id_funcs = StructuralIdentifiability.find_identifiable_functions( #= ┌ Info: │ id_funcs = -│ 5-element Vector{AbstractAlgebra.Generic.Frac{fmpq_mpoly}}: +│ 5-element Vector{AbstractAlgebra.Generic.Frac{QQMPolyRingElem}}: │ x2*x1 │ a*b │ x2 + x1 @@ -292,10 +292,10 @@ new_vector_field, new_iutputs, new_vars = reparametrize_with_respect_to(ode, new │ T2 => T2*T4 │ T3 => -1//2*T1*T4^2 + 2*T1*T5 + 1//2*T3*T4 │ new_outputs = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 1 entry: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 1 entry: │ y => T1 │ new_vars = -│ Dict{fmpq_mpoly, AbstractAlgebra.Generic.Frac{fmpq_mpoly}} with 5 entries: +│ Dict{QQMPolyRingElem, AbstractAlgebra.Generic.Frac{QQMPolyRingElem}} with 5 entries: │ T1 => x2 + x1 │ T2 => x2*x1 │ T3 => a*x2 - a*x1 - b*x2 + b*x1 diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitExt.jl index aefe97aa6..0e1a00026 100644 --- a/ext/ModelingToolkitExt.jl +++ b/ext/ModelingToolkitExt.jl @@ -166,7 +166,7 @@ function __mtk_to_si( de::ModelingToolkit.AbstractTimeDependentSystem, measured_quantities::Array{<:Tuple{String, <:SymbolicUtils.BasicSymbolic}}, ) - polytype = StructuralIdentifiability.Nemo.fmpq_mpoly + polytype = StructuralIdentifiability.Nemo.QQMPolyRingElem fractype = StructuralIdentifiability.Nemo.Generic.Frac{polytype} diff_eqs = filter(eq -> !(ModelingToolkit.isoutput(eq.lhs)), ModelingToolkit.equations(de)) @@ -197,7 +197,7 @@ function __mtk_to_si( input_symbols = vcat(state_vars, inputs, params) generators = vcat(string.(input_symbols), [e[1] for e in measured_quantities]) generators = map(g -> replace(g, "(t)" => ""), generators) - R, gens_ = Nemo.PolynomialRing(Nemo.QQ, generators) + R, gens_ = Nemo.polynomial_ring(Nemo.QQ, generators) y_vars = Vector{polytype}([str_to_var(e[1], R) for e in measured_quantities]) symb2gens = Dict(input_symbols .=> gens_[1:length(input_symbols)]) diff --git a/src/ODE.jl b/src/ODE.jl index 1113a204f..83ba44ae2 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -20,7 +20,7 @@ struct ODE{P} # P is the type of polynomials in the rhs of the ODE system x_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, y_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, inputs::Array{P, 1}, - ) where {P <: MPolyElem{<:FieldElem}} + ) where {P <: MPolyRingElem{<:FieldElem}} # Initialize ODE # x_eqs is a dictionary x_i => f_i(x, u, params) # y_eqs is a dictionary y_i => g_i(x, u, params) @@ -40,7 +40,7 @@ struct ODE{P} # P is the type of polynomials in the rhs of the ODE system x_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, y_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, inputs::Array{P, 1}, - ) where {P <: MPolyElem{<:FieldElem}} + ) where {P <: MPolyRingElem{<:FieldElem}} x_vars = collect(keys(x_eqs)) y_vars = collect(keys(y_eqs)) return ODE{P}(x_vars, y_vars, x_eqs, y_eqs, inputs) @@ -53,10 +53,13 @@ end #------------------------------------------------------------------------------ -function add_outputs(ode::ODE{P}, extra_y::Dict{String, <:RingElem}) where {P <: MPolyElem} +function add_outputs( + ode::ODE{P}, + extra_y::Dict{String, <:RingElem}, +) where {P <: MPolyRingElem} new_var_names = vcat(collect(map(var_to_str, gens(ode.poly_ring))), collect(keys(extra_y))) - new_ring, new_vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_var_names) + new_ring, new_vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_var_names) new_x = Array{P, 1}([parent_ring_change(x, new_ring) for x in ode.x_vars]) new_x_eqs = Dict{P, Union{P, Generic.Frac{P}}}( @@ -94,10 +97,10 @@ Output: function set_parameter_values( ode::ODE{P}, param_values::Dict{P, T}, -) where {T <: FieldElem, P <: MPolyElem{T}} +) where {T <: FieldElem, P <: MPolyRingElem{T}} new_vars = map(var_to_str, [v for v in gens(ode.poly_ring) if !(v in keys(param_values))]) - small_ring, small_vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_vars) + small_ring, small_vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_vars) eval_dict = Dict(str_to_var(v, ode.poly_ring) => str_to_var(v, small_ring) for v in new_vars) merge!(eval_dict, Dict(p => small_ring(val) for (p, val) in param_values)) @@ -130,8 +133,8 @@ end function set_parameter_values( ode::ODE{P}, param_values::Dict{P, <:Number}, -) where {P <: MPolyElem} - new_values = Dict{P, fmpq}(x => _to_rational(v) for (x, v) in param_values) +) where {P <: MPolyRingElem} + new_values = Dict{P, QQFieldElem}(x => _to_rational(v) for (x, v) in param_values) return set_parameter_values(ode, new_values) end @@ -156,11 +159,11 @@ function power_series_solution( initial_conditions::Dict{P, T}, input_values::Dict{P, Array{T, 1}}, prec::Int, -) where {T <: FieldElem, P <: MPolyElem{T}} +) where {T <: FieldElem, P <: MPolyRingElem{T}} new_varnames = map(var_to_str, vcat(ode.x_vars, ode.u_vars)) append!(new_varnames, map(v -> var_to_str(v) * "_dot", ode.x_vars)) - new_ring, new_vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_varnames) + new_ring, new_vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_varnames) equations = Array{P, 1}() evaluation = Dict(k => new_ring(v) for (k, v) in param_values) for v in vcat(ode.x_vars, ode.u_vars) @@ -196,7 +199,7 @@ function power_series_solution( initial_conditions::Dict{P, Int}, input_values::Dict{P, Array{Int, 1}}, prec::Int, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} bring = base_ring(ode.poly_ring) return power_series_solution( ode, @@ -213,7 +216,7 @@ end Reduces a polynomial/rational function over Q modulo p """ -function _reduce_mod_p(poly::fmpq_mpoly, p::Int) +function _reduce_mod_p(poly::QQMPolyRingElem, p::Int) den = denominator(poly) num = change_base_ring(Nemo.ZZ, den * poly) if Nemo.Native.GF(p)(den) == 0 @@ -222,7 +225,7 @@ function _reduce_mod_p(poly::fmpq_mpoly, p::Int) return change_base_ring(Nemo.Native.GF(p), num) * (1 // Nemo.Native.GF(p)(den)) end -function _reduce_mod_p(rat::Generic.Frac{fmpq_mpoly}, p::Int) +function _reduce_mod_p(rat::Generic.Frac{QQMPolyRingElem}, p::Int) num, den = map(poly -> _reduce_mod_p(poly, p), [numerator(rat), denominator(rat)]) if den == 0 throw(Base.ArgumentError("Prime $p divides the denominator of $rat")) @@ -238,9 +241,9 @@ end Input: ode is an ODE over QQ, p is a prime number Output: the reduction mod p, throws an exception if p divides one of the denominators """ -function reduce_ode_mod_p(ode::ODE{<:MPolyElem{Nemo.fmpq}}, p::Int) +function reduce_ode_mod_p(ode::ODE{<:MPolyRingElem{Nemo.QQFieldElem}}, p::Int) new_ring, new_vars = - Nemo.PolynomialRing(Nemo.Native.GF(p), map(var_to_str, gens(ode.poly_ring))) + Nemo.polynomial_ring(Nemo.Native.GF(p), map(var_to_str, gens(ode.poly_ring))) new_type = typeof(new_vars[1]) new_inputs = map(u -> switch_ring(u, new_ring), ode.u_vars) new_x = map(x -> switch_ring(x, new_ring), ode.x_vars) @@ -375,7 +378,7 @@ macro ODEmodel(ex::Expr...) R = gensym() vars_aux = gensym() exp_ring = :( - ($R, $vars_aux) = StructuralIdentifiability.Nemo.PolynomialRing( + ($R, $vars_aux) = StructuralIdentifiability.Nemo.polynomial_ring( StructuralIdentifiability.Nemo.QQ, map(string, $all_symb_with_t), ) @@ -385,8 +388,10 @@ macro ODEmodel(ex::Expr...) # setting x_vars and y_vars in the right order vx = gensym() vy = gensym() - x_var_expr = :($vx = Vector{StructuralIdentifiability.Nemo.fmpq_mpoly}([$(x_vars...)])) - y_var_expr = :($vy = Vector{StructuralIdentifiability.Nemo.fmpq_mpoly}([$(y_vars...)])) + x_var_expr = + :($vx = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(x_vars...)])) + y_var_expr = + :($vy = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(y_vars...)])) # preparing equations equations = map(macrohelper_clean, equations) @@ -394,22 +399,22 @@ macro ODEmodel(ex::Expr...) y_dict = gensym() x_dict_create_expr = :( $x_dict = Dict{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, Union{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, }, }, }() ) y_dict_create_expr = :( $y_dict = Dict{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, Union{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ - StructuralIdentifiability.Nemo.fmpq_mpoly, + StructuralIdentifiability.Nemo.QQMPolyRingElem, }, }, }() @@ -472,13 +477,14 @@ macro ODEmodel(ex::Expr...) ), ] # creating the ode object - ode_expr = :(StructuralIdentifiability.ODE{StructuralIdentifiability.Nemo.fmpq_mpoly}( - $vx, - $vy, - $x_dict, - $y_dict, - Array{StructuralIdentifiability.Nemo.fmpq_mpoly}([$(u_vars...)]), - )) + ode_expr = + :(StructuralIdentifiability.ODE{StructuralIdentifiability.Nemo.QQMPolyRingElem}( + $vx, + $vy, + $x_dict, + $y_dict, + Array{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(u_vars...)]), + )) result = Expr( :block, diff --git a/src/ODEexport.jl b/src/ODEexport.jl index aa4f8d63d..c74898476 100644 --- a/src/ODEexport.jl +++ b/src/ODEexport.jl @@ -10,7 +10,7 @@ function print_for_maple(ode::ODE, package = :SIAN) varstr = Dict(x => var_to_str(x) * "(t)" for x in vcat(ode.x_vars, ode.u_vars, ode.y_vars)) merge!(varstr, Dict(p => var_to_str(p) for p in ode.parameters)) - R_print, vars_print = Nemo.PolynomialRing( + R_print, vars_print = Nemo.polynomial_ring( base_ring(ode.poly_ring), [varstr[v] for v in gens(ode.poly_ring)], ) @@ -189,7 +189,7 @@ function print_for_COMBOS(ode::ODE) merge!(varstr, Dict(u => "u" * string(ind) for (ind, u) in enumerate(ode.u_vars))) merge!(varstr, Dict(y => "y" * string(ind) for (ind, y) in enumerate(ode.y_vars))) merge!(varstr, Dict(p => var_to_str(p) for p in ode.parameters)) - R_print, vars_print = Nemo.PolynomialRing( + R_print, vars_print = Nemo.polynomial_ring( base_ring(ode.poly_ring), [varstr[v] for v in gens(ode.poly_ring)], ) diff --git a/src/RationalFunctionFields/IdealMQS.jl b/src/RationalFunctionFields/IdealMQS.jl index 6d8ddb81c..519254fa7 100644 --- a/src/RationalFunctionFields/IdealMQS.jl +++ b/src/RationalFunctionFields/IdealMQS.jl @@ -100,7 +100,7 @@ mutable struct IdealMQS{T} <: AbstractBlackboxIdeal end varnames = append_at_index(ystrs, sat_var_index, sat_varname) @debug "Saturating variable is $sat_varname, index is $sat_var_index" - R_sat, v_sat = Nemo.PolynomialRing(K, varnames, ordering = ordering) + R_sat, v_sat = Nemo.polynomial_ring(K, varnames, ordering = ordering) # Saturation t_sat = v_sat[sat_var_index] den_lcm_orig = den_lcm @@ -144,7 +144,7 @@ mutable struct IdealMQS{T} <: AbstractBlackboxIdeal push!(nums_qq, num) end end - parent_ring_param, _ = PolynomialRing(ring, varnames, ordering = ordering) + parent_ring_param, _ = polynomial_ring(ring, varnames, ordering = ordering) @debug "Constructed MQS ideal in $R_sat with $(length(nums_qq) + 1) elements" @assert length(pivots_indices) == length(dens_indices) == length(dens_qq) @assert length(pivots_indices) == length(funcs_den_nums) @@ -202,7 +202,7 @@ function fractionfree_generators_raw(mqs::IdealMQS) end # NOTE: new variables go first! big_ring, big_vars = - PolynomialRing(K, vcat(new_varnames, old_varnames), ordering = :lex) + polynomial_ring(K, vcat(new_varnames, old_varnames), ordering = :lex) @info "$(mqs.sat_var_index) $(varnames) $ring_params $(parent(mqs.sat_qq))" nums_qq, dens_qq, sat_qq = mqs.nums_qq, mqs.dens_qq, mqs.sat_qq nums_y = map(num -> parent_ring_change(num, big_ring, matching = :byindex), nums_qq) @@ -229,7 +229,7 @@ end function ParamPunPam.reduce_mod_p!( mqs::IdealMQS, ff::Field, -) where {Field <: Union{Nemo.GaloisField, Nemo.GaloisFmpzField}} +) where {Field <: Union{Nemo.GaloisField, Nemo.FpField}} @debug "Reducing MQS ideal modulo $(ff)" # If there is a reduction modulo this field already, if haskey(mqs.cached_nums_gf, ff) @@ -251,7 +251,7 @@ function ParamPunPam.specialize_mod_p( mqs::IdealMQS, point::Vector{T}; saturated = true, -) where {T <: Union{gfp_elem, gfp_fmpz_elem}} +) where {T <: Union{fpFieldElem, gfp_fmpz_elem}} K_1 = parent(first(point)) @debug "Evaluating MQS ideal over $K_1 at $point" @assert haskey(mqs.cached_nums_gf, K_1) @@ -283,7 +283,7 @@ function ParamPunPam.specialize_mod_p( return polys end -function specialize(mqs::IdealMQS, point::Vector{Nemo.fmpq}; saturated = true) +function specialize(mqs::IdealMQS, point::Vector{Nemo.QQFieldElem}; saturated = true) @debug "Evaluating MQS ideal over QQ at $point" nums_qq, dens_qq, sat_qq = mqs.nums_qq, mqs.dens_qq, mqs.sat_qq dens_indices = mqs.dens_indices diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 6ef80d5b0..2793ce16d 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -99,7 +99,7 @@ function assess_identifiability( funcs_to_check = Vector(), prob_threshold::Float64 = 0.99, loglevel = Logging.Info, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do @@ -115,7 +115,7 @@ function _assess_identifiability( ode::ODE{P}; funcs_to_check = Vector(), prob_threshold::Float64 = 0.99, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} p_glob = 1 - (1 - prob_threshold) * 0.9 p_loc = 1 - (1 - prob_threshold) * 0.1 @@ -124,7 +124,7 @@ function _assess_identifiability( end @info "Assessing local identifiability" - trbasis = Array{fmpq_mpoly, 1}() + trbasis = Array{QQMPolyRingElem, 1}() runtime = @elapsed local_result = _assess_local_identifiability( ode, funcs_to_check = funcs_to_check, diff --git a/src/discrete.jl b/src/discrete.jl index 7cb92ea31..3c2db3dc5 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -18,7 +18,7 @@ function sequence_solution( initial_conditions::Dict{P, T}, input_values::Dict{P, Array{T, 1}}, num_terms::Int, -) where {T <: FieldElem, P <: MPolyElem{T}} +) where {T <: FieldElem, P <: MPolyRingElem{T}} result = Dict(x => [initial_conditions[x]] for x in dds.x_vars) for i in 2:num_terms eval_dict = merge( @@ -41,7 +41,7 @@ function sequence_solution( initial_conditions::Dict{P, Int}, input_values::Dict{P, Array{Int, 1}}, num_terms::Int, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} bring = base_ring(dds.poly_ring) return sequence_solution( dds, @@ -71,7 +71,7 @@ function differentiate_sequence_solution( ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, num_terms::Int, -) where {T <: Generic.FieldElem, P <: MPolyElem{T}} +) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing the power series solution of the system" seq_sol = sequence_solution(dds, params, ic, inputs, num_terms) generalized_params = vcat(dds.x_vars, dds.parameters) @@ -125,7 +125,7 @@ function differentiate_sequence_output( ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, num_terms::Int, -) where {T <: Generic.FieldElem, P <: MPolyElem{T}} +) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing partial derivatives of the solution" seq_sol, sol_diff = differentiate_sequence_solution(dds, params, ic, inputs, num_terms) @@ -178,7 +178,7 @@ function _degree_with_common_denom(polys) end """ - _assess_local_identifiability_discrete_aux(dds::ODE{P}, funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99) where P <: MPolyElem{Nemo.fmpq} + _assess_local_identifiability_discrete_aux(dds::ODE{P}, funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99) where P <: MPolyRingElem{Nemo.QQFieldElem} Checks the local identifiability/observability of the functions in `funcs_to_check` treating `dds` as a discrete-time system with **shift** instead of derivative in the right-hand side. @@ -193,7 +193,7 @@ function _assess_local_identifiability_discrete_aux( funcs_to_check::Array{<:Any, 1}, known_ic = :none, prob_threshold::Float64 = 0.99, -) where {P <: MPolyElem{Nemo.fmpq}} +) where {P <: MPolyRingElem{Nemo.QQFieldElem}} bring = base_ring(dds.poly_ring) @debug "Extending the model" @@ -228,8 +228,8 @@ function _assess_local_identifiability_discrete_aux( # Parameter values are the same across all the replicas params_vals = Dict(p => bring(rand(1:D)) for p in dds_ext.parameters) ic = Dict(x => bring(rand(1:D)) for x in dds_ext.x_vars) - # TODO: parametric type instead of fmpq - inputs = Dict{P, Array{fmpq, 1}}( + # TODO: parametric type instead of QQFieldElem + inputs = Dict{P, Array{QQFieldElem, 1}}( u => [bring(rand(1:D)) for i in 1:prec] for u in dds_ext.u_vars ) diff --git a/src/elimination.jl b/src/elimination.jl index 6798889e9..24978ca1b 100644 --- a/src/elimination.jl +++ b/src/elimination.jl @@ -58,7 +58,7 @@ Inputs: Output: - `M::MatrixElem` - The Bezout matrix """ -function Bezout_matrix(f::P, g::P, var_elim::P) where {P <: MPolyElem} +function Bezout_matrix(f::P, g::P, var_elim::P) where {P <: MPolyRingElem} parent_ring = parent(f) deg_f = Nemo.degree(f, var_elim) deg_g = Nemo.degree(g, var_elim) @@ -91,7 +91,7 @@ Inputs: Output: - `M::MatrixElem` - The Sylvester matrix """ -function Sylvester_matrix(f::P, g::P, var_elim::P) where {P <: MPolyElem} +function Sylvester_matrix(f::P, g::P, var_elim::P) where {P <: MPolyRingElem} parent_ring = parent(f) deg_f = Nemo.degree(f, var_elim) deg_g = Nemo.degree(g, var_elim) @@ -124,9 +124,9 @@ Input: Output: - `M::MatrixElem` - Simplified matrix -- `extra_factors::Vector{AbstractAlgebra.MPolyElem}` - array of GCDs eliminated from `M`. +- `extra_factors::Vector{AbstractAlgebra.MPolyRingElem}` - array of GCDs eliminated from `M`. """ -function simplify_matrix(M::MatElem{P}) where {P <: MPolyElem} +function simplify_matrix(M::MatElem{P}) where {P <: MPolyRingElem} """ An auxiliary function taking a list of coordinates of cells and dividing them by their gcd. @@ -188,7 +188,10 @@ mutable struct ODEPointGenerator{P} <: PointGenerator{P} cached_points::Array{Dict{P, <:FieldElem}, 1} number_type::Type - function ODEPointGenerator{P}(ode::ODE{P}, big_ring::MPolyRing) where {P <: MPolyElem} + function ODEPointGenerator{P}( + ode::ODE{P}, + big_ring::MPolyRing, + ) where {P <: MPolyRingElem} prec = length(ode.x_vars) + 1 number_type = typeof(one(base_ring(big_ring))) return new(ode, big_ring, prec, Array{Dict{P, number_type}}[], number_type) @@ -200,7 +203,7 @@ end function Base.iterate( gpg::ODEPointGenerator{P}, i::Int = 1, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} if i > length(gpg.cached_points) @debug "Generating new point on the variety" sample_max = i * 50 @@ -273,7 +276,7 @@ Output: function choose( polys::Array{P, 1}, generic_point_generator, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} vars = gens(parent(polys[1])) for p in generic_point_generator # get accounts for the fact that the big ring may contain some auxiliary variables, e.g. rand_proj_var @@ -307,7 +310,7 @@ Output: g::P, var_elim::P, generic_point_generator, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} # Linear comb while f != 0 && g != 0 if Nemo.degree(f, var_elim) > Nemo.degree(g, var_elim) diff --git a/src/global_identifiability.jl b/src/global_identifiability.jl index 984747e3b..da93e6d74 100644 --- a/src/global_identifiability.jl +++ b/src/global_identifiability.jl @@ -17,14 +17,14 @@ are identifiable functions containing or not the state variables ode::ODE{P}, known::Array{P, 1}, with_states::Bool, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} coeff_lists = Dict(:with_states => Array{Array{P, 1}, 1}(), :no_states => Array{Array{P, 1}, 1}()) varnames = [var_to_str(p) for p in ode.parameters] if with_states append!(varnames, map(var_to_str, ode.x_vars)) end - bring, _ = Nemo.PolynomialRing(base_ring(ode.poly_ring), varnames) + bring, _ = Nemo.polynomial_ring(base_ring(ode.poly_ring), varnames) if with_states @debug "Computing Lie derivatives" @@ -67,7 +67,7 @@ are identifiable functions containing or not the state variables if with_states new_vars = vcat(new_vars, ode.x_vars) end - new_ring, _ = PolynomialRing(Nemo.QQ, map(Symbol, new_vars)) + new_ring, _ = polynomial_ring(Nemo.QQ, map(Symbol, new_vars)) new_coeff_lists = empty(coeff_lists) for (key, coeff_list) in coeff_lists new_coeff_lists[key] = @@ -145,7 +145,7 @@ The function returns a tuple containing the following: if with_states && !isempty(ode.parameters) @debug "Generators of identifiable functions involve states, the parameter-only part is getting simplified" # NOTE: switching to a ring without states for a moment - param_ring, _ = PolynomialRing( + param_ring, _ = polynomial_ring( base_ring(bring), map(string, ode.parameters), ordering = Nemo.ordering(bring), @@ -195,7 +195,7 @@ Output: a list L of booleans with L[i] being the identifiability status of the i known::Array{P, 1} = Array{P, 1}(), prob_threshold::Float64 = 0.99, var_change_policy = :default, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} states_needed = false for f in funcs_to_check num, den = unpack_fraction(f) @@ -236,7 +236,7 @@ function check_identifiability( known::Array{P, 1} = Array{P, 1}(), prob_threshold::Float64 = 0.99, var_change_policy = :default, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} return check_identifiability( ode, ode.parameters, @@ -248,7 +248,7 @@ end #------------------------------------------------------------------------------ """ - assess_global_identifiability(ode::ODE{P}, prob_threshold::Float64=0.99; var_change=:default) where P <: MPolyElem{fmpq} + assess_global_identifiability(ode::ODE{P}, prob_threshold::Float64=0.99; var_change=:default) where P <: MPolyRingElem{QQFieldElem} Input: - `ode` - the ODE model @@ -266,7 +266,7 @@ function assess_global_identifiability( known::Array{P, 1} = Array{P, 1}(), prob_threshold::Float64 = 0.99; var_change = :default, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} result_list = assess_global_identifiability( ode, ode.parameters, @@ -303,7 +303,7 @@ Checks global identifiability of functions of parameters specified in `funcs_to_ known::Array{P, 1} = Array{P, 1}(), prob_threshold::Float64 = 0.99; var_change = :default, -) where {P <: MPolyElem{fmpq}} +) where {P <: MPolyRingElem{QQFieldElem}} submodels = find_submodels(ode) if length(submodels) > 0 @info "Note: the input model has nontrivial submodels. If the computation for the full model will be too heavy, you may want to try to first analyze one of the submodels. They can be produced using function `find_submodels`" diff --git a/src/identifiable_functions.jl b/src/identifiable_functions.jl index d84e0f14e..ec116843e 100644 --- a/src/identifiable_functions.jl +++ b/src/identifiable_functions.jl @@ -37,7 +37,7 @@ ode = @ODEmodel( find_identifiable_functions(ode) # prints -3-element Vector{AbstractAlgebra.Generic.Frac{Nemo.fmpq_mpoly}}: +3-element Vector{AbstractAlgebra.Generic.Frac{Nemo.QQMPolyRingElem}}: a12 + a01 + a21 a12*a01 ``` @@ -51,7 +51,7 @@ function find_identifiable_functions( simplify = :standard, rational_interpolator = :VanDerHoevenLecerf, loglevel = Logging.Info, -) where {T <: MPolyElem{fmpq}} +) where {T <: MPolyRingElem{QQFieldElem}} restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do @@ -73,7 +73,7 @@ function _find_identifiable_functions( with_states = false, simplify = :standard, rational_interpolator = :VanDerHoevenLecerf, -) where {T <: MPolyElem{fmpq}} +) where {T <: MPolyRingElem{QQFieldElem}} Random.seed!(seed) @assert simplify in (:standard, :weak, :strong, :absent) runtime_start = time_ns() diff --git a/src/io_equation.jl b/src/io_equation.jl index ed1b1b1c4..92d90ff4e 100644 --- a/src/io_equation.jl +++ b/src/io_equation.jl @@ -2,7 +2,12 @@ const PROJECTION_VARNAME = "rand_proj_var" # ------------------------------------------------------------------------------ -function generator_var_change(generator, var::P, numer::P, denom::P) where {P <: MPolyElem} +function generator_var_change( + generator, + var::P, + numer::P, + denom::P, +) where {P <: MPolyRingElem} return IterTools.imap( point -> begin result = copy(point) @@ -15,7 +20,7 @@ end # ------------------------------------------------------------------------------ -function diff_poly(poly::P, derivation::Dict{P, T}) where {P <: MPolyElem, T} +function diff_poly(poly::P, derivation::Dict{P, T}) where {P <: MPolyRingElem, T} return sum(derivative(poly, x) * xd for (x, xd) in derivation) end @@ -28,7 +33,7 @@ end # ------------------------------------------------------------------------------ -function generate_io_equation_problem(ode::ODE{P}) where {P <: MPolyElem{<:FieldElem}} +function generate_io_equation_problem(ode::ODE{P}) where {P <: MPolyRingElem{<:FieldElem}} dim_x = length(ode.x_vars) # Creating a ring @@ -40,7 +45,7 @@ function generate_io_equation_problem(ode::ODE{P}) where {P <: MPolyElem{<:Field [var_to_str(u) * "_$i" for i in 0:dim_x for u in ode.u_vars], [PROJECTION_VARNAME], ) - ring, ring_vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), var_names) + ring, ring_vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), var_names) # Definiting a (partial) derivation on it derivation = Dict{P, P}() @@ -105,7 +110,7 @@ Output: ode::ODE{P}, auto_var_change::Bool, extra_projection = nothing, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} # Initialization ring, derivation, x_equations, y_equations, point_generator = generate_io_equation_problem(ode) @@ -341,7 +346,7 @@ Output: ode::ODE{P}; var_change_policy = :default, loglevel = Logging.Info, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} # Setting the var_change policy if (var_change_policy == :yes) || (var_change_policy == :default && length(ode.y_vars) < 3) diff --git a/src/lincomp.jl b/src/lincomp.jl index 72dd6a327..0603e39a9 100644 --- a/src/lincomp.jl +++ b/src/lincomp.jl @@ -45,14 +45,15 @@ function linear_compartment_model( push!(edges_vars_names, "a_0_$(s)") end - R, vars = StructuralIdentifiability.Nemo.PolynomialRing( + R, vars = StructuralIdentifiability.Nemo.polynomial_ring( StructuralIdentifiability.Nemo.QQ, vcat(x_vars_names, y_vars_names, u_vars_names, edges_vars_names), ) x_vars = @view vars[1:n] - x_equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}( - x => R(0) for x in x_vars - ) + x_equations = + Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}}( + x => R(0) for x in x_vars + ) for i in 1:n for j in graph[i] rate = str_to_var("a_$(j)_$(i)", R) @@ -68,14 +69,15 @@ function linear_compartment_model( end end - y_equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}( - str_to_var("y$i", R) => str_to_var("x$i", R) for i in outputs - ) + y_equations = + Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}}( + str_to_var("y$i", R) => str_to_var("x$i", R) for i in outputs + ) - return ODE{fmpq_mpoly}( + return ODE{QQMPolyRingElem}( x_equations, y_equations, - Array{fmpq_mpoly}([str_to_var("u$i", R) for i in inputs]), + Array{QQMPolyRingElem}([str_to_var("u$i", R) for i in inputs]), ) end diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index da6909777..2580d2de2 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -30,7 +30,7 @@ function differentiate_solution( ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, prec::Int, -) where {T <: Generic.FieldElem, P <: MPolyElem{T}} +) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing the power series solution of the system" ps_sol = power_series_solution(ode, params, ic, inputs, prec) ps_ring = parent(first(values(ps_sol))) @@ -86,7 +86,7 @@ function differentiate_output( ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, prec::Int, -) where {T <: Generic.FieldElem, P <: MPolyElem{T}} +) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing partial derivatives of the solution" ps_sol, sol_diff = differentiate_solution(ode, params, ic, inputs, prec) ps_ring = parent(first(values(ps_sol))) @@ -124,7 +124,7 @@ end for `f` being a polynomial/rational function over rationals (`QQ`) returns a tuple `(degree, max_coef_size)` """ -function get_degree_and_coeffsize(f::MPolyElem{Nemo.fmpq}) +function get_degree_and_coeffsize(f::MPolyRingElem{Nemo.QQFieldElem}) if length(f) == 0 return (0, 1) end @@ -135,7 +135,7 @@ function get_degree_and_coeffsize(f::MPolyElem{Nemo.fmpq}) return (total_degree(f), max_coef) end -function get_degree_and_coeffsize(f::Generic.Frac{<:MPolyElem{Nemo.fmpq}}) +function get_degree_and_coeffsize(f::Generic.Frac{<:MPolyRingElem{Nemo.QQFieldElem}}) num_deg, num_coef = get_degree_and_coeffsize(numerator(f)) den_deg, den_coef = get_degree_and_coeffsize(denominator(f)) return (max(num_deg, den_deg), max(num_coef, den_coef)) @@ -144,7 +144,7 @@ end # ------------------------------------------------------------------------------ """ - assess_local_identifiability(ode::ODE{P}; funcs_to_check::Array{<: Any, 1}, prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) where P <: MPolyElem{Nemo.fmpq} + assess_local_identifiability(ode::ODE{P}; funcs_to_check::Array{<: Any, 1}, prob_threshold::Float64=0.99, type=:SE, loglevel=Logging.Info) where P <: MPolyRingElem{Nemo.QQFieldElem} Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `prob_threshold`. @@ -160,7 +160,7 @@ function assess_local_identifiability( type = :SE, trbasis = nothing, loglevel = Logging.Info, -) where {P <: MPolyElem{Nemo.fmpq}} +) where {P <: MPolyRingElem{Nemo.QQFieldElem}} restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do @@ -180,7 +180,7 @@ function _assess_local_identifiability( prob_threshold::Float64 = 0.99, type = :SE, trbasis = nothing, -) where {P <: MPolyElem{Nemo.fmpq}} +) where {P <: MPolyRingElem{Nemo.QQFieldElem}} if isempty(funcs_to_check) funcs_to_check = ode.parameters if type == :SE @@ -253,7 +253,7 @@ function _assess_local_identifiability( # in the SE case, it will exit right away while true ic = Dict(x => F(rand(1:prime)) for x in ode_red.x_vars) - inputs = Dict{Nemo.gfp_mpoly, Array{Nemo.gfp_elem, 1}}( + inputs = Dict{Nemo.gfp_mpoly, Array{Nemo.fpFieldElem, 1}}( u => [F(rand(1:prime)) for i in 1:prec] for u in ode_red.u_vars ) diff --git a/src/parametrizations.jl b/src/parametrizations.jl index a8b6e6ec4..c725c914c 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -174,7 +174,8 @@ $(join(map(x -> string(x[1]) * " -> " * string(x[2]), zip(fracs_gen, tag_string Saturation tag: $sat_string """ - poly_ring_tag, vars_tag = PolynomialRing(K, vcat(sat_string, orig_strings, tag_strings)) + poly_ring_tag, vars_tag = + polynomial_ring(K, vcat(sat_string, orig_strings, tag_strings)) sat_var = vars_tag[1] orig_vars = vars_tag[2:(nvars(poly_ring) + 1)] tag_vars = vars_tag[(nvars(poly_ring) + 2):end] @@ -227,7 +228,7 @@ $sat_string # # NOTE: reduction actually happens in K(T)[x]. So we map polynomials to the # parametric ring K(T)[x]. - ring_of_tags, tags = PolynomialRing(K, tag_strings) + ring_of_tags, tags = polynomial_ring(K, tag_strings) tag_to_gen = Dict(tags[i] => fracs_gen[i] for i in 1:length(fracs_gen)) if !isempty(intersect(tag_strings, orig_strings)) @warn """ @@ -236,7 +237,7 @@ $sat_string Original vars: $orig_strings""" end parametric_ring, _ = - PolynomialRing(FractionField(ring_of_tags), orig_strings, ordering = :degrevlex) + polynomial_ring(FractionField(ring_of_tags), orig_strings, ordering = :degrevlex) relations_between_tags = map(poly -> parent_ring_change(poly, ring_of_tags), relations_between_tags) param_var_mapping = merge( @@ -380,7 +381,7 @@ function reparametrize_with_respect_to(ode, new_states, new_params) input_variable_names, output_variable_names, ) - ring_output, _ = PolynomialRing( + ring_output, _ = polynomial_ring( base_ring(ring_of_tags), all_variable_names, ordering = Nemo.ordering(ring_of_tags), @@ -492,7 +493,7 @@ function _reparametrize_global(ode::ODE{P}; prob_threshold = 0.99, seed = 42) wh ode_ring = parent(ode) @assert base_ring(parent(first(id_funcs))) == ode_ring @info "Constructing a new parametrization" - contains_states(poly::MPolyElem) = any(x -> degree(poly, x) > 0, ode.x_vars) + contains_states(poly::MPolyRingElem) = any(x -> degree(poly, x) > 0, ode.x_vars) contains_states(func) = contains_states(numerator(func)) || contains_states(denominator(func)) id_funcs_contains_states = filter(contains_states, id_funcs) diff --git a/src/pb_representation.jl b/src/pb_representation.jl index 3594b143a..34a618bd4 100644 --- a/src/pb_representation.jl +++ b/src/pb_representation.jl @@ -13,7 +13,7 @@ struct PBRepresentation u_names::Array{String} # variables with infinite orders in the profile param_names::Array{String} # scalar parameters profile::Dict{String, Int} # the profile restricted on the y-variables - projections::Dict{String, <:MPolyElem} + projections::Dict{String, <:MPolyRingElem} function PBRepresentation(ode::ODE, io_equations) if length(keys(io_equations)) > length(ode.y_vars) @@ -33,10 +33,10 @@ struct PBRepresentation decompose_derivative(v, vcat(y_names, u_names)) != nothing, map(var_to_str, gens(old_ring)), ) - newring, _ = Nemo.PolynomialRing(base_ring(old_ring), new_varnames) + newring, _ = Nemo.polynomial_ring(base_ring(old_ring), new_varnames) profile = Dict{String, Int}() - projections = Dict{String, MPolyElem}() + projections = Dict{String, MPolyRingElem}() for (y, eq) in io_equations (name, ord) = decompose_derivative(var_to_str(y), y_names) profile[name] = ord @@ -60,7 +60,7 @@ Among the variables `vars`, determines the leading derivative if the y-variable (if exists) with respect to the ordering defined by the PB-representation (see Remark 2.20 in https://arxiv.org/abs/2111.00991) """ -function find_leader(vars::Array{<:MPolyElem}, pbr::PBRepresentation) +function find_leader(vars::Array{<:MPolyRingElem}, pbr::PBRepresentation) y_ders = filter(v -> decompose_derivative(var_to_str(v), pbr.y_names) != nothing, vars) if length(y_ders) == 0 return nothing @@ -83,7 +83,7 @@ For a polynomial `poly` in the same differential variables as `pbr`, finds a polynomial ring sufficient for carrying out the reduction and the corresponding differentiation mapping on the variables """ -function common_ring(poly::MPolyElem, pbr::PBRepresentation) +function common_ring(poly::MPolyRingElem, pbr::PBRepresentation) max_ords = Dict{String, Int}(v => 0 for v in vcat(pbr.y_names, pbr.u_names)) new_params = Array{String, 1}() for v in vars(poly) @@ -118,8 +118,8 @@ function common_ring(poly::MPolyElem, pbr::PBRepresentation) append!(varnames, new_params) newring, _ = - StructuralIdentifiability.Nemo.PolynomialRing(base_ring(parent(poly)), varnames) - derivation = Dict{MPolyElem, MPolyElem}() + StructuralIdentifiability.Nemo.polynomial_ring(base_ring(parent(poly)), varnames) + derivation = Dict{MPolyRingElem, MPolyRingElem}() for v in varnames d = decompose_derivative(v, vcat(pbr.y_names, pbr.u_names)) if d == nothing @@ -143,7 +143,7 @@ end Computes the leading coefficient of `f` viewed as a univariate polynomiall in variable `x` """ -function lc_univariate(f::MPolyElem, x::MPolyElem) +function lc_univariate(f::MPolyRingElem, x::MPolyRingElem) FieldType = typeof(one(base_ring(parent(f)))) dict_result = Dict{Array{Int, 1}, FieldType}() x_ind = findfirst(v -> v == x, gens(parent(f))) @@ -174,7 +174,7 @@ Input: Output: the pseudoremainder of `f` divided by `g` w.r.t. `x` """ -function pseudodivision(f::MPolyElem, g::MPolyElem, x::MPolyElem) +function pseudodivision(f::MPolyRingElem, g::MPolyRingElem, x::MPolyRingElem) result = f lcg = lc_univariate(g, x) while Nemo.degree(result, x) >= Nemo.degree(g, x) @@ -190,7 +190,7 @@ end # ----------------------------------------------------------------------------- -function diff(p::MPolyElem, derivation::Dict{<:MPolyElem, <:MPolyElem}, i::Int) +function diff(p::MPolyRingElem, derivation::Dict{<:MPolyRingElem, <:MPolyRingElem}, i::Int) if i == 0 return p end @@ -213,7 +213,7 @@ Input: Output: the result of differential reduction of `diffpoly` by `pbr` considered as a characteristic set (see Remark 2.20 in the paper) """ -function diffreduce(diffpoly::MPolyElem, pbr::PBRepresentation) +function diffreduce(diffpoly::MPolyRingElem, pbr::PBRepresentation) (ring, der) = common_ring(diffpoly, pbr) result = parent_ring_change(diffpoly, ring) diff --git a/src/power_series_utils.jl b/src/power_series_utils.jl index 3124d47c0..0e2e8e615 100644 --- a/src/power_series_utils.jl +++ b/src/power_series_utils.jl @@ -1,18 +1,18 @@ #------------------------------------------------------------------------------ -function truncate_matrix(M::MatElem{<:Generic.AbsSeriesElem}, prec::Int) +function truncate_matrix(M::MatElem{<:AbsPowerSeriesRingElem}, prec::Int) return map(e -> truncate(e, prec), M) end #------------------------------------------------------------------------------ -function matrix_set_precision!(M::MatElem{<:Generic.AbsSeriesElem}, prec::Int) +function matrix_set_precision!(M::MatElem{<:AbsPowerSeriesRingElem}, prec::Int) map(e -> set_precision!(e, prec), M) end #------------------------------------------------------------------------------ -function ps_matrix_const_term(M::MatElem{<:Generic.AbsSeriesElem}) +function ps_matrix_const_term(M::MatElem{<:AbsPowerSeriesRingElem}) return map(e -> coeff(e, 0), M) end @@ -26,7 +26,7 @@ Performs a single step of Newton iteration for inverting `M` with `Minv` being a function _matrix_inv_newton_iteration( M::MatElem{T}, Minv::MatElem{T}, -) where {T <: Generic.AbsSeriesElem{<:Generic.FieldElem}} +) where {T <: AbsPowerSeriesRingElem{<:Generic.FieldElem}} return 2 * Minv - Minv * M * Minv end @@ -44,7 +44,7 @@ Output: - the inverse of `M` computed up to `prec` """ function ps_matrix_inv( - M::MatElem{<:Generic.AbsSeriesElem{<:Generic.FieldElem}}, + M::MatElem{<:AbsPowerSeriesRingElem{<:Generic.FieldElem}}, prec::Int = -1, ) const_term = ps_matrix_const_term(M) @@ -70,7 +70,7 @@ Input: Output: - the derivative of `ps` """ -function ps_diff(ps::Generic.AbsSeriesElem{<:Generic.RingElem}) +function ps_diff(ps::AbsPowerSeriesRingElem{<:Generic.RingElem}) result = zero(parent(ps)) set_precision!(result, precision(ps)) for exp in 1:(precision(ps) - 1) @@ -89,7 +89,7 @@ Input: Output: - the integral of `ps` without constant term """ -function ps_integrate(ps::Generic.AbsSeriesElem{<:Generic.FieldElem}) +function ps_integrate(ps::AbsPowerSeriesRingElem{<:Generic.FieldElem}) result = zero(parent(ps)) set_precision!(result, precision(ps) + 1) for exp in 0:(precision(ps) - 1) @@ -109,7 +109,7 @@ Input: Output: - the natural log of `M` """ -function ps_matrix_log(M::MatElem{<:Generic.AbsSeriesElem{<:Generic.FieldElem}}) +function ps_matrix_log(M::MatElem{<:AbsPowerSeriesRingElem{<:Generic.FieldElem}}) const_term = ps_matrix_const_term(M) if const_term != one(parent(const_term)) throw(Base.DomainError("Constant term must be the identity matrix")) @@ -123,9 +123,9 @@ end #------------------------------------------------------------------------------ function _matrix_homlinear_de_newton_iteration( - A::MatElem{<:Generic.AbsSeriesElem{T}}, - Y::MatElem{<:Generic.AbsSeriesElem{T}}, - Z::MatElem{<:Generic.AbsSeriesElem{T}}, + A::MatElem{<:AbsPowerSeriesRingElem{T}}, + Y::MatElem{<:AbsPowerSeriesRingElem{T}}, + Z::MatElem{<:AbsPowerSeriesRingElem{T}}, cur_prec::Int, ) where {T <: Generic.FieldElem} Yprime = map(ps_diff, Y) @@ -150,7 +150,7 @@ Output: - matrix `Y` such that `Y' = AY` up to precision of `A - 1` and `Y(0) = Y0` """ function ps_matrix_homlinear_de( - A::MatElem{<:Generic.AbsSeriesElem{T}}, + A::MatElem{<:AbsPowerSeriesRingElem{T}}, Y0::MatElem{<:T}, prec::Int = -1, ) where {T <: Generic.FieldElem} @@ -173,10 +173,10 @@ end #------------------------------------------------------------------------------ function _variation_of_constants( - A::MatElem{<:Generic.AbsSeriesElem{T}}, - B::MatElem{<:Generic.AbsSeriesElem{T}}, - Yh::MatElem{<:Generic.AbsSeriesElem{T}}, - Zh::MatElem{<:Generic.AbsSeriesElem{T}}, + A::MatElem{<:AbsPowerSeriesRingElem{T}}, + B::MatElem{<:AbsPowerSeriesRingElem{T}}, + Yh::MatElem{<:AbsPowerSeriesRingElem{T}}, + Zh::MatElem{<:AbsPowerSeriesRingElem{T}}, Y0::MatElem{<:T}, prec::Int, ) where {T <: Generic.FieldElem} @@ -198,8 +198,8 @@ Output: - matrix `Y` such that `Y' = AY + B` up to precision of `A - 1` and `Y(0) = Y0` """ function ps_matrix_linear_de( - A::MatElem{<:Generic.AbsSeriesElem{T}}, - B::MatElem{<:Generic.AbsSeriesElem{T}}, + A::MatElem{<:AbsPowerSeriesRingElem{T}}, + B::MatElem{<:AbsPowerSeriesRingElem{T}}, Y0::MatElem{<:T}, prec::Int = -1, ) where {T <: Generic.FieldElem} @@ -231,7 +231,7 @@ function ps_ode_solution( ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, prec::Int, -) where {T <: Generic.FieldElem, P <: MPolyElem{T}} +) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} n = length(equations) ring = parent(equations[1]) S = AbstractAlgebra.MatrixSpace(ring, n, n) @@ -292,7 +292,7 @@ function ps_ode_solution( ic::Dict{P, Int}, inputs::Dict{P, Array{Int, 1}}, prec::Int, -) where {P <: MPolyElem{<:Generic.FieldElem}} +) where {P <: MPolyRingElem{<:Generic.FieldElem}} bring = base_ring(parent(equations[1])) ps_ode_solution( equations, diff --git a/src/primality_check.jl b/src/primality_check.jl index ae98f006b..e236849d5 100644 --- a/src/primality_check.jl +++ b/src/primality_check.jl @@ -1,6 +1,6 @@ # ------------------------------------------------------------------------------ -function check_primality_zerodim(J::Array{fmpq_mpoly, 1}) +function check_primality_zerodim(J::Array{QQMPolyRingElem, 1}) J = Groebner.groebner(J, loglevel = _groebner_loglevel[]) basis = Groebner.kbase(J, loglevel = _groebner_loglevel[]) dim = length(basis) @@ -22,7 +22,7 @@ function check_primality_zerodim(J::Array{fmpq_mpoly, 1}) generic_multiplication = sum(Nemo.QQ(rand(1:100)) * M for M in matrices) @debug generic_multiplication - R, t = Nemo.PolynomialRing(Nemo.QQ, "t") + R, t = Nemo.polynomial_ring(Nemo.QQ, "t") @debug "$(Nemo.charpoly(R, generic_multiplication))" return Nemo.isirreducible(Nemo.charpoly(R, generic_multiplication)) @@ -30,7 +30,7 @@ end #------------------------------------------------------------------------------ """ - check_primality(polys::Dict{fmpq_mpoly, fmpq_mpoly}, extra_relations::Array{fmpq_mpoly, 1}) + check_primality(polys::Dict{QQMPolyRingElem, QQMPolyRingElem}, extra_relations::Array{QQMPolyRingElem, 1}) The function checks if the ideal generated by the polynomials and saturated at the leading coefficient with respect to the corresponding variables is prime @@ -39,13 +39,13 @@ over rationals. The `extra_relations` allows adding more polynomials to the generators (not affecting the saturation). """ function check_primality( - polys::Dict{fmpq_mpoly, fmpq_mpoly}, - extra_relations::Array{fmpq_mpoly, 1}, + polys::Dict{QQMPolyRingElem, QQMPolyRingElem}, + extra_relations::Array{QQMPolyRingElem, 1}, ) leaders = collect(keys(polys)) ring = parent(leaders[1]) - Rspec, vspec = Nemo.PolynomialRing(Nemo.QQ, [var_to_str(l) for l in leaders]) + Rspec, vspec = Nemo.polynomial_ring(Nemo.QQ, [var_to_str(l) for l in leaders]) eval_point = [v in keys(polys) ? v : ring(rand(1:100)) for v in gens(ring)] all_polys = vcat(collect(values(polys)), extra_relations) zerodim_ideal = @@ -56,14 +56,14 @@ end #------------------------------------------------------------------------------ """ - check_primality(polys::Dict{fmpq_mpoly, fmpq_mpoly}) + check_primality(polys::Dict{QQMPolyRingElem, QQMPolyRingElem}) The function checks if the ideal generated by the polynomials and saturated at the leading coefficient with respect to the corresponding variables is prime over rationals. """ -function check_primality(polys::Dict{fmpq_mpoly, fmpq_mpoly}) - return check_primality(polys, Array{fmpq_mpoly, 1}()) +function check_primality(polys::Dict{QQMPolyRingElem, QQMPolyRingElem}) + return check_primality(polys, Array{QQMPolyRingElem, 1}()) end # ------------------------------------------------------------------------------ diff --git a/src/states.jl b/src/states.jl index c706a9ab5..86e0269d8 100644 --- a/src/states.jl +++ b/src/states.jl @@ -15,7 +15,7 @@ Output: function extract_coefficients_ratfunc( f::AbstractAlgebra.Generic.Frac{<:P}, vars::Vector{<:P}, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} num, denom = unpack_fraction(f) total_coeffs = Vector{P}() for p in (num, denom) @@ -32,7 +32,7 @@ end function extract_coefficients_ratfunc( f::P, vars::Vector{<:P}, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} return extract_coefficients_ratfunc(f // 1, vars) end @@ -50,7 +50,7 @@ Output: function lie_derivative( f::Generic.Frac{<:P}, ode::ODE{<:P}, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} @assert all([(x in ode.parameters) || (x in ode.x_vars) for x in vars(f)]) res = zero(parent(ode)) // 1 for (x, eq) in ode.x_equations @@ -59,7 +59,7 @@ function lie_derivative( return res end -function lie_derivative(f::P, ode::ODE{<:P}) where {P <: MPolyElem{<:FieldElem}} +function lie_derivative(f::P, ode::ODE{<:P}) where {P <: MPolyRingElem{<:FieldElem}} return lie_derivative(f // 1, ode) end @@ -79,7 +79,7 @@ identifiable functions of parameters only @timeit _to function states_generators( ode::ODE{P}, io_equations::Dict{P, P}, -) where {P <: MPolyElem{<:FieldElem}} +) where {P <: MPolyRingElem{<:FieldElem}} y_to_ord = Dict{P, Int}() ynames = [var_to_str(y) for y in ode.y_vars] for (leader, ioeq) in io_equations diff --git a/src/submodels.jl b/src/submodels.jl index 107ceac4e..793e77ba4 100644 --- a/src/submodels.jl +++ b/src/submodels.jl @@ -12,15 +12,15 @@ Output: - Dictionary where each key is a variable and each value is a list of variables on which the key depends """ -function construct_graph(ode::ODE{P}) where {P <: MPolyElem} - graph = Dict{fmpq_mpoly, Set{fmpq_mpoly}}() +function construct_graph(ode::ODE{P}) where {P <: MPolyRingElem} + graph = Dict{QQMPolyRingElem, Set{QQMPolyRingElem}}() for (x, f) in ode.x_equations temp = unpack_fraction(f) - graph[x] = Set{fmpq_mpoly}(union(vars(temp[1]), vars(temp[2]))) + graph[x] = Set{QQMPolyRingElem}(union(vars(temp[1]), vars(temp[2]))) end for (y, f) in ode.y_equations temp = unpack_fraction(f) - graph[y] = Set{fmpq_mpoly}(union(vars(temp[1]), vars(temp[2]))) + graph[y] = Set{QQMPolyRingElem}(union(vars(temp[1]), vars(temp[2]))) end return graph @@ -29,9 +29,9 @@ end # ------------------------------------------------------------------------------ function dfs( - graph::Dict{fmpq_mpoly, Set{fmpq_mpoly}}, - start::fmpq_mpoly, - visited::Set{fmpq_mpoly}, + graph::Dict{QQMPolyRingElem, Set{QQMPolyRingElem}}, + start::QQMPolyRingElem, + visited::Set{QQMPolyRingElem}, ) push!(visited, start) if start in keys(graph) @@ -47,12 +47,12 @@ end # ------------------------------------------------------------------------------ function traverse_outputs( - graph::Dict{fmpq_mpoly, Set{fmpq_mpoly}}, - ys::Array{fmpq_mpoly, 1}, + graph::Dict{QQMPolyRingElem, Set{QQMPolyRingElem}}, + ys::Array{QQMPolyRingElem, 1}, ) - raw_models = Dict{fmpq_mpoly, Set{fmpq_mpoly}}() + raw_models = Dict{QQMPolyRingElem, Set{QQMPolyRingElem}}() for y in ys - model = dfs(graph, y, Set{fmpq_mpoly}()) + model = dfs(graph, y, Set{QQMPolyRingElem}()) raw_models[y] = model end return raw_models @@ -61,10 +61,10 @@ end # ------------------------------------------------------------------------------ function saturate_ys( - unions::Array{Set{fmpq_mpoly}, 1}, - Y::Array{fmpq_mpoly, 1}, - graph::Dict{fmpq_mpoly, Set{fmpq_mpoly}}, - X::Array{fmpq_mpoly, 1}, + unions::Array{Set{QQMPolyRingElem}, 1}, + Y::Array{QQMPolyRingElem, 1}, + graph::Dict{QQMPolyRingElem, Set{QQMPolyRingElem}}, + X::Array{QQMPolyRingElem, 1}, ) for element in unions for y in Y @@ -78,8 +78,8 @@ end # ------------------------------------------------------------------------------ -function search_add_unions(submodels::Array{Set{fmpq_mpoly}, 1}) - result = Array{Set{fmpq_mpoly}, 1}([Set{fmpq_mpoly}()]) +function search_add_unions(submodels::Array{Set{QQMPolyRingElem}, 1}) + result = Array{Set{QQMPolyRingElem}, 1}([Set{QQMPolyRingElem}()]) for model in submodels for index in 1:length(result) push!(result, union(result[index], model)) @@ -93,10 +93,10 @@ end # filters the models containin all states or no states function filter_max_empty( ode::ODE{P}, - submodels::Array{Set{fmpq_mpoly}, 1}, -) where {P <: MPolyElem} + submodels::Array{Set{QQMPolyRingElem}, 1}, +) where {P <: MPolyRingElem} n = length(ode.x_vars) - new_sub = Array{Set{fmpq_mpoly}, 1}([]) + new_sub = Array{Set{QQMPolyRingElem}, 1}([]) for submodel in submodels list_x = [x for x in submodel if x in ode.x_vars] if !(length(list_x) == n) && (length(list_x) > 0) @@ -108,10 +108,10 @@ end # ------------------------------------------------------------------------------ -function ode_aux(ode::ODE{P}, submodel::Set{fmpq_mpoly}) where {P <: MPolyElem} +function ode_aux(ode::ODE{P}, submodel::Set{QQMPolyRingElem}) where {P <: MPolyRingElem} new_y = copy(ode.y_equations) new_x = copy(ode.x_equations) - new_u = Array{fmpq_mpoly, 1}([u for u in ode.u_vars if u in submodel]) + new_u = Array{QQMPolyRingElem, 1}([u for u in ode.u_vars if u in submodel]) for (x, f) in ode.x_equations if !(issubset(vars(x), submodel) && issubset(vars(f), submodel)) delete!(new_x, x) @@ -125,8 +125,8 @@ function ode_aux(ode::ODE{P}, submodel::Set{fmpq_mpoly}) where {P <: MPolyElem} end sub_str = map(var_to_str, collect(submodel)) - S, _ = Nemo.PolynomialRing(Nemo.QQ, sub_str) - dict_type = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}} + S, _ = Nemo.polynomial_ring(Nemo.QQ, sub_str) + dict_type = Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}} fin_x = dict_type(parent_ring_change(x, S) => parent_ring_change(f, S) for (x, f) in new_x) fin_y = @@ -142,7 +142,7 @@ function ode_aux(ode::ODE{P}, submodel::Set{fmpq_mpoly}) where {P <: MPolyElem} y in ode.y_vars if var_to_str(y) in map(var_to_str, collect(keys(fin_y))) ] - return ODE{fmpq_mpoly}(new_x_vars, new_y_vars, fin_x, fin_y, fin_u) + return ODE{QQMPolyRingElem}(new_x_vars, new_y_vars, fin_x, fin_y, fin_u) end # ------------------------------------------------------------------------------ @@ -163,8 +163,8 @@ Output: function submodel2ode( ode::ODE{P}, - submodels::Array{Set{fmpq_mpoly}, 1}, -) where {P <: MPolyElem} + submodels::Array{Set{QQMPolyRingElem}, 1}, +) where {P <: MPolyRingElem} return [ode_aux(ode, submodel) for submodel in submodels] end @@ -187,14 +187,14 @@ Example: y1(t) = x1(t), y2(t) = x2(t)) >find_submodels(ode) - ODE{fmpq_mpoly}[ + ODE{QQMPolyRingElem}[ x1'(t) = a(t)*x2(t)^2 + x1(t) y1(t) = x1(t) ] ``` """ -function find_submodels(ode::ODE{P}) where {P <: MPolyElem} +function find_submodels(ode::ODE{P}) where {P <: MPolyRingElem} graph = construct_graph(ode) ys = ode.y_vars xs = ode.x_vars diff --git a/src/util.jl b/src/util.jl index b6cb29d83..5ed817315 100644 --- a/src/util.jl +++ b/src/util.jl @@ -1,10 +1,10 @@ # ------------------------------------------------------------------------------ -function Nemo.vars(f::Generic.Frac{<:MPolyElem}) +function Nemo.vars(f::Generic.Frac{<:MPolyRingElem}) return collect(union(Set(vars(numerator(f))), Set(vars(denominator(f))))) end -function Nemo.total_degree(f::Generic.Frac{<:MPolyElem}) +function Nemo.total_degree(f::Generic.Frac{<:MPolyRingElem}) return sum(map(total_degree, unpack_fraction(f))) end @@ -36,13 +36,13 @@ end Evaluates a polynomial/rational function on a dictionary of type `var => val` and missing values are replaced with zeroes """ -function eval_at_dict(poly::P, d::Dict{P, <:RingElem}) where {P <: MPolyElem} +function eval_at_dict(poly::P, d::Dict{P, <:RingElem}) where {P <: MPolyRingElem} R = parent(first(values(d))) point = [get(d, v, zero(R)) for v in gens(parent(poly))] return evaluate(poly, point) end -function eval_at_dict(poly::P, d::Dict{P, S}) where {P <: MPolyElem, S <: Real} +function eval_at_dict(poly::P, d::Dict{P, S}) where {P <: MPolyRingElem, S <: Real} R = parent(poly) @assert R == parent(first(keys(d))) xs = gens(parent(first(keys(d)))) @@ -59,7 +59,10 @@ function eval_at_dict(poly::P, d::Dict{P, S}) where {P <: MPolyElem, S <: Real} return accum end -function eval_at_dict(rational::Generic.Frac{T}, d::Dict{T, V}) where {T <: MPolyElem, V} +function eval_at_dict( + rational::Generic.Frac{T}, + d::Dict{T, V}, +) where {T <: MPolyRingElem, V} f, g = unpack_fraction(rational) return eval_at_dict(f, d) / eval_at_dict(g, d) end @@ -67,7 +70,7 @@ end function eval_at_dict( rational::Generic.Frac{<:T}, d::Dict{T, <:RingElem}, -) where {T <: MPolyElem} +) where {T <: MPolyRingElem} f, g = unpack_fraction(rational) return eval_at_dict(f, d) * inv(eval_at_dict(g, d)) end @@ -75,24 +78,24 @@ end function eval_at_dict( rational::Generic.Frac{<:P}, d::Dict{<:P, <:Union{<:Generic.Frac, <:P}}, -) where {P <: MPolyElem} +) where {P <: MPolyRingElem} f, g = unpack_fraction(rational) return eval_at_dict(f, d) // eval_at_dict(g, d) end # ------------------------------------------------------------------------------ -function unpack_fraction(f::MPolyElem) +function unpack_fraction(f::MPolyRingElem) return (f, one(parent(f))) end -function unpack_fraction(f::Generic.Frac{<:MPolyElem}) +function unpack_fraction(f::Generic.Frac{<:MPolyRingElem}) return (numerator(f), denominator(f)) end # ------------------------------------------------------------------------------ -function simplify_frac(numer::P, denom::P) where {P <: MPolyElem} +function simplify_frac(numer::P, denom::P) where {P <: MPolyRingElem} gcd_sub = gcd(numer, denom) sub_numer = divexact(numer, gcd_sub) sub_denom = divexact(denom, gcd_sub) @@ -163,7 +166,7 @@ function make_substitution( var_sub::P, val_numer::P, val_denom::P, -) where {P <: MPolyElem} +) where {P <: MPolyRingElem} d = Nemo.degree(f, var_sub) result = 0 @@ -180,7 +183,7 @@ end function homogenize(fs) ring = parent(fs[1]) - newring, hom_vars = PolynomialRing( + newring, hom_vars = polynomial_ring( base_ring(ring), vcat("X0", map(string, gens(ring))), ordering = ordering(ring), @@ -202,7 +205,7 @@ end function dehomogenize(Fs) ring = parent(Fs[1]) - newring, dehom_vars = PolynomialRing( + newring, dehom_vars = polynomial_ring( base_ring(ring), map(string, gens(ring)[2:end]), ordering = ordering(ring), @@ -229,7 +232,7 @@ Output: - a polynomial in `new_ring` “equal” to `poly` """ function parent_ring_change( - poly::MPolyElem, + poly::MPolyRingElem, new_ring::MPolyRing; matching = :byname, shift = 0, @@ -279,7 +282,7 @@ function parent_ring_change( end function parent_ring_change( - f::Generic.Frac{<:MPolyElem}, + f::Generic.Frac{<:MPolyRingElem}, new_ring::MPolyRing; matching = :byname, ) @@ -301,7 +304,7 @@ Output: - `div`'s are divisors of `f` such that `f` is their product with certain powers - if `certainty` is true, `div` is ``Q``-irreducible """ -function uncertain_factorization(f::MPolyElem{fmpq}) +function uncertain_factorization(f::MPolyRingElem{QQFieldElem}) vars_f = vars(f) if isempty(vars_f) return Array{Tuple{typeof(f), Bool}, 1}() @@ -319,7 +322,7 @@ function uncertain_factorization(f::MPolyElem{fmpq}) while true plugin = rand(-3:3, length(gens(parent(f)))) if evaluate(mainvar_coeffs[end], plugin) != 0 - uni_ring, T = Nemo.PolynomialRing(base_ring(f), "T") + uni_ring, T = Nemo.polynomial_ring(base_ring(f), "T") f_uni = sum([evaluate(mainvar_coeffs[i + 1], plugin) * T^i for i in 0:d]) if !isone(gcd(f_uni, derivative(f_uni))) factor_out = gcd(f, derivative(f, main_var)) @@ -342,7 +345,7 @@ end # ------------------------------------------------------------------------------ -function fast_factor(poly::MPolyElem{fmpq}) +function fast_factor(poly::MPolyRingElem{QQFieldElem}) prelim_factors = uncertain_factorization(poly) cert_factors = map(pair -> pair[1], filter(f -> f[2], prelim_factors)) uncert_factors = map(pair -> pair[1], filter(f -> !f[2], prelim_factors)) @@ -375,7 +378,7 @@ Input: Output: - dictionary with keys being tuples of length `lenght(variables)` and values being polynomials in the variables other than those which are the coefficients at the corresponding monomials (in a smaller polynomial ring) """ -function extract_coefficients(poly::P, variables::Array{P, 1}) where {P <: MPolyElem} +function extract_coefficients(poly::P, variables::Array{P, 1}) where {P <: MPolyRingElem} xs = gens(parent(poly)) @assert all(in(xs), variables) cut_indices = map(v -> findfirst(x -> x == v, xs), variables) @@ -383,7 +386,7 @@ function extract_coefficients(poly::P, variables::Array{P, 1}) where {P <: MPoly coeff_vars = xs[coeff_indices] K = base_ring(parent(poly)) - new_ring, _ = Nemo.PolynomialRing(K, map(vv -> var_to_str(vv, xs = xs), coeff_vars)) + new_ring, _ = Nemo.polynomial_ring(K, map(vv -> var_to_str(vv, xs = xs), coeff_vars)) FieldType = elem_type(K) result = Dict{Vector{Int}, Tuple{Vector{Vector{Int}}, Vector{FieldType}}}() @@ -422,7 +425,7 @@ end # ------------------------------------------------------------------------------ -function var_to_str(v::MPolyElem; xs = gens(parent(v))) +function var_to_str(v::MPolyRingElem; xs = gens(parent(v))) ind = findfirst(vv -> vv == v, xs) return string(symbols(parent(v))[ind]) end @@ -470,7 +473,7 @@ end For a variable `v`, returns a variable in `ring` with the same name """ -function switch_ring(v::MPolyElem, ring::MPolyRing) +function switch_ring(v::MPolyRingElem, ring::MPolyRing) ind = findfirst(vv -> vv == v, gens(parent(v))) return str_to_var(string(symbols(parent(v))[ind]), ring) @@ -504,7 +507,7 @@ Finds the order of a differential polynomial `diffpoly` in a variable `prefix`, returns -1 is the variable does not appear """ -function difforder(diffpoly::MPolyElem, prefix::String) +function difforder(diffpoly::MPolyRingElem, prefix::String) orders = [-1] for v in vars(diffpoly) d = decompose_derivative(var_to_str(v), [prefix]) diff --git a/src/wronskian.jl b/src/wronskian.jl index d796f4758..85bee6870 100644 --- a/src/wronskian.jl +++ b/src/wronskian.jl @@ -20,7 +20,7 @@ function monomial_compress(io_equation, ode::ODE) return monomial_compress(io_equation, ode.parameters) end -function monomial_compress(io_equation, params::Array{<:MPolyElem, 1}) +function monomial_compress(io_equation, params::Array{<:MPolyRingElem, 1}) params_xs = isempty(params) ? empty(params) : gens(parent(first(params))) other_vars = [ v for v in gens(parent(io_equation)) if @@ -197,7 +197,10 @@ Output: Computes the Wronskians of io_equations """ -@timeit _to function wronskian(io_equations::Dict{P, P}, ode::ODE{P}) where {P <: MPolyElem} +@timeit _to function wronskian( + io_equations::Dict{P, P}, + ode::ODE{P}, +) where {P <: MPolyRingElem} @debug "Compressing monomials" termlists = [monomial_compress(ioeq, ode)[2] for ioeq in values(io_equations)] @debug "Matrix sizes $(map(length, termlists))" @@ -209,7 +212,7 @@ Computes the Wronskians of io_equations PRIME = 2^31 - 1 F = Nemo.Native.GF(PRIME) polyring_red, gens_red = - Nemo.PolynomialRing(F, map(var_to_str, gens(parent(termlists[1][1])))) + Nemo.polynomial_ring(F, map(var_to_str, gens(parent(termlists[1][1])))) termlists = [map(p -> parent_ring_change(p, polyring_red), tlist) for tlist in termlists] ode_red = reduce_ode_mod_p(ode, PRIME) @@ -223,7 +226,7 @@ Computes the Wronskians of io_equations ord, ) @debug "Computing the derivatives of the solution" - ps_ext = Dict{MPolyElem, Nemo.gfp_abs_series}()# Generic.AbsSeries}() + ps_ext = Dict{MPolyRingElem, Nemo.gfp_abs_series}()# Generic.AbsSeries}() for v in vcat(ode_red.y_vars, ode_red.u_vars) cur_ps = ps[v] for i in 0:length(ode_red.x_vars) diff --git a/test/RationalFunctionFields/normalforms.jl b/test/RationalFunctionFields/normalforms.jl index b8c503de7..40381e74c 100644 --- a/test/RationalFunctionFields/normalforms.jl +++ b/test/RationalFunctionFields/normalforms.jl @@ -63,7 +63,7 @@ if GROUP == "All" || GROUP == "Core" ### # Some arbitrary generators for the SLIQR model R, (b, e, In, S, Ninv, s, Q, g, u, a, y, L) = - PolynomialRing(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) + polynomial_ring(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) f = [ In // one(R), s // one(R), diff --git a/test/check_field_membership.jl b/test/check_field_membership.jl index 3c42a3ed0..72f8eec82 100644 --- a/test/check_field_membership.jl +++ b/test/check_field_membership.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Check field membership" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) @test field_contains( RationalFunctionField([[R(1), x + y], [R(1), x * y], [z, (x + y)^2]]), diff --git a/test/check_primality_zerodim.jl b/test/check_primality_zerodim.jl index 2ac4005d5..b9e880f1f 100644 --- a/test/check_primality_zerodim.jl +++ b/test/check_primality_zerodim.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Primality check (zerodim subroutine)" begin - R, (x, y) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y"]) + R, (x, y) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y"]) @test check_primality_zerodim([x^2 - 1, y^2 - 4]) == false diff --git a/test/common_ring.jl b/test/common_ring.jl index 68c3e5bde..acec21ecf 100644 --- a/test/common_ring.jl +++ b/test/common_ring.jl @@ -3,7 +3,7 @@ if GROUP == "All" || GROUP == "Core" ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) ioeqs = find_ioequations(ode) pbr = PBRepresentation(ode, ioeqs) - R, (y_2, y_5, c) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_2", "y(t)_5", "c"]) + R, (y_2, y_5, c) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_2", "y(t)_5", "c"]) p = y_2^2 + c * y_5 (r, der) = common_ring(p, pbr) @test Set(map(var_to_str, gens(r))) == @@ -19,7 +19,7 @@ if GROUP == "All" || GROUP == "Core" ioeqs = find_ioequations(ode) pbr = PBRepresentation(ode, ioeqs) R, (y1_0, y2_3, u_3) = - Nemo.PolynomialRing(Nemo.QQ, ["y1(t)_0", "y2(t)_3", "u(t)_3"]) + Nemo.polynomial_ring(Nemo.QQ, ["y1(t)_0", "y2(t)_3", "u(t)_3"]) p = y1_0 + y2_3 + u_3 (r, der) = common_ring(p, pbr) @test Set([var_to_str(v) for v in gens(r)]) == Set([ diff --git a/test/constructive_membership.jl b/test/constructive_membership.jl index d568a52b4..56bfe0636 100644 --- a/test/constructive_membership.jl +++ b/test/constructive_membership.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Constructive field membership" begin - R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) + R, (x,) = polynomial_ring(Nemo.QQ, ["x"]) generators = [x^2, x^3] to_be_reduced = [x^2, x, 3one(R), zero(R)] @@ -22,13 +22,13 @@ if GROUP == "All" || GROUP == "Core" cases = [] - R, (T1,) = PolynomialRing(Nemo.QQ, ["T1"]) + R, (T1,) = polynomial_ring(Nemo.QQ, ["T1"]) append!( cases, [(generators = [T1^2], to_be_reduced = [T1, T1^2], memberships = Bool[0, 1])], ) - R, (T1, t, _t) = PolynomialRing(Nemo.QQ, ["T1", "t", "_t"]) + R, (T1, t, _t) = polynomial_ring(Nemo.QQ, ["T1", "t", "_t"]) append!( cases, [( @@ -38,7 +38,7 @@ if GROUP == "All" || GROUP == "Core" )], ) - R, (x,) = PolynomialRing(Nemo.QQ, ["x"]) + R, (x,) = polynomial_ring(Nemo.QQ, ["x"]) append!( cases, [ @@ -60,7 +60,7 @@ if GROUP == "All" || GROUP == "Core" ], ) - R, (x, y, z) = PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + R, (x, y, z) = polynomial_ring(Nemo.QQ, ["x", "y", "z"]) append!( cases, [ @@ -84,7 +84,7 @@ if GROUP == "All" || GROUP == "Core" # NOTE: in this case it actually matter to cancel out the gcd after # computing the normal forms - R, (a, b, y, x2, c, x1) = PolynomialRing(Nemo.QQ, ["a", "b", "y", "x2", "c", "x1"]) + R, (a, b, y, x2, c, x1) = polynomial_ring(Nemo.QQ, ["a", "b", "y", "x2", "c", "x1"]) append!( cases, [ diff --git a/test/diff_sequence_solution.jl b/test/diff_sequence_solution.jl index c23475807..143654053 100644 --- a/test/diff_sequence_solution.jl +++ b/test/diff_sequence_solution.jl @@ -7,13 +7,13 @@ if GROUP == "All" || GROUP == "Core" ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, num_terms::Int, - ) where {T <: Generic.FieldElem, P <: MPolyElem{T}} + ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} newvars = [var_to_str(v) for v in gens(dds.poly_ring)] append!( newvars, [var_to_str(v) * "$i" for v in dds.u_vars for i in 1:num_terms], ) - R, _ = StructuralIdentifiability.Nemo.PolynomialRing( + R, _ = StructuralIdentifiability.Nemo.polynomial_ring( base_ring(dds.poly_ring), newvars, ) diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index f9a191bae..9d978b6e0 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -10,7 +10,7 @@ if GROUP == "All" || GROUP == "Core" [var_to_str(u) * "_$i" for u in ode.u_vars for i in 0:(prec - 1)], ) end - new_ring, vars = Nemo.PolynomialRing(base_ring(ode.poly_ring), new_varnames) + new_ring, vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_varnames) # mapping everything to the new ring eval_point = Dict(v => switch_ring(v, new_ring) for v in gens(ode.poly_ring)) @@ -108,7 +108,7 @@ if GROUP == "All" || GROUP == "Core" @testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin test_cases = [] - P = fmpq_mpoly + P = QQMPolyRingElem DType = Union{P, Generic.Frac{P}} ode = @ODEmodel(x'(t) = x(t) + a, y(t) = x(t)^2) @@ -118,7 +118,7 @@ if GROUP == "All" || GROUP == "Core" :ODE => ode, :ic => Dict(x => Nemo.QQ(rand(1:10))), :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), :prec => 20, ), ) @@ -130,7 +130,7 @@ if GROUP == "All" || GROUP == "Core" :ODE => ode, :ic => Dict(x => Nemo.QQ(rand(1:10))), :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), :prec => 20, ), ) @@ -147,7 +147,7 @@ if GROUP == "All" || GROUP == "Core" :ODE => ode, :ic => Dict(x => Nemo.QQ(rand(1:10)), y => Nemo.QQ(rand(1:10))), :param_vals => Dict(a => Nemo.QQ(rand(1:10)), b => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{fmpq, 1}}(), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), :prec => 8, ), ) @@ -174,7 +174,7 @@ if GROUP == "All" || GROUP == "Core" ["u_$i" for i in 1:2], ["y_$i" for i in 1:3], ) - R, vars = Nemo.PolynomialRing(F, varnames) + R, vars = Nemo.polynomial_ring(F, varnames) push!( test_cases, Dict( @@ -196,7 +196,7 @@ if GROUP == "All" || GROUP == "Core" ["u_$i" for i in 1:2], ["y_$i" for i in 1:3], ) - R, vars = Nemo.PolynomialRing(F, varnames) + R, vars = Nemo.polynomial_ring(F, varnames) push!( test_cases, Dict( @@ -213,7 +213,7 @@ if GROUP == "All" || GROUP == "Core" ) varnames = vcat(["x_$i" for i in 1:2], ["p_$i" for i in 1:2], "u", ["y_1", "y_2"]) - R, vars = Nemo.PolynomialRing(F, varnames) + R, vars = Nemo.polynomial_ring(F, varnames) push!( test_cases, Dict( diff --git a/test/diffreduction.jl b/test/diffreduction.jl index 338a29be8..556a44d5c 100644 --- a/test/diffreduction.jl +++ b/test/diffreduction.jl @@ -2,7 +2,7 @@ if GROUP == "All" || GROUP == "Core" @testset "Differential reduction" begin ode = @ODEmodel(x'(t) = a * x(t), y(t) = x(t)) pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4"]) + R, (y_5, y_4) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4"]) res = diffreduce(y_5, pbr) @test res == str_to_var("a", parent(res))^5 * str_to_var("y(t)_0", parent(res)) res = diffreduce(y_4, pbr) @@ -12,7 +12,7 @@ if GROUP == "All" || GROUP == "Core" ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = -x1(t), y(t) = x1(t) + u(t)) pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4, u_10) = Nemo.PolynomialRing(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) + R, (y_5, y_4, u_10) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) res = diffreduce(y_4 + u_10, pbr) @test res == str_to_var("u(t)_10", parent(res)) + str_to_var("y(t)_0", parent(res)) - @@ -21,7 +21,7 @@ if GROUP == "All" || GROUP == "Core" # Next two verified with Maple # Mizuka's example R, (y_0, y_1, y_2, y_3, u_0, u_1, u_2) = - Nemo.PolynomialRing(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) + Nemo.polynomial_ring(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) pbr = PBRepresentation( ["y"], ["u"], @@ -130,7 +130,7 @@ if GROUP == "All" || GROUP == "Core" y(t) = x1(t) ) pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.PolynomialRing( + R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.polynomial_ring( Nemo.QQ, ["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "u(t)_0", "u(t)_3"], ) diff --git a/test/extract_coefficients.jl b/test/extract_coefficients.jl index 7237ac142..a6092a23f 100644 --- a/test/extract_coefficients.jl +++ b/test/extract_coefficients.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Coefficient extraction for rational fucntions" begin - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) C = extract_coefficients_ratfunc( (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), [z], @@ -15,12 +15,12 @@ if GROUP == "All" || GROUP == "Core" (x^2 + 1) // 1, ]) - R, (x, y) = PolynomialRing(QQ, ["x", "y"]) + R, (x, y) = polynomial_ring(QQ, ["x", "y"]) f = (x^2 + y^2) // (1 - x - 3 * y) - @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.fmpq_mpoly}())) == + @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.QQMPolyRingElem}())) == Set([f, one(R) // 1]) - R, (x, y, u, v) = PolynomialRing(QQ, ["x", "y", "u", "v"]) + R, (x, y, u, v) = polynomial_ring(QQ, ["x", "y", "u", "v"]) C = extract_coefficients_ratfunc( (x + (y + 3) * u * v + y^2 * v^3) // (u + 3 * v - (x^2 + y^2) * u^2), [u, v], @@ -36,21 +36,21 @@ if GROUP == "All" || GROUP == "Core" end @testset "Coefficient extraction for polynomials" begin - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) C = extract_coefficients((y + z + 8), [x]) R_coef = parent(first(values(C))) y, z = gens(R_coef) @test symbols(R_coef) == [:y, :z] @test C == Dict([0] => y + z + 8) - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [z]) R_coef = parent(first(values(C))) x, y = gens(R_coef) @test symbols(R_coef) == [:x, :y] @test C == Dict([3] => 3x - y^2, [1] => y, [0] => x^2) - R, (x, y, z) = PolynomialRing(QQ, ["x", "y", "z"]) + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [x, z]) R_coef = parent(first(values(C))) y = gens(R_coef)[1] diff --git a/test/find_leader.jl b/test/find_leader.jl index 36dd41c40..de9409632 100644 --- a/test/find_leader.jl +++ b/test/find_leader.jl @@ -11,7 +11,7 @@ if GROUP == "All" || GROUP == "Core" println("IOEQS: ", ioeqs) pbr = PBRepresentation(ode, ioeqs) - R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.PolynomialRing( + R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.polynomial_ring( Nemo.QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y2(t)_0", "y2(t)_1", "y2(t)_2"], ) diff --git a/test/io_cases.jl b/test/io_cases.jl index 00dc85366..762f1c55f 100644 --- a/test/io_cases.jl +++ b/test/io_cases.jl @@ -3,7 +3,7 @@ if GROUP == "All" || GROUP == "Core" test_cases = [] # 2-compartiment model - R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = PolynomialRing( + R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = polynomial_ring( QQ, ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], ) @@ -24,7 +24,7 @@ if GROUP == "All" || GROUP == "Core" #--------------------------------------- # Chen-Lee model R, (y1_0, y1_1, y1_2, y1_3, a, b, c) = - PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) + polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) correct = 9 * y1_3^2 * y1_0^2 - 18 * y1_3 * y1_2 * y1_1 * y1_0 - 18 * y1_3 * y1_2 * y1_0^2 * a - 36 * y1_3 * y1_2 * y1_0^2 * b - @@ -124,7 +124,7 @@ if GROUP == "All" || GROUP == "Core" # predator-prey model R, (y1_0, y1_1, y1_2, a, b, c, d) = - PolynomialRing(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) + polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) correct = y1_2 * y1_0 - y1_1^2 - y1_1 * y1_0^2 * d + y1_1 * y1_0 * c + y1_0^3 * a * d - y1_0^2 * a * c diff --git a/test/lc_univariate.jl b/test/lc_univariate.jl index 0c442a97d..532b2a50c 100644 --- a/test/lc_univariate.jl +++ b/test/lc_univariate.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Univariate leading coefficient" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) p = x^2 * y + x^2 * (z + z^3) + y - 5 @test lc_univariate(p, x) == y + z + z^3 @test lc_univariate(p, z) == x^2 diff --git a/test/linear_compartment.jl b/test/linear_compartment.jl index 7d244f59e..1b2e48bda 100644 --- a/test/linear_compartment.jl +++ b/test/linear_compartment.jl @@ -158,7 +158,7 @@ if GROUP == "All" || GROUP == "Core" case[:leaks], ) bring = ode.poly_ring - correct = Dict{fmpq_mpoly, Symbol}() + correct = Dict{QQMPolyRingElem, Symbol}() for (e, id) in case[:result] correct[str_to_var("a_$(e[2])_$(e[1])", bring)] = id end diff --git a/test/local_identifiability_me.jl b/test/local_identifiability_me.jl index 769402e25..e14eb2f46 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -29,13 +29,16 @@ if GROUP == "All" || GROUP == "Core" push!(edges_vars_names, "a_$(s)_0") push!(edges_vars_names, "b_$(s)_0") end - R, vars = - Nemo.PolynomialRing(Nemo.QQ, vcat(x_vars_names, edges_vars_names, ["y1", "y2"])) + R, vars = Nemo.polynomial_ring( + Nemo.QQ, + vcat(x_vars_names, edges_vars_names, ["y1", "y2"]), + ) x_vars = vars[2:(n + 1)] x0 = vars[1] - equations = Dict{fmpq_mpoly, Union{fmpq_mpoly, Generic.Frac{fmpq_mpoly}}}( - x => R(0) for x in x_vars - ) + equations = + Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}}( + x => R(0) for x in x_vars + ) equations[x0] = R(0) for i in 1:n for j in graph[i] @@ -53,10 +56,10 @@ if GROUP == "All" || GROUP == "Core" equations[x_vars[i]] += -x_vars[i] * rate end end - return ODE{fmpq_mpoly}( + return ODE{QQMPolyRingElem}( equations, Dict(vars[end] => x_vars[1], vars[end - 1] => x0), - Array{fmpq_mpoly, 1}(), + Array{QQMPolyRingElem, 1}(), ) end diff --git a/test/monomial_compress.jl b/test/monomial_compress.jl index f7e378b9a..2eded4cc4 100644 --- a/test/monomial_compress.jl +++ b/test/monomial_compress.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Monomial compression test" begin - R, (v1, v2, v3, v4) = Nemo.PolynomialRing(Nemo.QQ, ["v1", "v2", "v3", "v4"]) + R, (v1, v2, v3, v4) = Nemo.polynomial_ring(Nemo.QQ, ["v1", "v2", "v3", "v4"]) tests = [ v1 + v2 - 2, v4 + v4 * v1 - 3 * v2 * v4, diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index 09145f6a6..dc2e17da2 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -21,17 +21,17 @@ if GROUP == "All" || GROUP == "Core" end @testset "Power series solution for an ODE system" begin - R, (x, x_dot) = Nemo.PolynomialRing(Nemo.QQ, ["x", "x_dot"]) + R, (x, x_dot) = Nemo.polynomial_ring(Nemo.QQ, ["x", "x_dot"]) exp_t = ps_ode_solution( [x_dot - x], - Dict{fmpq_mpoly, fmpq}(x => 1), - Dict{fmpq_mpoly, Array{fmpq, 1}}(), + Dict{QQMPolyRingElem, QQFieldElem}(x => 1), + Dict{QQMPolyRingElem, Array{QQFieldElem, 1}}(), 20, )[x] @test valuation(ps_diff(exp_t) - exp_t) == 19 R, (x, y, x_dot, y_dot, u) = - Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) + Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) prec = 100 eqs = [x_dot - x + 3 * x * y - u, y_dot + 2 * y - 4 * x * y] u_coeff = [rand(1:5) for i in 1:prec] @@ -51,7 +51,7 @@ if GROUP == "All" || GROUP == "Core" ["x_$i" for i in 1:NUMX], ["u_$i" for i in 1:NUMU], ) - R, vars = Nemo.PolynomialRing(F, varnames) + R, vars = Nemo.polynomial_ring(F, varnames) # Generating the initial conditions and inputs ic = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMX) @@ -88,7 +88,7 @@ if GROUP == "All" || GROUP == "Core" ["p_$i" for i in 1:NUMP], ["u_$i" for i in 1:NUMU], ) - R, vars = Nemo.PolynomialRing(F, varnames) + R, vars = Nemo.polynomial_ring(F, varnames) PType = gfp_mpoly TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} diff --git a/test/paradigm_shift.jl b/test/paradigm_shift.jl index ed1c009f9..6a76f2e06 100644 --- a/test/paradigm_shift.jl +++ b/test/paradigm_shift.jl @@ -23,7 +23,8 @@ if GROUP == "All" || GROUP == "Core" ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) old_var_ic = Dict(old_vars .=> ic_point) input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) - old_input_ts = Dict{eltype(old_vars), Vector{Nemo.fmpq}}(old_inputs .=> input_ts) + old_input_ts = + Dict{eltype(old_vars), Vector{Nemo.QQFieldElem}}(old_inputs .=> input_ts) old_solutions = StructuralIdentifiability.power_series_solution( old_ode, @@ -48,7 +49,7 @@ if GROUP == "All" || GROUP == "Core" merge(old_param_spec, old_var_ic), ) for new_var in new_vars ) - new_input_ts = Dict{eltype(new_vars), Vector{Nemo.fmpq}}( + new_input_ts = Dict{eltype(new_vars), Vector{Nemo.QQFieldElem}}( new_input => old_input_ts[numerator(var_mapping[new_input])] for new_input in new_inputs ) diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index 97fd4ac07..4fa0a58f9 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -1,10 +1,10 @@ if GROUP == "All" || GROUP == "Core" @testset "Parent ring change" begin for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] - R, (z, x, y) = PolynomialRing(QQ, ["z", "x", "y"], ordering = :degrevlex) - R_, (y_, x_) = PolynomialRing(QQ, ["y", "x"], ordering = :lex) + R, (z, x, y) = polynomial_ring(QQ, ["z", "x", "y"], ordering = :degrevlex) + R_, (y_, x_) = polynomial_ring(QQ, ["y", "x"], ordering = :lex) R__, (x__, t__, y__, z__) = - PolynomialRing(QQ, ["x", "t", "y", "z"], ordering = :deglex) + polynomial_ring(QQ, ["x", "t", "y", "z"], ordering = :deglex) f = 2x + 3y + x^7 * y @test f == @@ -42,9 +42,9 @@ if GROUP == "All" || GROUP == "Core" @test f__ == StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) - R, (x,) = PolynomialRing(field, ["x"]) - R2, (x2,) = PolynomialRing(Nemo.FractionField(R), ["x"]) - R3, (x3,) = PolynomialRing(field, ["x"]) + R, (x,) = polynomial_ring(field, ["x"]) + R2, (x2,) = polynomial_ring(Nemo.FractionField(R), ["x"]) + R3, (x3,) = polynomial_ring(field, ["x"]) f = x3^2 f_ = StructuralIdentifiability.parent_ring_change(f, R2) @test parent(f) == R3 diff --git a/test/pseudodivision.jl b/test/pseudodivision.jl index 9fdf157a5..dec930243 100644 --- a/test/pseudodivision.jl +++ b/test/pseudodivision.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Pseudodivision" begin - R, (x, y, z) = Nemo.PolynomialRing(Nemo.QQ, ["x", "y", "z"]) + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) @test iszero(pseudodivision(x^2 - y^2, x - y, x)) @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x diff --git a/test/sequence_solution.jl b/test/sequence_solution.jl index 128a5c7df..70b37f719 100644 --- a/test/sequence_solution.jl +++ b/test/sequence_solution.jl @@ -5,9 +5,9 @@ if GROUP == "All" || GROUP == "Core" sol = sequence_solution( ode, - Dict{fmpq_mpoly, Int}(), + Dict{QQMPolyRingElem, Int}(), Dict(f0 => 1, f1 => 1), - Dict{fmpq_mpoly, Array{Int, 1}}(), + Dict{QQMPolyRingElem, Array{Int, 1}}(), 10, ) @test sol[f1] == [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] From 9bd79ecd325f3b4bd84dfcb3c82ebc3785cb369e Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 21 Jan 2024 16:52:19 +0100 Subject: [PATCH 53/82] fixing depreciation warnings, part2 --- src/RationalFunctionFields/IdealMQS.jl | 4 ++-- src/discrete.jl | 2 +- src/elimination.jl | 6 +++--- src/local_identifiability.jl | 14 +++++++------- src/power_series_utils.jl | 10 +++++----- src/primality_check.jl | 4 ++-- src/util.jl | 2 +- src/wronskian.jl | 2 +- test/det_minor_expansion.jl | 2 +- test/ps_diff.jl | 2 +- test/ps_integrate.jl | 2 +- test/ps_inverse.jl | 4 ++-- test/ps_matrix_homlinear.jl | 6 +++--- test/ps_matrix_linear.jl | 6 +++--- test/ps_matrix_log.jl | 8 ++++---- 15 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/RationalFunctionFields/IdealMQS.jl b/src/RationalFunctionFields/IdealMQS.jl index 519254fa7..95b9fa060 100644 --- a/src/RationalFunctionFields/IdealMQS.jl +++ b/src/RationalFunctionFields/IdealMQS.jl @@ -229,7 +229,7 @@ end function ParamPunPam.reduce_mod_p!( mqs::IdealMQS, ff::Field, -) where {Field <: Union{Nemo.GaloisField, Nemo.FpField}} +) where {Field <: Union{Nemo.fpField, Nemo.FpField}} @debug "Reducing MQS ideal modulo $(ff)" # If there is a reduction modulo this field already, if haskey(mqs.cached_nums_gf, ff) @@ -251,7 +251,7 @@ function ParamPunPam.specialize_mod_p( mqs::IdealMQS, point::Vector{T}; saturated = true, -) where {T <: Union{fpFieldElem, gfp_fmpz_elem}} +) where {T <: Union{fpFieldElem, FpFieldElem}} K_1 = parent(first(point)) @debug "Evaluating MQS ideal over $K_1 at $point" @assert haskey(mqs.cached_nums_gf, K_1) diff --git a/src/discrete.jl b/src/discrete.jl index 3c2db3dc5..9d9117931 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -239,7 +239,7 @@ function _assess_local_identifiability_discrete_aux( @debug "Building the matrices" Jac = zero( - Nemo.MatrixSpace( + Nemo.matrix_space( bring, length(dds.x_vars) + length(dds.parameters), 1 + prec * length(dds.y_vars) + length(known_ic), diff --git a/src/elimination.jl b/src/elimination.jl index 24978ca1b..426bd924a 100644 --- a/src/elimination.jl +++ b/src/elimination.jl @@ -65,7 +65,7 @@ function Bezout_matrix(f::P, g::P, var_elim::P) where {P <: MPolyRingElem} n = max(deg_f, deg_g) coeffs_f = [coeff(f, [var_elim], [i]) for i in 0:n] coeffs_g = [coeff(g, [var_elim], [i]) for i in 0:n] - GL = AbstractAlgebra.MatrixSpace(parent_ring, n, n) + GL = AbstractAlgebra.matrix_space(parent_ring, n, n) M = zero(GL) for i in 1:n for j in 1:n @@ -96,7 +96,7 @@ function Sylvester_matrix(f::P, g::P, var_elim::P) where {P <: MPolyRingElem} deg_f = Nemo.degree(f, var_elim) deg_g = Nemo.degree(g, var_elim) n = deg_f + deg_g - GL = AbstractAlgebra.MatrixSpace(parent_ring, n, n) + GL = AbstractAlgebra.matrix_space(parent_ring, n, n) M = zero(GL) for i in 1:deg_f for j in 0:deg_g @@ -384,7 +384,7 @@ Output: flush(_si_logger[].stream) M_simp, matrix_factors = simplify_matrix(M) @debug "Removed factors $(map(length, matrix_factors))" - M_size = zero(Nemo.MatrixSpace(Nemo.ZZ, ncols(M_simp), ncols(M_simp))) + M_size = zero(Nemo.matrix_space(Nemo.ZZ, ncols(M_simp), ncols(M_simp))) for i in 1:ncols(M_simp) for j in 1:ncols(M_simp) M_size[i, j] = length(M_simp[i, j]) diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index 2580d2de2..875fe7dd2 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -41,12 +41,12 @@ function differentiate_solution( @debug "Building the variational system at the solution" # Y' = AY + B vars = vcat(ode.x_vars, ode.parameters) - SA = AbstractAlgebra.MatrixSpace(ps_ring, length(ode.x_vars), length(ode.x_vars)) + SA = AbstractAlgebra.matrix_space(ps_ring, length(ode.x_vars), length(ode.x_vars)) A = SA([ eval_at_dict(derivative(ode.x_equations[vars[i]], vars[j]), ps_sol) for i in 1:length(ode.x_vars), j in 1:length(ode.x_vars) ]) - SB = AbstractAlgebra.MatrixSpace(ps_ring, length(ode.x_vars), length(vars)) + SB = AbstractAlgebra.matrix_space(ps_ring, length(ode.x_vars), length(vars)) B = zero(SB) for i in 1:length(ode.x_vars) for j in (length(ode.x_vars) + 1):length(vars) @@ -55,7 +55,7 @@ function differentiate_solution( end # TODO: make use of one() function (problems modulo prime) initial_condition = - zero(Nemo.MatrixSpace(base_ring(ode.poly_ring), length(ode.x_vars), length(vars))) + zero(Nemo.matrix_space(base_ring(ode.poly_ring), length(ode.x_vars), length(vars))) for i in 1:length(ode.x_vars) initial_condition[i, i] = 1 end @@ -247,7 +247,7 @@ function _assess_local_identifiability( num_exp = 0 # rows are the "parameters": parameters and initial conditions # columns are "observations": derivatives of the outputs - Jac = zero(Nemo.MatrixSpace(F, length(ode.parameters), 1)) + Jac = zero(Nemo.matrix_space(F, length(ode.parameters), 1)) output_derivatives = undef # the while loop is primarily for ME-deintifiability, it is adding replicas until the rank stabilizes # in the SE case, it will exit right away @@ -261,10 +261,10 @@ function _assess_local_identifiability( output_derivatives = differentiate_output(ode_red, params_vals, ic, inputs, prec) @debug "Building the matrices" - newJac = vcat(Jac, zero(Nemo.MatrixSpace(F, length(ode.x_vars), ncols(Jac)))) + newJac = vcat(Jac, zero(Nemo.matrix_space(F, length(ode.x_vars), ncols(Jac)))) newJac = hcat( newJac, - zero(Nemo.MatrixSpace(F, nrows(newJac), prec * length(ode.y_vars))), + zero(Nemo.matrix_space(F, nrows(newJac), prec * length(ode.y_vars))), ) xs_params = vcat(ode_red.x_vars, ode_red.parameters) for (i, y) in enumerate(ode.y_vars) @@ -300,7 +300,7 @@ function _assess_local_identifiability( if !isnothing(trbasis) @debug "Transcendence basis computation requested" - reverted_Jac = zero(Nemo.MatrixSpace(F, size(Jac)[2], size(Jac)[1])) + reverted_Jac = zero(Nemo.matrix_space(F, size(Jac)[2], size(Jac)[1])) for i in 1:size(Jac)[1] for j in 1:size(Jac)[2] reverted_Jac[j, i] = Jac[size(Jac)[1] - i + 1, j] diff --git a/src/power_series_utils.jl b/src/power_series_utils.jl index 0e2e8e615..ac4429243 100644 --- a/src/power_series_utils.jl +++ b/src/power_series_utils.jl @@ -205,7 +205,7 @@ function ps_matrix_linear_de( ) where {T <: Generic.FieldElem} prec = (prec == -1) ? precision(A[1, 1]) : prec n = nrows(A) - identity = one(AbstractAlgebra.MatrixSpace(base_ring(parent(Y0)), n, n)) + identity = one(AbstractAlgebra.matrix_space(base_ring(parent(Y0)), n, n)) Yh, Zh = ps_matrix_homlinear_de(A, identity, prec) matrix_set_precision!(Zh, prec) return _variation_of_constants(A, B, Yh, Zh, Y0, prec) @@ -234,9 +234,9 @@ function ps_ode_solution( ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} n = length(equations) ring = parent(equations[1]) - S = AbstractAlgebra.MatrixSpace(ring, n, n) - Sv = AbstractAlgebra.MatrixSpace(ring, n, 1) - Svconst = AbstractAlgebra.MatrixSpace(base_ring(ring), n, 1) + S = AbstractAlgebra.matrix_space(ring, n, n) + Sv = AbstractAlgebra.matrix_space(ring, n, 1) + Svconst = AbstractAlgebra.matrix_space(base_ring(ring), n, 1) eqs = Sv(equations) x_vars = filter(v -> ("$(v)_dot" in map(string, gens(ring))), gens(ring)) @@ -246,7 +246,7 @@ function ps_ode_solution( Jac_dots = S([derivative(p, xd) for p in equations, xd in x_dot_vars]) Jac_xs = S([derivative(p, x) for p in equations, x in x_vars]) - ps_ring, t = PowerSeriesRing(base_ring(ring), prec, "t"; model = :capped_absolute) + ps_ring, t = power_series_ring(base_ring(ring), prec, "t"; model = :capped_absolute) solution = Dict() for (u, coeffs) in inputs solution[u] = sum(coeffs[i] * t^(i - 1) for i in 1:length(coeffs)) diff --git a/src/primality_check.jl b/src/primality_check.jl index e236849d5..acccf0ebe 100644 --- a/src/primality_check.jl +++ b/src/primality_check.jl @@ -4,7 +4,7 @@ function check_primality_zerodim(J::Array{QQMPolyRingElem, 1}) J = Groebner.groebner(J, loglevel = _groebner_loglevel[]) basis = Groebner.kbase(J, loglevel = _groebner_loglevel[]) dim = length(basis) - S = Nemo.MatrixSpace(Nemo.QQ, dim, dim) + S = Nemo.matrix_space(Nemo.QQ, dim, dim) matrices = [] @debug "$J $basis" @debug "Dim is $dim" @@ -25,7 +25,7 @@ function check_primality_zerodim(J::Array{QQMPolyRingElem, 1}) R, t = Nemo.polynomial_ring(Nemo.QQ, "t") @debug "$(Nemo.charpoly(R, generic_multiplication))" - return Nemo.isirreducible(Nemo.charpoly(R, generic_multiplication)) + return Nemo.is_irreducible(Nemo.charpoly(R, generic_multiplication)) end #------------------------------------------------------------------------------ diff --git a/src/util.jl b/src/util.jl index 5ed817315..919c2bb69 100644 --- a/src/util.jl +++ b/src/util.jl @@ -333,7 +333,7 @@ function uncertain_factorization(f::MPolyRingElem{QQFieldElem}) f = divexact(f, factor_out) f_uni = divexact(f_uni, gcd(f_uni, derivative(f_uni))) end - is_irr = Nemo.isirreducible(f_uni) + is_irr = Nemo.is_irreducible(f_uni) break end end diff --git a/src/wronskian.jl b/src/wronskian.jl index 85bee6870..cca62bc5a 100644 --- a/src/wronskian.jl +++ b/src/wronskian.jl @@ -240,7 +240,7 @@ Computes the Wronskians of io_equations for (i, tlist) in enumerate(termlists) n = length(tlist) evaled = massive_eval(tlist, ps_ext) - S = Nemo.MatrixSpace(F, n, n) + S = Nemo.matrix_space(F, n, n) W = zero(S) for (i, ps) in enumerate(evaled) for j in 1:n diff --git a/test/det_minor_expansion.jl b/test/det_minor_expansion.jl index b21509f59..42a84069a 100644 --- a/test/det_minor_expansion.jl +++ b/test/det_minor_expansion.jl @@ -2,7 +2,7 @@ if GROUP == "All" || GROUP == "Core" @testset "Determinant by minor expansion" begin for d in 1:5 for testcase in 1:10 - mat_space = Nemo.MatrixSpace(Nemo.QQ, d, d) + mat_space = Nemo.matrix_space(Nemo.QQ, d, d) rnd_matrix = mat_space([mod(rand(Int), 1000) for i in 1:d, j in 1:d]) @test det(rnd_matrix) == det_minor_expansion(rnd_matrix) end diff --git a/test/ps_diff.jl b/test/ps_diff.jl index c0c1795dc..9368ac858 100644 --- a/test/ps_diff.jl +++ b/test/ps_diff.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Power Series Differentiation" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) @test ps_diff(zero(T)) == zero(T) diff --git a/test/ps_integrate.jl b/test/ps_integrate.jl index cca7e34f3..79ae5b692 100644 --- a/test/ps_integrate.jl +++ b/test/ps_integrate.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Power series integration" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) @test ps_integrate(zero(T)) == zero(T) diff --git a/test/ps_inverse.jl b/test/ps_inverse.jl index 282e9bf07..076e891ad 100644 --- a/test/ps_inverse.jl +++ b/test/ps_inverse.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Power series matrix inverse" begin - T, t = Nemo.PowerSeriesRing( + T, t = Nemo.power_series_ring( Nemo.Native.GF(2^31 - 1), 50, "t"; @@ -8,7 +8,7 @@ if GROUP == "All" || GROUP == "Core" ) for d in 1:5 - S = Nemo.MatrixSpace(T, d, d) + S = Nemo.matrix_space(T, d, d) for case in 1:20 M = S([random_ps(T) for i in 1:d, j in 1:d]) while isequal( diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index be2aebaf7..dfb3a916f 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Homogeneous linear differential equations" begin - T, t = Nemo.PowerSeriesRing( + T, t = Nemo.power_series_ring( Nemo.Native.GF(2^31 - 1), 300, "t"; @@ -9,9 +9,9 @@ if GROUP == "All" || GROUP == "Core" for d in 1:5 for c in 1:5 - S = Nemo.MatrixSpace(T, d, d) + S = Nemo.matrix_space(T, d, d) A = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) + Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index 31d5a2822..ead2add44 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,6 +1,6 @@ if GROUP == "All" || GROUP == "Core" @testset "Linear differential equations" begin - T, t = Nemo.PowerSeriesRing( + T, t = Nemo.power_series_ring( Nemo.Native.GF(2^31 - 1), 300, "t"; @@ -9,10 +9,10 @@ if GROUP == "All" || GROUP == "Core" for d in 1:5 for c in 1:5 - S = Nemo.MatrixSpace(T, d, d) + S = Nemo.matrix_space(T, d, d) A = random_ps_matrix(T, S) B = random_ps_matrix(T, S) - Sconst = Nemo.MatrixSpace(Nemo.Native.GF(2^31 - 1), d, d) + Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) @time sol = ps_matrix_linear_de(A, B, Y0) to_be_zero = map(ps_diff, sol) - A * sol - B diff --git a/test/ps_matrix_log.jl b/test/ps_matrix_log.jl index b4a27aa6d..b01a95ad5 100644 --- a/test/ps_matrix_log.jl +++ b/test/ps_matrix_log.jl @@ -1,10 +1,10 @@ if GROUP == "All" || GROUP == "Core" @testset "Logarith of power series matrices" begin - T, t = Nemo.PowerSeriesRing(Nemo.QQ, 300, "t"; model = :capped_absolute) + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) for d in 1:5 diag_elements = [1 + t * random_ps(T) for i in 1:d] - S = Nemo.MatrixSpace(T, d, d) + S = Nemo.matrix_space(T, d, d) M = S([(i == j ? diag_elements[i] : T(0)) for i in 1:d, j in 1:d]) result = ps_matrix_log(M) correct = S([(i == j ? log(diag_elements[i]) : T(0)) for i in 1:d, j in 1:d]) @@ -13,7 +13,7 @@ if GROUP == "All" || GROUP == "Core" for c in 1:5 f = random_ps(T) * t - S = Nemo.MatrixSpace(T, 2, 2) + S = Nemo.matrix_space(T, 2, 2) m = S([ T(1) f T(0) T(1) @@ -27,7 +27,7 @@ if GROUP == "All" || GROUP == "Core" for c in 1:5 f, g = random_ps(T) * t, random_ps(T) * t - S = Nemo.MatrixSpace(T, 3, 3) + S = Nemo.matrix_space(T, 3, 3) m = S([ T(1) f g T(0) T(1) f From 2c1e7482c84e6466446c7c7462679849930c6b1b Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 21 Jan 2024 20:15:21 +0100 Subject: [PATCH 54/82] last warnings --- src/RationalFunctionFields/normalforms.jl | 2 +- src/local_identifiability.jl | 2 +- src/parametrizations.jl | 2 +- test/differentiate_output.jl | 28 +---------------------- test/ode_ps_solution.jl | 23 +------------------ test/parent_ring_change.jl | 2 +- test/runtests.jl | 21 +++++++++++++++++ 7 files changed, 27 insertions(+), 53 deletions(-) diff --git a/src/RationalFunctionFields/normalforms.jl b/src/RationalFunctionFields/normalforms.jl index 530518487..9477fe7f0 100644 --- a/src/RationalFunctionFields/normalforms.jl +++ b/src/RationalFunctionFields/normalforms.jl @@ -319,7 +319,7 @@ is not specified but is assumed to be close to 1. push!(relations_ff_1, monoms_ff_1[i]) push!(tref, exponent_vector(monoms_ff_1[i], 1)) end - complete_intersection_relations_ff = Vector{Nemo.gfp_mpoly}(undef, 0) + complete_intersection_relations_ff = Vector{Nemo.fpMPolyRingElem}(undef, 0) iters = 0 # Compute relations at several random points until a consensus is reached while true diff --git a/src/local_identifiability.jl b/src/local_identifiability.jl index 875fe7dd2..a42a24098 100644 --- a/src/local_identifiability.jl +++ b/src/local_identifiability.jl @@ -253,7 +253,7 @@ function _assess_local_identifiability( # in the SE case, it will exit right away while true ic = Dict(x => F(rand(1:prime)) for x in ode_red.x_vars) - inputs = Dict{Nemo.gfp_mpoly, Array{Nemo.fpFieldElem, 1}}( + inputs = Dict{Nemo.fpMPolyRingElem, Array{Nemo.fpFieldElem, 1}}( u => [F(rand(1:prime)) for i in 1:prec] for u in ode_red.u_vars ) diff --git a/src/parametrizations.jl b/src/parametrizations.jl index c725c914c..94b385858 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -237,7 +237,7 @@ $sat_string Original vars: $orig_strings""" end parametric_ring, _ = - polynomial_ring(FractionField(ring_of_tags), orig_strings, ordering = :degrevlex) + polynomial_ring(fraction_field(ring_of_tags), orig_strings, ordering = :degrevlex) relations_between_tags = map(poly -> parent_ring_change(poly, ring_of_tags), relations_between_tags) param_var_mapping = merge( diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index 9d978b6e0..4ca876c79 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -80,32 +80,6 @@ if GROUP == "All" || GROUP == "Core" #------------------------------------------------------------------------------ - function rand_poly(deg, vars) - if deg == 0 - return parent(vars[1])(1) - end - result = 0 - indices = collect(1:length(vars)) - monomials = [] - for d in 0:deg - for subs in StructuralIdentifiability.IterTools.subsets(indices, d) - push!(monomials, subs) - end - end - - for subs in monomials - monom = rand(-50:50) - for v_ind in subs - monom *= vars[v_ind] - end - result += monom - end - - return result - end - - #------------------------------------------------------------------------------ - @testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin test_cases = [] P = QQMPolyRingElem @@ -165,7 +139,7 @@ if GROUP == "All" || GROUP == "Core" ) F = Nemo.Native.GF(2^31 - 1) - P = gfp_mpoly + P = fpMPolyRingElem DType = Union{P, Generic.Frac{P}} varnames = vcat( diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index dc2e17da2..1a072a7f5 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -1,25 +1,4 @@ if GROUP == "All" || GROUP == "Core" - function rand_poly(deg, vars) - result = 0 - indices = vcat(collect(1:length(vars)), collect(1:length(vars))) - monomials = [] - for d in 0:deg - for subs in StructuralIdentifiability.IterTools.subsets(indices, d) - push!(monomials, subs) - end - end - - for subs in monomials - monom = rand(-50:50) - for v_ind in subs - monom *= vars[v_ind] - end - result += monom - end - - return result - end - @testset "Power series solution for an ODE system" begin R, (x, x_dot) = Nemo.polynomial_ring(Nemo.QQ, ["x", "x_dot"]) exp_t = ps_ode_solution( @@ -89,7 +68,7 @@ if GROUP == "All" || GROUP == "Core" ["u_$i" for i in 1:NUMU], ) R, vars = Nemo.polynomial_ring(F, varnames) - PType = gfp_mpoly + PType = fpMPolyRingElem TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} # Generating the intial conditions etc diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index 4fa0a58f9..9e9403012 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -43,7 +43,7 @@ if GROUP == "All" || GROUP == "Core" StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) R, (x,) = polynomial_ring(field, ["x"]) - R2, (x2,) = polynomial_ring(Nemo.FractionField(R), ["x"]) + R2, (x2,) = polynomial_ring(Nemo.fraction_field(R), ["x"]) R3, (x3,) = polynomial_ring(field, ["x"]) f = x3^2 f_ = StructuralIdentifiability.parent_ring_change(f, R2) diff --git a/test/runtests.jl b/test/runtests.jl index 970a2e371..cbd0fdd1f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -88,6 +88,27 @@ function random_ps_matrix(ps_ring, matrix_space) return result end +function rand_poly(deg, vars) + result = 0 + indices = vcat(collect(1:length(vars)), collect(1:length(vars))) + monomials = [] + for d in 0:deg + for subs in StructuralIdentifiability.IterTools.subsets(indices, d) + push!(monomials, subs) + end + end + + for subs in monomials + monom = rand(-50:50) + for v_ind in subs + monom *= vars[v_ind] + end + result += monom + end + + return result +end + @info "Testing started" @test isempty(Test.detect_ambiguities(StructuralIdentifiability)) From c708c6cfdec2645c2b8aa8562044b105afe1d968 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:00:48 +0000 Subject: [PATCH 55/82] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/CI.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4955b36c8..13e7d89b9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -21,7 +21,7 @@ jobs: - uses: julia-actions/setup-julia@v1 with: version: ${{ matrix.version }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: From b655cc0e5deec45489a40f49a88d7e01df1d472b Mon Sep 17 00:00:00 2001 From: gpogudin Date: Tue, 23 Jan 2024 14:31:15 +0100 Subject: [PATCH 56/82] creating a dedicated structure for discrete dynamical systems (but some tests are reluctant to pass for the moment) --- ext/ModelingToolkitExt.jl | 10 +- src/ODE.jl | 242 ---------------- src/StructuralIdentifiability.jl | 3 +- src/discrete.jl | 187 +++++++++---- src/input_macro.jl | 309 +++++++++++++++++++++ test/extensions/modelingtoolkit.jl | 6 +- test/local_identifiability_discrete_aux.jl | 6 +- 7 files changed, 461 insertions(+), 302 deletions(-) create mode 100644 src/input_macro.jl diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitExt.jl index 0e1a00026..4ca8de85f 100644 --- a/ext/ModelingToolkitExt.jl +++ b/ext/ModelingToolkitExt.jl @@ -404,7 +404,7 @@ end prob_threshold::Float64=0.99) Input: -- `dds` - the DiscreteSystem object from ModelingToolkit (with **difference** operator in the right-hand side) +- `dds` - the DiscreteSystem object from ModelingToolkit (with **difference** operator in the left-hand side) - `measured_quantities` - the measurable outputs of the model - `funcs_to_check` - functions of parameters for which to check identifiability (all parameters and states if not specified) - `known_ic` - functions (of states and parameter) whose initial conditions are assumed to be known @@ -467,14 +467,18 @@ function _assess_local_identifiability( dds_shift = DiscreteSystem(eqs_shift, name = gensym()) @debug "System transformed from difference to shift: $dds_shift" - dds_aux, conversion = mtk_to_si(dds_shift, measured_quantities) + dds_aux_ode, conversion = mtk_to_si(dds_shift, measured_quantities) + dds_aux = StructuralIdentifiability.DDS{QQMPolyRingElem}(dds_aux_ode) if length(funcs_to_check) == 0 params = parameters(dds) params_from_measured_quantities = union( [filter(s -> !istree(s), get_variables(y)) for y in measured_quantities]..., ) funcs_to_check = vcat( - [x for x in states(dds) if conversion[x] in dds_aux.x_vars], + [ + x for x in states(dds) if + conversion[x] in StructuralIdentifiability.x_vars(dds_aux) + ], union(params, params_from_measured_quantities), ) end diff --git a/src/ODE.jl b/src/ODE.jl index 83ba44ae2..addf9f933 100644 --- a/src/ODE.jl +++ b/src/ODE.jl @@ -261,248 +261,6 @@ end #------------------------------------------------------------------------------ -function _extract_aux!(funcs, all_symb, eq, ders_ok = false) - aux_symb = Set([:(+), :(-), :(=), :(*), :(^), :t, :(/), :(//)]) - MacroTools.postwalk( - x -> begin - if @capture(x, f_'(t)) - if !ders_ok - throw( - Base.ArgumentError( - "Derivative are not allowed in the right-hand side", - ), - ) - end - push!(all_symb, f) - elseif @capture(x, f_(t)) - push!(funcs, f) - elseif (x isa Symbol) && !(x in aux_symb) - push!(all_symb, x) - end - return x - end, - eq, - ) -end - -""" - For an expression of the form f'(t) or f(t) returns (f, true) and (f, false), resp -""" -function _get_var(expr) - if @capture(expr, f_'(t)) - return (f, true) - end - if @capture(expr, f_(t)) - return (f, false) - end - error("cannot extract the single function name from $expr") -end - -function macrohelper_extract_vars(equations::Array{Expr, 1}) - funcs, all_symb = Set(), Set() - x_vars, y_vars = Vector(), Vector() - aux_symb = Set([:(+), :(-), :(=), :(*), :(^), :t, :(/), :(//)]) - for eq in equations - if eq.head != :(=) - _extract_aux!(funcs, all_symb, eq) - else - lhs, rhs = eq.args[1:2] - _extract_aux!(funcs, all_symb, lhs, true) - _extract_aux!(funcs, all_symb, rhs) - (v, is_state) = _get_var(lhs) - if is_state - push!(x_vars, v) - else - push!(y_vars, v) - end - end - end - u_vars = setdiff(funcs, vcat(x_vars, y_vars)) - all_symb = collect(all_symb) - return x_vars, y_vars, collect(u_vars), collect(all_symb) -end - -function macrohelper_extract_vars(equations::Array{Symbol, 1}) - return macrohelper_extract_vars(map(Expr, equations)) -end - -#------------------------------------------------------------------------------ - -function macrohelper_clean(ex::Expr) - ex = MacroTools.postwalk(x -> @capture(x, f_'(t)) ? f : x, ex) - ex = MacroTools.postwalk(x -> @capture(x, f_(t)) ? f : x, ex) - ex = MacroTools.postwalk(x -> x == :(/) ? :(//) : x, ex) - ex = MacroTools.postwalk(x -> x isa Float64 ? rationalize(x) : x, ex) - return ex -end - -#------------------------------------------------------------------------------ - -""" - macro ODEmodel - -Macro for creating an ODE from a list of equations. -It also injects all variables into the global scope. - -## Example - -Creating a simple `ODE`: - -```jldoctest -using StructuralIdentifiability - -ode = @ODEmodel( - x1'(t) = a * x1(t) + u(t), - x2'(t) = b * x2(t) + c*x1(t)*x2(t), - y(t) = x1(t) -) -``` - -Here, -- `x1`, `x2` are state variables -- `y` is an output variable -- `u` is an input variable -- `a`, `b`, `c` are time-indepdendent parameters - -""" -macro ODEmodel(ex::Expr...) - equations = [ex...] - x_vars, y_vars, u_vars, all_symb = macrohelper_extract_vars(equations) - time_dependent = vcat(x_vars, y_vars, u_vars) - params = sort([s for s in all_symb if !(s in time_dependent)]) - all_symb_no_t = vcat(time_dependent, params) - all_symb_with_t = vcat([:($s(t)) for s in time_dependent], params) - - # creating the polynomial ring - vars_list = :([$(all_symb_with_t...)]) - R = gensym() - vars_aux = gensym() - exp_ring = :( - ($R, $vars_aux) = StructuralIdentifiability.Nemo.polynomial_ring( - StructuralIdentifiability.Nemo.QQ, - map(string, $all_symb_with_t), - ) - ) - assignments = [:($(all_symb_no_t[i]) = $vars_aux[$i]) for i in 1:length(all_symb_no_t)] - - # setting x_vars and y_vars in the right order - vx = gensym() - vy = gensym() - x_var_expr = - :($vx = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(x_vars...)])) - y_var_expr = - :($vy = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(y_vars...)])) - - # preparing equations - equations = map(macrohelper_clean, equations) - x_dict = gensym() - y_dict = gensym() - x_dict_create_expr = :( - $x_dict = Dict{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - Union{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - }, - }, - }() - ) - y_dict_create_expr = :( - $y_dict = Dict{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - Union{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ - StructuralIdentifiability.Nemo.QQMPolyRingElem, - }, - }, - }() - ) - eqs_expr = [] - for eq in equations - if eq.head != :(=) - throw("Problem with parsing at $eq") - end - lhs, rhs = eq.args[1:2] - loc_all_symb = macrohelper_extract_vars([rhs])[4] - to_insert = undef - if lhs in x_vars - to_insert = x_dict - elseif lhs in y_vars - to_insert = y_dict - else - throw("Unknown left-hand side $lhs") - end - - uniqueness_check_expr = quote - if haskey($to_insert, $lhs) - throw( - DomainError( - $lhs, - "The variable occurs twice in the left-hand-side of the ODE system", - ), - ) - end - end - push!(eqs_expr, uniqueness_check_expr) - if isempty(loc_all_symb) - push!(eqs_expr, :($to_insert[$lhs] = $R($rhs))) - else - push!(eqs_expr, :($to_insert[$lhs] = ($rhs))) - end - end - - for n in all_symb_no_t - if !Base.isidentifier(n) - throw( - ArgumentError( - "The names of the variables will be injected into the global scope, so their name must be allowed Julia names, $n is not", - ), - ) - end - end - - logging_exprs = [ - :( - StructuralIdentifiability.Logging.with_logger( - StructuralIdentifiability._si_logger[], - ) do - @info "Summary of the model:" - @info "State variables: " * $(join(map(string, collect(x_vars)), ", ")) - @info "Parameters: " * $(join(map(string, collect(params)), ", ")) - @info "Inputs: " * $(join(map(string, collect(u_vars)), ", ")) - @info "Outputs: " * $(join(map(string, collect(y_vars)), ", ")) - end - ), - ] - # creating the ode object - ode_expr = - :(StructuralIdentifiability.ODE{StructuralIdentifiability.Nemo.QQMPolyRingElem}( - $vx, - $vy, - $x_dict, - $y_dict, - Array{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(u_vars...)]), - )) - - result = Expr( - :block, - logging_exprs..., - exp_ring, - assignments..., - x_var_expr, - y_var_expr, - x_dict_create_expr, - y_dict_create_expr, - eqs_expr..., - ode_expr, - ) - return esc(result) -end - -#------------------------------------------------------------------------------ - function Base.show(io::IO, ode::ODE) for x in ode.x_vars if endswith(var_to_str(x), "(t)") diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index 2793ce16d..a18c98285 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -21,7 +21,7 @@ using ParamPunPam: reduce_mod_p!, specialize_mod_p, AbstractBlackboxIdeal ParamPunPam.enable_progressbar(false) # defining a model -export ODE, @ODEmodel, mtk_to_si +export ODE, @ODEmodel, @DDSmodel, mtk_to_si # assessing identifiability export assess_local_identifiability, assess_identifiability @@ -70,6 +70,7 @@ include("lincomp.jl") include("pb_representation.jl") include("submodels.jl") include("discrete.jl") +include("input_macro.jl") function __init__() _si_logger[] = @static if VERSION >= v"1.7.0" diff --git a/src/discrete.jl b/src/discrete.jl index 9d9117931..aa3ea73cf 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -1,8 +1,95 @@ +""" +The structue to represent a discrete dynamical system +with respect to *shift*. Internally just stores an ODE structur + +Can be constructed with @DDSmodel macro +""" +struct DDS{P} # P is the type of polynomials in the rhs of the DDS system + ode::ODE{P} + + function DDS{P}( + x_vars::Array{P, 1}, + y_vars::Array{P, 1}, + x_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, + y_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, + inputs::Array{P, 1}, + ) where {P <: MPolyRingElem{<:FieldElem}} + new{P}(ODE{P}(x_vars, y_vars, x_eqs, y_eqs, inputs)) + end + + function DDS{P}(ode::ODE{P}) where {P <: MPolyRingElem{<:FieldElem}} + new{P}(ode) + end +end + +#------------------------------------------------------------------------------ + +# getters + +function x_vars(dds::DDS) + return dds.ode.x_vars +end + +function y_vars(dds::DDS) + return dds.ode.y_vars +end + +function parameters(dds::DDS) + return dds.ode.parameters +end + +function inputs(dds::DDS) + return dds.ode.u_vars +end + +function x_equations(dds::DDS) + return dds.ode.x_equations +end + +function y_equations(dds::DDS) + return dds.ode.y_equations +end + +function Base.parent(dds::DDS) + return parent(dds.ode) +end + +#------------------------------------------------------------------------------ +# Some functions to transform DDS's + +function add_outputs( + dds::DDS{P}, + extra_y::Dict{String, <:RingElem}, +) where {P <: MPolyRingElem} + return DDS{P}(add_outputs(dds.ode, extra_y)) +end + +#------------------------------------------------------------------------------ + +function Base.show(io::IO, dds::DDS) + for x in x_vars(dds) + if endswith(var_to_str(x), "(t)") + print(io, var_to_str(x)[1:(end - 3)] * "(t + 1) = ") + else + print(io, var_to_str(x) * "(t + 1) = ") + end + print(io, x_equations(dds)[x]) + print(io, "\n") + end + for y in y_vars(dds) + print(io, var_to_str(y) * " = ") + print(io, y_equations(dds)[y]) + print(io, "\n") + end +end + +#------------------------------------------------------------------------------ + """ sequence_solution(dds, param_values, initial_conditions, input_values, num_terms) Input: -- `dds` - a discrete dynamical system to solve (represented as an ODE struct) +- `dds` - a discrete dynamical system to solve - `param_values` - parameter values, must be a dictionary mapping parameter to a value - `initial_conditions` - initial conditions of `ode`, must be a dictionary mapping state variable to a value - `input_values` - input sequences in the form input => list of terms; length of the lists must be at least @@ -13,21 +100,21 @@ Output: - computes a sequence solution with teh required number of terms prec presented as a dictionary state_variable => corresponding sequence """ function sequence_solution( - dds::ODE{P}, + dds::DDS{P}, param_values::Dict{P, T}, initial_conditions::Dict{P, T}, input_values::Dict{P, Array{T, 1}}, num_terms::Int, ) where {T <: FieldElem, P <: MPolyRingElem{T}} - result = Dict(x => [initial_conditions[x]] for x in dds.x_vars) + result = Dict(x => [initial_conditions[x]] for x in x_vars(dds)) for i in 2:num_terms eval_dict = merge( param_values, Dict(k => v[end] for (k, v) in result), Dict(u => val[i - 1] for (u, val) in input_values), ) - for x in dds.x_vars - push!(result[x], eval_at_dict(dds.x_equations[x], eval_dict)) + for x in x_vars(dds) + push!(result[x], eval_at_dict(x_equations(dds)[x], eval_dict)) end end return result @@ -36,13 +123,13 @@ end #------------------------------------------------------------------------------ function sequence_solution( - dds::ODE{P}, + dds::DDS{P}, param_values::Dict{P, Int}, initial_conditions::Dict{P, Int}, input_values::Dict{P, Array{Int, 1}}, num_terms::Int, ) where {P <: MPolyRingElem{<:FieldElem}} - bring = base_ring(dds.poly_ring) + bring = base_ring(parent(dds)) return sequence_solution( dds, Dict(p => bring(v) for (p, v) in param_values), @@ -66,7 +153,7 @@ Output: the function `u` w.r.t. `v` evaluated at the solution """ function differentiate_sequence_solution( - dds::ODE{P}, + dds::DDS{P}, params::Dict{P, T}, ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, @@ -74,16 +161,16 @@ function differentiate_sequence_solution( ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing the power series solution of the system" seq_sol = sequence_solution(dds, params, ic, inputs, num_terms) - generalized_params = vcat(dds.x_vars, dds.parameters) - bring = base_ring(dds.poly_ring) + generalized_params = vcat(x_vars(dds), parameters(dds)) + bring = base_ring(parent(dds)) @debug "Solving the variational system at the solution" part_diffs = Dict( - (x, p) => derivative(dds.x_equations[x], p) for x in dds.x_vars for + (x, p) => derivative(x_equations(dds)[x], p) for x in x_vars(dds) for p in generalized_params ) result = Dict( - (x, p) => [x == p ? one(bring) : zero(bring)] for x in dds.x_vars for + (x, p) => [x == p ? one(bring) : zero(bring)] for x in x_vars(dds) for p in generalized_params ) for i in 2:num_terms @@ -93,13 +180,13 @@ function differentiate_sequence_solution( Dict(u => val[i - 1] for (u, val) in inputs), ) for p in generalized_params - local_eval = Dict(x => result[(x, p)][end] for x in dds.x_vars) - for x in dds.x_vars + local_eval = Dict(x => result[(x, p)][end] for x in x_vars(dds)) + for x in x_vars(dds) res = sum([ eval_at_dict(part_diffs[(x, x2)], eval_dict) * local_eval[x2] for - x2 in dds.x_vars + x2 in x_vars(dds) ]) - if p in dds.parameters + if p in parameters(dds) res += eval_at_dict(part_diffs[(x, p)], eval_dict) end push!(result[(x, p)], res) @@ -120,7 +207,7 @@ returns a dictionary of the form `y_function => Dict(var => dy/dvar)` where `dy/ of `y_function` with respect to `var`. """ function differentiate_sequence_output( - dds::ODE{P}, + dds::DDS{P}, params::Dict{P, T}, ic::Dict{P, T}, inputs::Dict{P, Array{T, 1}}, @@ -130,13 +217,13 @@ function differentiate_sequence_output( seq_sol, sol_diff = differentiate_sequence_solution(dds, params, ic, inputs, num_terms) @debug "Evaluating the partial derivatives of the outputs" - generalized_params = vcat(dds.x_vars, dds.parameters) + generalized_params = vcat(x_vars(dds), parameters(dds)) part_diffs = Dict( - (y, p) => derivative(dds.y_equations[y], p) for y in dds.y_vars for + (y, p) => derivative(y_equations(dds)[y], p) for y in y_vars(dds) for p in generalized_params ) - result = Dict((y, p) => [] for y in dds.y_vars for p in generalized_params) + result = Dict((y, p) => [] for y in y_vars(dds) for p in generalized_params) for i in 1:num_terms eval_dict = merge( params, @@ -144,14 +231,14 @@ function differentiate_sequence_output( Dict(u => val[i] for (u, val) in inputs), ) - for p in vcat(dds.x_vars, dds.parameters) - local_eval = Dict(x => sol_diff[(x, p)][i] for x in dds.x_vars) - for (y, y_eq) in dds.y_equations + for p in generalized_params + local_eval = Dict(x => sol_diff[(x, p)][i] for x in x_vars(dds)) + for (y, y_eq) in y_equations(dds) res = sum([ eval_at_dict(part_diffs[(y, x)], eval_dict) * local_eval[x] for - x in dds.x_vars + x in x_vars(dds) ]) - if p in dds.parameters + if p in parameters(dds) res += eval_at_dict(part_diffs[(y, p)], eval_dict) end push!(result[(y, p)], res) @@ -178,10 +265,9 @@ function _degree_with_common_denom(polys) end """ - _assess_local_identifiability_discrete_aux(dds::ODE{P}, funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99) where P <: MPolyRingElem{Nemo.QQFieldElem} + _assess_local_identifiability_discrete_aux(dds::DDS{P}, funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99) where P <: MPolyRingElem{Nemo.QQFieldElem} -Checks the local identifiability/observability of the functions in `funcs_to_check` treating `dds` as a discrete-time system with **shift** -instead of derivative in the right-hand side. +Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `prob_threshold`. `known_ic` can take one of the following * `:none` - no initial conditions are assumed to be known @@ -189,12 +275,12 @@ The result is correct with probability at least `prob_threshold`. * a list of rational functions in states and parameters assumed to be known at t = 0 """ function _assess_local_identifiability_discrete_aux( - dds::ODE{P}, + dds::DDS{P}, funcs_to_check::Array{<:Any, 1}, known_ic = :none, prob_threshold::Float64 = 0.99, ) where {P <: MPolyRingElem{Nemo.QQFieldElem}} - bring = base_ring(dds.poly_ring) + bring = base_ring(parent(dds)) @debug "Extending the model" dds_ext = @@ -204,16 +290,16 @@ function _assess_local_identifiability_discrete_aux( known_ic = [] end if known_ic == :all - known_ic = dds_ext.x_vars + known_ic = x_vars(dds_ext) end @debug "Computing the observability matrix" - prec = length(dds.x_vars) + length(dds.parameters) + prec = length(x_vars(dds)) + length(x_vars(dds)) @debug "The truncation order is $prec" # Computing the bound from the Schwartz-Zippel-DeMilo-Lipton lemma - deg_x = _degree_with_common_denom(values(dds.x_equations)) - deg_y = _degree_with_common_denom(values(dds.y_equations)) + deg_x = _degree_with_common_denom(values(x_equations(dds))) + deg_y = _degree_with_common_denom(values(y_equations(dds))) deg_known = reduce(+, map(total_degree, known_ic), init = 0) deg_to_check = max(map(total_degree, funcs_to_check)...) Jac_degree = deg_to_check + deg_known @@ -226,11 +312,12 @@ function _assess_local_identifiability_discrete_aux( @debug "Sampling range $D" # Parameter values are the same across all the replicas - params_vals = Dict(p => bring(rand(1:D)) for p in dds_ext.parameters) - ic = Dict(x => bring(rand(1:D)) for x in dds_ext.x_vars) + params_vals = Dict(p => bring(rand(1:D)) for p in parameters(dds_ext)) + ic = Dict(x => bring(rand(1:D)) for x in x_vars(dds_ext)) # TODO: parametric type instead of QQFieldElem inputs = Dict{P, Array{QQFieldElem, 1}}( - u => [bring(rand(1:D)) for i in 1:prec] for u in dds_ext.u_vars + u => [bring(rand(1:D)) for i in 1:prec] for + u in StructuralIdentifiability.inputs(dds_ext) ) @debug "Computing the output derivatives" @@ -241,28 +328,28 @@ function _assess_local_identifiability_discrete_aux( Jac = zero( Nemo.matrix_space( bring, - length(dds.x_vars) + length(dds.parameters), - 1 + prec * length(dds.y_vars) + length(known_ic), + length(x_vars(dds)) + length(parameters(dds)), + 1 + prec * length(y_vars(dds)) + length(known_ic), ), ) - xs_params = vcat(dds_ext.x_vars, dds_ext.parameters) - for (i, y) in enumerate(dds.y_vars) - y = switch_ring(y, dds_ext.poly_ring) + xs_params = vcat(x_vars(dds_ext), parameters(dds_ext)) + for (i, y) in enumerate(y_vars(dds)) + y = switch_ring(y, parent(dds_ext)) for j in 1:prec - for (k, p) in enumerate(dds_ext.parameters) + for (k, p) in enumerate(parameters(dds_ext)) Jac[k, 1 + (i - 1) * prec + j] = output_derivatives[(y, p)][j] end - for (k, x) in enumerate(dds_ext.x_vars) + for (k, x) in enumerate(x_vars(dds_ext)) Jac[end - k + 1, 1 + (i - 1) * prec + j] = output_derivatives[(y, x)][j] end end end eval_point = merge(params_vals, ic) for (i, v) in enumerate(known_ic) - for (k, p) in enumerate(dds_ext.parameters) + for (k, p) in enumerate(parameters(dds_ext)) Jac[k, end - i + 1] = eval_at_dict(derivative(v, p), eval_point) end - for (k, x) in enumerate(dds_ext.x_vars) + for (k, x) in enumerate(x_vars(dds_ext)) Jac[end - k + 1, end - i + 1] = eval_at_dict(derivative(v, x), eval_point) end end @@ -271,13 +358,13 @@ function _assess_local_identifiability_discrete_aux( base_rank = LinearAlgebra.rank(Jac) result = OrderedDict{Any, Bool}() for i in 1:length(funcs_to_check) - for (k, p) in enumerate(dds_ext.parameters) + for (k, p) in enumerate(parameters(dds_ext)) Jac[k, 1] = - output_derivatives[(str_to_var("loc_aux_$i", dds_ext.poly_ring), p)][1] + output_derivatives[(str_to_var("loc_aux_$i", parent(dds_ext)), p)][1] end - for (k, x) in enumerate(dds_ext.x_vars) + for (k, x) in enumerate(x_vars(dds_ext)) Jac[end - k + 1, 1] = - output_derivatives[(str_to_var("loc_aux_$i", dds_ext.poly_ring), x)][1] + output_derivatives[(str_to_var("loc_aux_$i", parent(dds_ext)), x)][1] end result[funcs_to_check[i]] = LinearAlgebra.rank(Jac) == base_rank end diff --git a/src/input_macro.jl b/src/input_macro.jl new file mode 100644 index 000000000..2a2cf16f2 --- /dev/null +++ b/src/input_macro.jl @@ -0,0 +1,309 @@ +function _extract_aux!(funcs, all_symb, eq; ders_ok = false, type = :ode) + aux_symb = Set([:(+), :(-), :(=), :(*), :(^), :t, :(/), :(//)]) + MacroTools.postwalk( + x -> begin + if @capture(x, f_'(t)) + if !ders_ok + throw( + Base.ArgumentError( + "Derivative are not allowed in the right-hand side", + ), + ) + end + if type != :ode + throw( + Base.ArgumentError( + "Derivative are not expected in the discrete case", + ), + ) + end + push!(all_symb, f) + elseif @capture(x, f_(t + 1)) + if !ders_ok + throw(Base.ArgumentError("Shifts are not allowed in the right-hand side")) + end + if type != :dds + throw( + Base.ArgumentError( + "Shifts are not expected in the differential case", + ), + ) + end + push!(all_symb, f) + elseif @capture(x, f_(t)) + push!(funcs, f) + elseif (x isa Symbol) && !(x in aux_symb) + push!(all_symb, x) + end + return x + end, + eq, + ) +end + +""" + For an expression of the form f'(t)/f(t + 1) or f(t) returns (f, true) and (f, false), resp +""" +function _get_var(expr, type = :ode) + if @capture(expr, f_'(t)) + @assert type == :ode + return (f, true) + end + if @capture(expr, f_(t + 1)) + @assert type == :dds + return (f, true) + end + if @capture(expr, f_(t)) + return (f, false) + end + error("cannot extract the single function name from $expr") +end + +function macrohelper_extract_vars(equations::Array{Expr, 1}, type = :ode) + funcs, all_symb = Set(), Set() + x_vars, y_vars = Vector(), Vector() + aux_symb = Set([:(+), :(-), :(=), :(*), :(^), :t, :(/), :(//)]) + for eq in equations + if eq.head != :(=) + _extract_aux!(funcs, all_symb, eq, type = type) + else + lhs, rhs = eq.args[1:2] + _extract_aux!(funcs, all_symb, lhs, ders_ok = true, type = type) + _extract_aux!(funcs, all_symb, rhs, type = type) + (v, is_state) = _get_var(lhs, type) + if is_state + push!(x_vars, v) + else + push!(y_vars, v) + end + end + end + u_vars = setdiff(funcs, vcat(x_vars, y_vars)) + all_symb = collect(all_symb) + return x_vars, y_vars, collect(u_vars), collect(all_symb) +end + +function macrohelper_extract_vars(equations::Array{Symbol, 1}, type = :ode) + return macrohelper_extract_vars(map(Expr, equations), type) +end + +#------------------------------------------------------------------------------ + +function macrohelper_clean(ex::Expr) + ex = MacroTools.postwalk(x -> @capture(x, f_'(t)) ? f : x, ex) + ex = MacroTools.postwalk(x -> @capture(x, f_(t + 1)) ? f : x, ex) + ex = MacroTools.postwalk(x -> @capture(x, f_(t)) ? f : x, ex) + ex = MacroTools.postwalk(x -> x == :(/) ? :(//) : x, ex) + ex = MacroTools.postwalk(x -> x isa Float64 ? rationalize(x) : x, ex) + return ex +end + +#------------------------------------------------------------------------------ + +function generate_model_code(type, ex::Expr...) + @assert type in (:ode, :dds) + equations = [ex...] + x_vars, y_vars, u_vars, all_symb = macrohelper_extract_vars(equations, type) + time_dependent = vcat(x_vars, y_vars, u_vars) + params = sort([s for s in all_symb if !(s in time_dependent)]) + all_symb_no_t = vcat(time_dependent, params) + all_symb_with_t = vcat([:($s(t)) for s in time_dependent], params) + + # creating the polynomial ring + vars_list = :([$(all_symb_with_t...)]) + R = gensym() + vars_aux = gensym() + exp_ring = :( + ($R, $vars_aux) = StructuralIdentifiability.Nemo.polynomial_ring( + StructuralIdentifiability.Nemo.QQ, + map(string, $all_symb_with_t), + ) + ) + assignments = [:($(all_symb_no_t[i]) = $vars_aux[$i]) for i in 1:length(all_symb_no_t)] + + # setting x_vars and y_vars in the right order + vx = gensym() + vy = gensym() + x_var_expr = + :($vx = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(x_vars...)])) + y_var_expr = + :($vy = Vector{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(y_vars...)])) + + # preparing equations + equations = map(macrohelper_clean, equations) + x_dict = gensym() + y_dict = gensym() + x_dict_create_expr = :( + $x_dict = Dict{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + Union{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + }, + }, + }() + ) + y_dict_create_expr = :( + $y_dict = Dict{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + Union{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + StructuralIdentifiability.AbstractAlgebra.Generic.Frac{ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + }, + }, + }() + ) + eqs_expr = [] + for eq in equations + if eq.head != :(=) + throw("Problem with parsing at $eq") + end + lhs, rhs = eq.args[1:2] + loc_all_symb = macrohelper_extract_vars([rhs], type)[4] + to_insert = undef + if lhs in x_vars + to_insert = x_dict + elseif lhs in y_vars + to_insert = y_dict + else + throw("Unknown left-hand side $lhs") + end + + uniqueness_check_expr = quote + if haskey($to_insert, $lhs) + throw( + DomainError( + $lhs, + "The variable occurs twice in the left-hand-side of the ODE system", + ), + ) + end + end + push!(eqs_expr, uniqueness_check_expr) + if isempty(loc_all_symb) + push!(eqs_expr, :($to_insert[$lhs] = $R($rhs))) + else + push!(eqs_expr, :($to_insert[$lhs] = ($rhs))) + end + end + + for n in all_symb_no_t + if !Base.isidentifier(n) + throw( + ArgumentError( + "The names of the variables will be injected into the global scope, so their name must be allowed Julia names, $n is not", + ), + ) + end + end + + logging_exprs = [ + :( + StructuralIdentifiability.Logging.with_logger( + StructuralIdentifiability._si_logger[], + ) do + @info "Summary of the model:" + @info "State variables: " * $(join(map(string, collect(x_vars)), ", ")) + @info "Parameters: " * $(join(map(string, collect(params)), ", ")) + @info "Inputs: " * $(join(map(string, collect(u_vars)), ", ")) + @info "Outputs: " * $(join(map(string, collect(y_vars)), ", ")) + end + ), + ] + # creating the ode/dds object + obj_type = Dict(:ode => :ODE, :dds => :DDS) + ds_expr = :(StructuralIdentifiability.$(obj_type[type]){ + StructuralIdentifiability.Nemo.QQMPolyRingElem, + }( + $vx, + $vy, + $x_dict, + $y_dict, + Array{StructuralIdentifiability.Nemo.QQMPolyRingElem}([$(u_vars...)]), + )) + + result = Expr( + :block, + logging_exprs..., + exp_ring, + assignments..., + x_var_expr, + y_var_expr, + x_dict_create_expr, + y_dict_create_expr, + eqs_expr..., + ds_expr, + ) + return result +end + +#------------------------------------------------------------------------------ + +""" + macro ODEmodel + +Macro for creating an ODE from a list of equations. +It also injects all variables into the global scope. + +## Example + +Creating a simple `ODE`: + +```jldoctest +using StructuralIdentifiability + +ode = @ODEmodel( + x1'(t) = a * x1(t) + u(t), + x2'(t) = b * x2(t) + c*x1(t)*x2(t), + y(t) = x1(t) +) +``` + +Here, +- `x1`, `x2` are state variables +- `y` is an output variable +- `u` is an input variable +- `a`, `b`, `c` are time-indepdendent parameters + +""" +macro ODEmodel(ex::Expr...) + return esc(generate_model_code(:ode, ex...)) +end + +#------------------------------------------------------------------------------ + +""" + macro DDSmodel + +Macro for creating a DDS (discrete dynamical system) +from a list of equations. +It also injects all variables into the global scope. + +## Example + +Creating a simple `DDS`: + +```jldoctest +using StructuralIdentifiability + +ode = @ODEmodel( + x1(t + 1) = a * x1(t) + u(t), + x2(t + 1) = b * x2(t) + c*x1(t)*x2(t), + y(t) = x1(t) +) +``` + +Here, +- `x1`, `x2` are state variables +- `y` is an output variable +- `u` is an input variable +- `a`, `b`, `c` are time-indepdendent parameters + +""" +macro DDSmodel(ex::Expr...) + return esc(generate_model_code(:dds, ex...)) +end + +#------------------------------------------------------------------------------ diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index bfafa363f..c62998796 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -437,14 +437,14 @@ correct end - @testset "Discrete local identifiability, internal function" begin + @testset "Discrete local identifiability, ModelingToolkit interface" begin cases = [] @parameters α β @variables t S(t) I(t) R(t) y(t) D = Difference(t; dt = 1.0) - eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] + eqs = [D(S) ~ -β * S * I, D(I) ~ β * S * I - α * I, D(R) ~ α * I] @named sir = DiscreteSystem(eqs) push!( cases, @@ -463,7 +463,7 @@ @variables t x(t) y(t) D = Difference(t; dt = 1.0) - eqs = [D(x) ~ θ * x^3] + eqs = [D(x) ~ θ * x^3 - x] @named eqs = DiscreteSystem(eqs) push!( diff --git a/test/local_identifiability_discrete_aux.jl b/test/local_identifiability_discrete_aux.jl index 119816146..0c41ad9d3 100644 --- a/test/local_identifiability_discrete_aux.jl +++ b/test/local_identifiability_discrete_aux.jl @@ -2,7 +2,7 @@ if GROUP == "All" || GROUP == "Core" @testset "Discrete local identifiability, internal function" begin cases = [] - dds = @ODEmodel(a'(t) = (b + c) * a(t) + 1, y(t) = a(t)) + dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) push!( cases, @@ -24,7 +24,7 @@ if GROUP == "All" || GROUP == "Core" #--------------------- - dds = @ODEmodel(a'(t) = b(t) * a(t) + c, b'(t) = d * a(t), y(t) = b(t)) + dds = @DDSmodel(a(t + 1) = b(t) * a(t) + c, b(t + 1) = d * a(t), y(t) = b(t)) push!( cases, @@ -63,7 +63,7 @@ if GROUP == "All" || GROUP == "Core" # ------------------- # 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)) + dds = @DDSmodel(x(t + 1) = theta^3 * x(t), y(t) = x(t)) push!( cases, From a199a3d687760f17e4d2330de8840236f5f5c668 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 24 Jan 2024 22:06:05 +0100 Subject: [PATCH 57/82] bug fixed, the tests are passing now --- src/discrete.jl | 27 +++++++------ test/diff_sequence_solution.jl | 64 ++++++++++++++++-------------- test/extensions/modelingtoolkit.jl | 4 +- test/runtests.jl | 7 +++- test/sequence_solution.jl | 4 +- 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/discrete.jl b/src/discrete.jl index aa3ea73cf..d9cb090d6 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -12,9 +12,9 @@ struct DDS{P} # P is the type of polynomials in the rhs of the DDS system y_vars::Array{P, 1}, x_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, y_eqs::Dict{P, <:Union{P, Generic.Frac{P}}}, - inputs::Array{P, 1}, + u_vars::Array{P, 1}, ) where {P <: MPolyRingElem{<:FieldElem}} - new{P}(ODE{P}(x_vars, y_vars, x_eqs, y_eqs, inputs)) + new{P}(ODE{P}(x_vars, y_vars, x_eqs, y_eqs, u_vars)) end function DDS{P}(ode::ODE{P}) where {P <: MPolyRingElem{<:FieldElem}} @@ -142,7 +142,7 @@ end #----------------------------------------------------------------------------- """ - differentiate_sequence_solution(dds, params, ic, inputs, num_terms) + differentiate_sequence_solution(dds, params, ic, input_values, num_terms) Input: - the same as for `sequence_solutions` @@ -156,11 +156,11 @@ function differentiate_sequence_solution( dds::DDS{P}, params::Dict{P, T}, ic::Dict{P, T}, - inputs::Dict{P, Array{T, 1}}, + input_values::Dict{P, Array{T, 1}}, num_terms::Int, ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing the power series solution of the system" - seq_sol = sequence_solution(dds, params, ic, inputs, num_terms) + seq_sol = sequence_solution(dds, params, ic, input_values, num_terms) generalized_params = vcat(x_vars(dds), parameters(dds)) bring = base_ring(parent(dds)) @@ -177,7 +177,7 @@ function differentiate_sequence_solution( eval_dict = merge( params, Dict(k => v[i - 1] for (k, v) in seq_sol), - Dict(u => val[i - 1] for (u, val) in inputs), + Dict(u => val[i - 1] for (u, val) in input_values), ) for p in generalized_params local_eval = Dict(x => result[(x, p)][end] for x in x_vars(dds)) @@ -200,7 +200,7 @@ end # ------------------------------------------------------------------------------ """ - differentiate_sequence_output(dds, params, ic, inputs, num_terms) + differentiate_sequence_output(dds, params, ic, input_values, num_terms) Similar to `differentiate_sequence_solution` but computes partial derivatives of prescribed outputs returns a dictionary of the form `y_function => Dict(var => dy/dvar)` where `dy/dvar` is the derivative @@ -210,11 +210,12 @@ function differentiate_sequence_output( dds::DDS{P}, params::Dict{P, T}, ic::Dict{P, T}, - inputs::Dict{P, Array{T, 1}}, + input_values::Dict{P, Array{T, 1}}, num_terms::Int, ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} @debug "Computing partial derivatives of the solution" - seq_sol, sol_diff = differentiate_sequence_solution(dds, params, ic, inputs, num_terms) + seq_sol, sol_diff = + differentiate_sequence_solution(dds, params, ic, input_values, num_terms) @debug "Evaluating the partial derivatives of the outputs" generalized_params = vcat(x_vars(dds), parameters(dds)) @@ -228,7 +229,7 @@ function differentiate_sequence_output( eval_dict = merge( params, Dict(k => v[i] for (k, v) in seq_sol), - Dict(u => val[i] for (u, val) in inputs), + Dict(u => val[i] for (u, val) in input_values), ) for p in generalized_params @@ -294,7 +295,7 @@ function _assess_local_identifiability_discrete_aux( end @debug "Computing the observability matrix" - prec = length(x_vars(dds)) + length(x_vars(dds)) + prec = length(x_vars(dds)) + length(parameters(dds)) @debug "The truncation order is $prec" # Computing the bound from the Schwartz-Zippel-DeMilo-Lipton lemma @@ -315,14 +316,14 @@ function _assess_local_identifiability_discrete_aux( params_vals = Dict(p => bring(rand(1:D)) for p in parameters(dds_ext)) ic = Dict(x => bring(rand(1:D)) for x in x_vars(dds_ext)) # TODO: parametric type instead of QQFieldElem - inputs = Dict{P, Array{QQFieldElem, 1}}( + input_values = Dict{P, Array{QQFieldElem, 1}}( u => [bring(rand(1:D)) for i in 1:prec] for u in StructuralIdentifiability.inputs(dds_ext) ) @debug "Computing the output derivatives" output_derivatives = - differentiate_sequence_output(dds_ext, params_vals, ic, inputs, prec) + differentiate_sequence_output(dds_ext, params_vals, ic, input_values, prec) @debug "Building the matrices" Jac = zero( diff --git a/test/diff_sequence_solution.jl b/test/diff_sequence_solution.jl index 143654053..912a632cb 100644 --- a/test/diff_sequence_solution.jl +++ b/test/diff_sequence_solution.jl @@ -2,61 +2,63 @@ if GROUP == "All" || GROUP == "Core" @testset "Computing variations around a sequence solution" begin # Computing sensitivities directly be explicitly writing down Lie derivatives function use_lie_derivatives( - dds::ODE{P}, + dds::StructuralIdentifiability.DDS{P}, params::Dict{P, T}, ic::Dict{P, T}, - inputs::Dict{P, Array{T, 1}}, + input_values::Dict{P, Array{T, 1}}, num_terms::Int, ) where {T <: Generic.FieldElem, P <: MPolyRingElem{T}} - newvars = [var_to_str(v) for v in gens(dds.poly_ring)] + newvars = [var_to_str(v) for v in gens(parent(dds))] append!( newvars, - [var_to_str(v) * "$i" for v in dds.u_vars for i in 1:num_terms], + [var_to_str(v) * "$i" for v in inputs(dds) for i in 1:num_terms], ) R, _ = StructuralIdentifiability.Nemo.polynomial_ring( - base_ring(dds.poly_ring), + base_ring(parent(dds)), newvars, ) explicit_sol = merge( Dict( parent_ring_change(x, R) => Vector{Any}([parent_ring_change(x, R)]) - for (x, eq) in dds.x_equations + for (x, eq) in x_equations(dds) ), Dict( parent_ring_change(y, R) => Vector{Any}([parent_ring_change(eq, R)]) for - (y, eq) in dds.y_equations + (y, eq) in y_equations(dds) ), ) time_step = merge( Dict( parent_ring_change(x, R) => parent_ring_change(eq, R) for - (x, eq) in dds.x_equations + (x, eq) in x_equations(dds) ), Dict( parent_ring_change(p, R) => parent_ring_change(p, R) for - p in dds.parameters + p in StructuralIdentifiability.parameters(dds) ), Dict( parent_ring_change(u, R) => str_to_var(var_to_str(u) * "1", R) for - u in dds.u_vars + u in inputs(dds) ), Dict( str_to_var(s * "$i", R) => str_to_var(s * "$(i + 1)", R) for - s in map(var_to_str, dds.u_vars) for i in 1:(num_terms - 1) + s in map(var_to_str, inputs(dds)) for i in 1:(num_terms - 1) ), ) eval_dict = merge( Dict(parent_ring_change(p, R) => v for (p, v) in params), Dict(parent_ring_change(x, R) => v for (x, v) in ic), - Dict(parent_ring_change(u, R) => val[1] for (u, val) in inputs), + Dict(parent_ring_change(u, R) => val[1] for (u, val) in input_values), Dict( - str_to_var(var_to_str(u) * "$i", R) => inputs[u][i + 1] for - u in dds.u_vars for i in 1:(num_terms - 1) + str_to_var(var_to_str(u) * "$i", R) => input_values[u][i + 1] for + u in inputs(dds) for i in 1:(num_terms - 1) ), ) - generalized_parameters = - [parent_ring_change(p, R) for p in vcat(dds.x_vars, dds.parameters)] + generalized_parameters = [ + parent_ring_change(p, R) for + p in vcat(x_vars(dds), StructuralIdentifiability.parameters(dds)) + ] for i in 2:num_terms for (k, v) in explicit_sol push!(explicit_sol[k], eval_at_dict(v[end], time_step)) @@ -77,36 +79,38 @@ if GROUP == "All" || GROUP == "Core" end return Dict( ( - parent_ring_change(k[1], dds.poly_ring), - parent_ring_change(k[2], dds.poly_ring), + parent_ring_change(k[1], parent(dds)), + parent_ring_change(k[2], parent(dds)), ) => res for (k, res) in part_diffs ) end locQQ = StructuralIdentifiability.Nemo.QQ - ode = @ODEmodel(a'(t) = a(t)^2 + b, y(t) = 1 / (a(t) * c(t))) + dds = @DDSmodel(a(t + 1) = a(t)^2 + b, y(t) = 1 / (a(t) * c(t))) params = Dict(b => locQQ(1)) ic = Dict(a => locQQ(2)) - inputs = Dict(c => [locQQ(1), locQQ(-2), locQQ(3), locQQ(-4), locQQ(5)]) - seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 4) - diff_y = differentiate_sequence_output(ode, params, ic, inputs, 4) - lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 4) + input_values = Dict(c => [locQQ(1), locQQ(-2), locQQ(3), locQQ(-4), locQQ(5)]) + seq_sol, diff_sol = + differentiate_sequence_solution(dds, params, ic, input_values, 4) + diff_y = differentiate_sequence_output(dds, params, ic, input_values, 4) + lie_ders_sol = use_lie_derivatives(dds, params, ic, input_values, 4) merged = merge(diff_sol, diff_y) @test merged == lie_ders_sol - ode = @ODEmodel( - a'(t) = + dds = @DDSmodel( + a(t + 1) = (23 * k1 * a(t) - 7 * b(t)^3) // (a(t)^2 + b(t)^2) - c(t)^3 * k1 * b(t), - b'(t) = a(t) + 17 * (b(t) - c(t))^2 + 1 // (a(t) + b(t) - k2), + b(t + 1) = a(t) + 17 * (b(t) - c(t))^2 + 1 // (a(t) + b(t) - k2), y(t) = (a(t) + b(t) - c(t)) // (a(t)^2 + k2) ) params = Dict(k1 => locQQ(1), k2 => locQQ(2)) ic = Dict(a => locQQ(3), b => locQQ(-4)) - inputs = Dict(c => [locQQ(5), locQQ(-6), locQQ(7), locQQ(-8)]) - seq_sol, diff_sol = differentiate_sequence_solution(ode, params, ic, inputs, 2) - diff_y = differentiate_sequence_output(ode, params, ic, inputs, 2) - lie_ders_sol = use_lie_derivatives(ode, params, ic, inputs, 2) + input_values = Dict(c => [locQQ(5), locQQ(-6), locQQ(7), locQQ(-8)]) + seq_sol, diff_sol = + differentiate_sequence_solution(dds, params, ic, input_values, 2) + diff_y = differentiate_sequence_output(dds, params, ic, input_values, 2) + lie_ders_sol = use_lie_derivatives(dds, params, ic, input_values, 2) merged = merge(diff_sol, diff_y) @test merged == lie_ders_sol end diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index c62998796..0a37b8f41 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,10 +1,8 @@ @static if VERSION >= v"1.10.0" if GROUP == "All" || GROUP == "ModelingToolkitExt" @testset "Check identifiability of `ODESystem` object" begin - #using Pkg - #Pkg.add("ModelingToolkit") - #Pkg.add("Symbolics") using ModelingToolkit + using ModelingToolkit: parameters using Symbolics @parameters a01 a21 a12 diff --git a/test/runtests.jl b/test/runtests.jl index cbd0fdd1f..966448b2b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -57,7 +57,12 @@ using StructuralIdentifiability: extract_coefficients_ratfunc, lie_derivative, states_generators, - RationalFunctionField + RationalFunctionField, + x_vars, + y_vars, + x_equations, + y_equations, + inputs const GROUP = get(ENV, "GROUP", "All") diff --git a/test/sequence_solution.jl b/test/sequence_solution.jl index 70b37f719..0065d8356 100644 --- a/test/sequence_solution.jl +++ b/test/sequence_solution.jl @@ -1,7 +1,7 @@ if GROUP == "All" || GROUP == "Core" @testset "Sequence solutions in the discrete case" begin # Fibonacci example - ode = @ODEmodel(f1'(t) = f1(t) + f0(t), f0'(t) = f1(t), y(t) = f1(t)) + ode = @DDSmodel(f1(t + 1) = f1(t) + f0(t), f0(t + 1) = f1(t), y(t) = f1(t)) sol = sequence_solution( ode, @@ -14,7 +14,7 @@ if GROUP == "All" || GROUP == "Core" @test sol[f0] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] # Sum of powers - ode = @ODEmodel(a'(t) = 2 * a(t) + b * u(t), y(t) = a(t)) + ode = @DDSmodel(a(t + 1) = 2 * a(t) + b * u(t), y(t) = a(t)) sol = sequence_solution( ode, Dict(b => 3), From 09740a79eecd20224644bfb88ffe15abd381832d Mon Sep 17 00:00:00 2001 From: gpogudin Date: Thu, 25 Jan 2024 11:21:30 +0100 Subject: [PATCH 58/82] interface for discrete case via DDSmodel --- src/discrete.jl | 27 +++++++++++ test/local_identifiability_discrete.jl | 65 ++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 test/local_identifiability_discrete.jl diff --git a/src/discrete.jl b/src/discrete.jl index d9cb090d6..4b96e6123 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -374,3 +374,30 @@ function _assess_local_identifiability_discrete_aux( end # ------------------------------------------------------------------------------ + +""" + assess_local_identifiability(dds::DDS{P}; funcs_to_check::Array{<: Any, 1}, known_ic, prob_threshold::Float64=0.99, loglevel=Logging.Info) where P <: MPolyRingElem{Nemo.QQFieldElem} + +Checks the local identifiability/observability of the functions in `funcs_to_check`. The result is correct with probability at least `prob_threshold`. +A list of quantities can be provided as `known_ic` for which the initial conditions can be assumed to be known and generic. +""" +function assess_local_identifiability( + dds::DDS{P}; + funcs_to_check::Array{<:Any, 1} = Array{Any, 1}(), + known_ic = :none, + prob_threshold::Float64 = 0.99, + loglevel = Logging.Info, +) where {P <: MPolyRingElem{Nemo.QQFieldElem}} + restart_logging(loglevel = loglevel) + reset_timings() + with_logger(_si_logger[]) do + return _assess_local_identifiability_discrete_aux( + dds, + funcs_to_check, + known_ic, + prob_threshold, + ) + end +end + +# ------------------------------------------------------------------------------ diff --git a/test/local_identifiability_discrete.jl b/test/local_identifiability_discrete.jl new file mode 100644 index 000000000..9e6936844 --- /dev/null +++ b/test/local_identifiability_discrete.jl @@ -0,0 +1,65 @@ +if GROUP == "All" || GROUP == "Core" + @testset "Discrete local identifiability, @DDSmodel interface" begin + cases = [] + + dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :none, + ), + ) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :all, + ), + ) + + #--------------------- + + cases = [] + + dds = @DDSmodel(x1(t + 1) = a * x1(t) * x2(t), x2(t + 1) = b * x1(t), y(t) = x2(t)) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict( + a => true, + b => false, + x1 => false, + x2 => true, + b * x1 => true, + ), + :known => :none, + ), + ) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => true, x1 => true, x2 => true), + :known => :all, + ), + ) + + #--------------------- + + for c in cases + @test assess_local_identifiability( + c[:dds]; + funcs_to_check = collect(keys(c[:res])), + known_ic = c[:known], + ) == c[:res] + end + end +end From 3e0840e2119e7b66d38d26e4b6d4f21c00292866 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Thu, 25 Jan 2024 15:11:20 +0100 Subject: [PATCH 59/82] Updating docs, especially the DDS part --- docs/Project.toml | 2 +- docs/src/assets/Project.toml | 4 +- docs/src/input/input.md | 8 ++- docs/src/tutorials/creating_ode.md | 4 +- docs/src/tutorials/discrete_time.md | 97 +++++++++++++++++++++-------- src/discrete.jl | 3 + 6 files changed, 86 insertions(+), 32 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 21e7b1811..e449e2a58 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -7,5 +7,5 @@ StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" [compat] BenchmarkTools = "1.3" Documenter = "0.27, 1" -ModelingToolkit = "8.34" +ModelingToolkit = "8.74" StructuralIdentifiability = "0.5" diff --git a/docs/src/assets/Project.toml b/docs/src/assets/Project.toml index e4c519b6b..e449e2a58 100644 --- a/docs/src/assets/Project.toml +++ b/docs/src/assets/Project.toml @@ -7,5 +7,5 @@ StructuralIdentifiability = "220ca800-aa68-49bb-acd8-6037fa93a544" [compat] BenchmarkTools = "1.3" Documenter = "0.27, 1" -ModelingToolkit = "8.34" -StructuralIdentifiability = "0.4" +ModelingToolkit = "8.74" +StructuralIdentifiability = "0.5" diff --git a/docs/src/input/input.md b/docs/src/input/input.md index d41bf7374..5fa6ad68c 100644 --- a/docs/src/input/input.md +++ b/docs/src/input/input.md @@ -1,4 +1,4 @@ -# Parsing input ODE system +# Parsing input system ```@docs @ODEmodel(ex::Expr...) @@ -11,3 +11,9 @@ set_parameter_values ```@docs linear_compartment_model ``` + +## Discrete-time systems + +```@docs +@DDSmodel +``` diff --git a/docs/src/tutorials/creating_ode.md b/docs/src/tutorials/creating_ode.md index ec7d039ff..00d362846 100644 --- a/docs/src/tutorials/creating_ode.md +++ b/docs/src/tutorials/creating_ode.md @@ -54,7 +54,9 @@ assess_identifiability(ode) ## Defining using `ModelingToolkit` -Alternatively, one can use `ModelingToolkit`: encode the equations for the states as `ODESystem` and specify the outputs separately. +`StructuralIdentifiability` has an extension `ModelingToolkitExt` which allows to use `ODESystem` from `ModelingToolkit` to descibe +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: ```@example 2; continued = true diff --git a/docs/src/tutorials/discrete_time.md b/docs/src/tutorials/discrete_time.md index 212f59db8..c8f0571f8 100644 --- a/docs/src/tutorials/discrete_time.md +++ b/docs/src/tutorials/discrete_time.md @@ -1,13 +1,20 @@ # Identifiability of Discrete-Time Models (Local) -Now we consider a discrete-time model in the state-space form +Now we consider a discrete-time model in the state-space form. Such a model is typically written either in terms of **shift**: + +$\begin{cases} +\mathbf{x}(t + 1) = \mathbf{f}(\mathbf{x}(t), \mathbf{p}, \mathbf{u}(t)),\\ +\mathbf{y}(t) = \mathbf{g}(\mathbf{x}(t), \mathbf{p}, \mathbf{u(t)}), +\end{cases}$ + +or in terms of **difference** $\begin{cases} \Delta\mathbf{x}(t) = \mathbf{f}(\mathbf{x}(t), \mathbf{p}, \mathbf{u}(t)),\\ \mathbf{y}(t) = \mathbf{g}(\mathbf{x}(t), \mathbf{p}, \mathbf{u(t)}), -\end{cases} \quad \text{where} \quad \Delta \mathbf{x}(t) := \mathbf{x}(t + 1) - \mathbf{x}(t)$ +\end{cases} \quad \text{where} \quad \Delta \mathbf{x}(t) := \mathbf{x}(t + 1) - \mathbf{x}(t).$ -and $\mathbf{x}(t), \mathbf{y}(t)$, and $\mathbf{u}(t)$ are time-dependent states, outputs, and inputs, respectively, +In both cases,$\mathbf{x}(t), \mathbf{y}(t)$, and $\mathbf{u}(t)$ are time-dependent states, outputs, and inputs, respectively, and $\mathbf{p}$ are scalar parameters. As in the ODE case, we will call that a parameter or a states (or a function of them) is **identifiable** if its value can be recovered from time series for inputs and outputs (in the generic case, see Definition 3 in [^1] for details). @@ -21,50 +28,53 @@ Currently, `StructuralIdentifiability.jl` allows to assess only local identifiab and below we will describe how this can be done. As a running example, we will use the following discrete version of the [SIR](https://en.wikipedia.org/wiki/Compartmental_models_in_epidemiology#The_SIR_model) model: -$\begin{cases} -\Delta S(t) = S(t) - \beta S(t) I(t),\\ -\Delta I(t) = I(t) + \beta S(t) I(t) - \alpha I(t),\\ -\Delta R(t) = R(t) + \alpha I(t),\\ +$ +\begin{cases} +S(t + 1) = S(t) - \beta S(t) I(t),\\ +I(t + 1) = I(t) + \beta S(t) I(t) - \alpha I(t),\\ +R(t + 1) = R(t) + \alpha I(t),\\ +y(t) = I(t), +\end{cases} +\quad \text{or} +\quad +\begin{cases} +\Delta S(t) = -\beta S(t) I(t),\\ +\Delta I(t) = \beta S(t) I(t) - \alpha I(t),\\ +\Delta R(t) = \alpha I(t),\\ y(t) = I(t), \end{cases}$ where the observable is `I`, the number of infected people. -We start with creating a system as a `DiscreteSystem` from `ModelingToolkit`: +The native way to define such a model in `StructuralIdentifiability` is to use `@DDSmodel` macro which +uses the shift notation: -```@example discrete -using ModelingToolkit +```@example discrete_dds using StructuralIdentifiability -@parameters α β -@variables t S(t) I(t) R(t) y(t) -D = Difference(t; dt = 1.0) - -eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] -@named sir = DiscreteSystem(eqs) +dds = @DDSmodel( + S(t + 1) = S(t) - β * S(t) * I(t), + I(t + 1) = I(t) + β * S(t) * I(t) - α * I(t), + R(t + 1) = R(t) + α * I(t), + y(t) = I(t) +) ``` -Once the model is defined, we can assess identifiability by providing the formula for the observable: +Then local identifiability can be assessed using `assess_local_identifiability` function: -```@example discrete -assess_local_identifiability(sir; measured_quantities = [y ~ I]) +```@example discrete_dds +assess_local_identifiability(dds) ``` For each parameter or state, the value in the returned dictionary is `true` (`1`) if the parameter is locally identifiable and `false` (`0`) otherwise. We see that `R(t)` is not identifiable, which makes sense: it does not affect the dynamics of the observable in any way. -In principle, it is not required to give a name to the observable, so one can write this shorter - -```@example discrete -assess_local_identifiability(sir; measured_quantities = [I]) -``` - The `assess_local_identifiability` function has three important keyword arguments: - `funcs_to_check` is a list of functions for which one want to assess identifiability, for example, the following code will check if `β * S` is locally identifiable. -```@example discrete -assess_local_identifiability(sir; measured_quantities = [I], funcs_to_check = [β * S]) +```@example discrete_dds +assess_local_identifiability(dds; funcs_to_check = [β * S]) ``` - `prob_threshold` is the probability of correctness (default value `0.99`, i.e., 99%). The underlying algorithm is a Monte-Carlo algorithm, so in @@ -77,6 +87,39 @@ assess_local_identifiability(sir; measured_quantities = [I], funcs_to_check = [ 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 +describe the input model (now in terms of difference!): + +```@example discrete_mtk +using ModelingToolkit +using StructuralIdentifiability + +@parameters α β +@variables t S(t) I(t) R(t) y(t) +D = Difference(t; dt = 1.0) + +eqs = [D(S) ~ S - β * S * I, D(I) ~ I + β * S * I - α * I, D(R) ~ R + α * I] +@named sir = DiscreteSystem(eqs) +``` + +Then the same computation can be carried out with the models defined this way: + +```@example discrete_mtk +assess_local_identifiability(sir; measured_quantities = [y ~ I]) +``` + +In principle, it is not required to give a name to the observable, so one can write this shorter + +```@example discrete_mtk +assess_local_identifiability(sir; measured_quantities = [I]) +``` + +The same example but with specified functions to check + +```@example discrete_mtk +assess_local_identifiability(sir; measured_quantities = [I], funcs_to_check = [β * S]) +``` + The implementation is based on a version of the observability rank criterion and will be described in a forthcoming paper. [^1]: > S. Nõmm, C. Moog, [*Identifiability of discrete-time nonlinear systems*](https://doi.org/10.1016/S1474-6670(17)31245-4), IFAC Proceedings Volumes, 2004. diff --git a/src/discrete.jl b/src/discrete.jl index 4b96e6123..a25004994 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -391,6 +391,9 @@ function assess_local_identifiability( restart_logging(loglevel = loglevel) reset_timings() with_logger(_si_logger[]) do + if isempty(funcs_to_check) + funcs_to_check = vcat(parameters(dds), x_vars(dds)) + end return _assess_local_identifiability_discrete_aux( dds, funcs_to_check, From 1a7ebffac2887d8b54c3e68cce4045f021c59d66 Mon Sep 17 00:00:00 2001 From: Alexander Demin Date: Fri, 26 Jan 2024 00:02:47 +0300 Subject: [PATCH 60/82] fix typo, remove uniused vars --- src/input_macro.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/input_macro.jl b/src/input_macro.jl index 2a2cf16f2..3743117c7 100644 --- a/src/input_macro.jl +++ b/src/input_macro.jl @@ -62,7 +62,6 @@ end function macrohelper_extract_vars(equations::Array{Expr, 1}, type = :ode) funcs, all_symb = Set(), Set() x_vars, y_vars = Vector(), Vector() - aux_symb = Set([:(+), :(-), :(=), :(*), :(^), :t, :(/), :(//)]) for eq in equations if eq.head != :(=) _extract_aux!(funcs, all_symb, eq, type = type) @@ -110,7 +109,6 @@ function generate_model_code(type, ex::Expr...) all_symb_with_t = vcat([:($s(t)) for s in time_dependent], params) # creating the polynomial ring - vars_list = :([$(all_symb_with_t...)]) R = gensym() vars_aux = gensym() exp_ring = :( @@ -288,7 +286,7 @@ Creating a simple `DDS`: ```jldoctest using StructuralIdentifiability -ode = @ODEmodel( +dds = @DDSmodel( x1(t + 1) = a * x1(t) + u(t), x2(t + 1) = b * x2(t) + c*x1(t)*x2(t), y(t) = x1(t) From 5edc123996cb138f98a7a9d3b995a720f8b9cd67 Mon Sep 17 00:00:00 2001 From: Alexander Demin Date: Fri, 26 Jan 2024 00:19:59 +0300 Subject: [PATCH 61/82] warn --> debug, since it's misleading --- src/parametrizations.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parametrizations.jl b/src/parametrizations.jl index 94b385858..b0d7f6fe3 100644 --- a/src/parametrizations.jl +++ b/src/parametrizations.jl @@ -67,7 +67,7 @@ function check_constructive_field_membership( # If norm_form(Num) // norm_form(Den) does not belongs to K(T), then # the fraction does not belong to the field if iszero(den_rem) - @warn """ + @debug """ The element $tagged_num // $tagged_den is not in the sub-field Normal form, numerator: $num_rem Normal form, denominator: $den_rem @@ -76,7 +76,7 @@ function check_constructive_field_membership( return false, zero(ring_of_tags) // one(ring_of_tags) end if total_degree(num_rem) > 0 || total_degree(den_rem) > 0 - @warn """ + @debug """ The element $tagged_num // $tagged_den is not in the sub-field Normal form, numerator: $num_rem Normal form, denominator: $den_rem From 13cac29179187e1efc41075eb8725492b63dc546 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 26 Jan 2024 10:02:35 +0100 Subject: [PATCH 62/82] fixing typos --- src/discrete.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/discrete.jl b/src/discrete.jl index a25004994..3134fb5e2 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -1,6 +1,6 @@ """ -The structue to represent a discrete dynamical system -with respect to *shift*. Internally just stores an ODE structur +The structure to represent a discrete dynamical system +with respect to *shift*. Internally just stores an ODE structure Can be constructed with @DDSmodel macro """ From 0789d73d223dff99a6929f6c4fb63f2b7f968510 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Fri, 26 Jan 2024 14:21:29 +0100 Subject: [PATCH 63/82] partly reorganized tests --- .../RationalFunctionField.jl | 66 +- test/RationalFunctionFields/normalforms.jl | 146 +- test/check_field_membership.jl | 55 +- test/differentiate_output.jl | 414 ++-- test/diffreduction.jl | 322 ++- test/exp_vec_trie.jl | 52 +- test/extensions/modelingtoolkit.jl | 1316 ++++++----- test/extract_coefficients.jl | 106 +- test/find_leader.jl | 46 +- test/identifiability.jl | 468 ++-- test/identifiable_functions.jl | 1996 ++++++++--------- test/io_cases.jl | 282 ++- test/io_projections.jl | 116 +- test/lc_univariate.jl | 16 +- test/lie_derivative.jl | 40 +- test/linear_compartment.jl | 312 ++- test/local_identifiability.jl | 234 +- test/local_identifiability_discrete.jl | 122 +- test/local_identifiability_discrete_aux.jl | 140 +- test/local_identifiability_me.jl | 412 ++-- test/logging.jl | 34 +- test/monomial_compress.jl | 30 +- test/ode.jl | 34 +- test/ode_ps_solution.jl | 174 +- test/paradigm_shift.jl | 468 ++-- test/parent_ring_change.jl | 92 +- test/pb_representation.jl | 42 +- test/ps_diff.jl | 18 +- test/ps_integrate.jl | 20 +- test/ps_inverse.jl | 42 +- test/ps_matrix_homlinear.jl | 36 +- test/ps_matrix_linear.jl | 36 +- test/ps_matrix_log.jl | 76 +- test/pseudodivision.jl | 10 +- test/reduce_ode_mod_p.jl | 24 +- test/sequence_solution.jl | 46 +- test/set_parameter_values.jl | 28 +- test/state_generators.jl | 58 +- test/submodels.jl | 274 ++- 39 files changed, 4063 insertions(+), 4140 deletions(-) diff --git a/test/RationalFunctionFields/RationalFunctionField.jl b/test/RationalFunctionFields/RationalFunctionField.jl index 9a9f88ba1..e63afe145 100644 --- a/test/RationalFunctionFields/RationalFunctionField.jl +++ b/test/RationalFunctionFields/RationalFunctionField.jl @@ -1,38 +1,36 @@ -if GROUP == "All" || GROUP == "Core" - @testset "RationalFunctionField" begin - p = 0.99 - R, (a, b, c) = QQ["a", "b", "c"] +@testset "RationalFunctionField" begin + p = 0.99 + R, (a, b, c) = QQ["a", "b", "c"] - f1 = [a, b, a + b + c] - f2 = [2c, 3b, 5a] - rff1 = StructuralIdentifiability.RationalFunctionField(f1) - rff2 = StructuralIdentifiability.RationalFunctionField(f2) - @test StructuralIdentifiability.fields_equal(rff1, rff2, p) - @test all(StructuralIdentifiability.field_contains(rff1, [zero(R), one(R)], p)) - @test all(StructuralIdentifiability.field_contains(rff1, [7a, 9b, 11c], p)) - @test all(StructuralIdentifiability.field_contains(rff2, [7a, 9b, 11c], p)) + f1 = [a, b, a + b + c] + f2 = [2c, 3b, 5a] + rff1 = StructuralIdentifiability.RationalFunctionField(f1) + rff2 = StructuralIdentifiability.RationalFunctionField(f2) + @test StructuralIdentifiability.fields_equal(rff1, rff2, p) + @test all(StructuralIdentifiability.field_contains(rff1, [zero(R), one(R)], p)) + @test all(StructuralIdentifiability.field_contains(rff1, [7a, 9b, 11c], p)) + @test all(StructuralIdentifiability.field_contains(rff2, [7a, 9b, 11c], p)) - s1, s2 = a + b + c, a^2 + b^2 + c^2 - f1 = [s1, s2] - rff1 = StructuralIdentifiability.RationalFunctionField(f1) - @test !any( - StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p), - ) - @test all( - StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p), - ) - @test all(StructuralIdentifiability.field_contains(rff1, [(s1)^8 - (s2)^9 + 89], p)) + s1, s2 = a + b + c, a^2 + b^2 + c^2 + f1 = [s1, s2] + rff1 = StructuralIdentifiability.RationalFunctionField(f1) + @test !any( + StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p), + ) + @test all( + StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p), + ) + @test all(StructuralIdentifiability.field_contains(rff1, [(s1)^8 - (s2)^9 + 89], p)) - # Example in Section 5 from - # https://mediatum.ub.tum.de/doc/685465/685465.pdf - R, (x1, x2) = QQ["x1", "x2"] - g1 = (x1^3 + x1 * x2 - 2) // (x1^2 - x2 - 1) - g2 = (x1^2 + x1^2 * x2 + 7) // (x1 - x1^2 * x2^2) - g3 = x1^2 + 3x1 * x2 - g4 = x1 * x2^2 + 5x1 * x2 - g5 = x1^3 * x2 - x2 - rff1 = StructuralIdentifiability.RationalFunctionField([g1, g2, g3, g4, g5]) - rff2 = StructuralIdentifiability.RationalFunctionField([x1, x2]) - @test StructuralIdentifiability.fields_equal(rff1, rff2, p) - end + # Example in Section 5 from + # https://mediatum.ub.tum.de/doc/685465/685465.pdf + R, (x1, x2) = QQ["x1", "x2"] + g1 = (x1^3 + x1 * x2 - 2) // (x1^2 - x2 - 1) + g2 = (x1^2 + x1^2 * x2 + 7) // (x1 - x1^2 * x2^2) + g3 = x1^2 + 3x1 * x2 + g4 = x1 * x2^2 + 5x1 * x2 + g5 = x1^3 * x2 - x2 + rff1 = StructuralIdentifiability.RationalFunctionField([g1, g2, g3, g4, g5]) + rff2 = StructuralIdentifiability.RationalFunctionField([x1, x2]) + @test StructuralIdentifiability.fields_equal(rff1, rff2, p) end diff --git a/test/RationalFunctionFields/normalforms.jl b/test/RationalFunctionFields/normalforms.jl index 40381e74c..a6635b669 100644 --- a/test/RationalFunctionFields/normalforms.jl +++ b/test/RationalFunctionFields/normalforms.jl @@ -1,83 +1,81 @@ -if GROUP == "All" || GROUP == "Core" - eq_up_to_the_order(a, b) = issubset(a, b) && issubset(b, a) +eq_up_to_the_order(a, b) = issubset(a, b) && issubset(b, a) - @testset "Linear relations over the rationals" begin - R, (a, b, c) = QQ["a", "b", "c"] +@testset "Linear relations over the rationals" begin + R, (a, b, c) = QQ["a", "b", "c"] - f = [a + 9] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a]) + f = [a + 9] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a]) - f = [a * b // R(1), (b * c + a * b) // (a * b)] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a * b // R(1), b * c // R(1)]) + f = [a * b // R(1), (b * c + a * b) // (a * b)] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a * b // R(1), b * c // R(1)]) - R, (a, b, c) = QQ["a", "b", "c"] - f = [a^2 + b^2, a^3 + b^3, a^4 + b^4] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) - @test eq_up_to_the_order(relations, [a + b]) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a + b, a * b, a^2 + b^2]) + R, (a, b, c) = QQ["a", "b", "c"] + f = [a^2 + b^2, a^3 + b^3, a^4 + b^4] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) + @test eq_up_to_the_order(relations, [a + b]) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a + b, a * b, a^2 + b^2]) - f = [9a^7 + 10b^6, b^10 - 5b^2] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) - @test eq_up_to_the_order(relations, empty(f)) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 7) - @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6]) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 12) - @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6, b^10 - 5b^2]) + f = [9a^7 + 10b^6, b^10 - 5b^2] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 1) + @test eq_up_to_the_order(relations, empty(f)) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 7) + @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6]) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 12) + @test eq_up_to_the_order(relations, [a^7 + (10 // 9) * b^6, b^10 - 5b^2]) - # Regression tests - ### - # LV model. - R, (x1, p2, p4, y1, x2, x3, u, p1, p3) = - QQ["x1", "p2", "p4", "y1", "x2", "x3", "u", "p1", "p3"] - f = [ - x3 // one(R), - x2 * x1 // one(R), - p1 * p3 // one(R), - p2 * p4 // one(R), - p1 + p3 // one(R), - (p2 * x2 + p4 * x1) // (x2 * x1), - (p2 * x2 - p4 * x1) // (p1 - p3), - ] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree( - rff, - 2, - strategy = :monte_carlo, - ) - @test (x1 * p4 + p2 * x2) // one(R) in relations + # Regression tests + ### + # LV model. + R, (x1, p2, p4, y1, x2, x3, u, p1, p3) = + QQ["x1", "p2", "p4", "y1", "x2", "x3", "u", "p1", "p3"] + f = [ + x3 // one(R), + x2 * x1 // one(R), + p1 * p3 // one(R), + p2 * p4 // one(R), + p1 + p3 // one(R), + (p2 * x2 + p4 * x1) // (x2 * x1), + (p2 * x2 - p4 * x1) // (p1 - p3), + ] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree( + rff, + 2, + strategy = :monte_carlo, + ) + @test (x1 * p4 + p2 * x2) // one(R) in relations - ### - R, (a, b, c) = QQ["a", "b", "c"] - f = [a, a * b + b * c] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test eq_up_to_the_order(relations, [a, a * b + b * c]) + ### + R, (a, b, c) = QQ["a", "b", "c"] + f = [a, a * b + b * c] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test eq_up_to_the_order(relations, [a, a * b + b * c]) - ### - # Some arbitrary generators for the SLIQR model - R, (b, e, In, S, Ninv, s, Q, g, u, a, y, L) = - polynomial_ring(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) - f = [ - In // one(R), - s // one(R), - Ninv // one(R), - b // one(R), - (g + a) // one(R), - (e * s * g - s * g + g * a) // one(R), - (e * S - S) // (e * Q), - (e * S * s - S * s + S * a) // e, - (s * Q^2 - Q^2 * a) // (e * g - g), - (e * In + e * L - In - Q - L) // (e * Q), - ] - rff = StructuralIdentifiability.RationalFunctionField(f) - relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) - @test s * Q - Q * a in relations - end + ### + # Some arbitrary generators for the SLIQR model + R, (b, e, In, S, Ninv, s, Q, g, u, a, y, L) = + polynomial_ring(QQ, [:b, :e, :In, :S, :Ninv, :s, :Q, :g, :u, :a, :y, :L]) + f = [ + In // one(R), + s // one(R), + Ninv // one(R), + b // one(R), + (g + a) // one(R), + (e * s * g - s * g + g * a) // one(R), + (e * S - S) // (e * Q), + (e * S * s - S * s + S * a) // e, + (s * Q^2 - Q^2 * a) // (e * g - g), + (e * In + e * L - In - Q - L) // (e * Q), + ] + rff = StructuralIdentifiability.RationalFunctionField(f) + relations = StructuralIdentifiability.monomial_generators_up_to_degree(rff, 2) + @test s * Q - Q * a in relations end diff --git a/test/check_field_membership.jl b/test/check_field_membership.jl index 72f8eec82..8be30604b 100644 --- a/test/check_field_membership.jl +++ b/test/check_field_membership.jl @@ -1,32 +1,31 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Check field membership" begin - R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) +@testset "Check field membership" begin + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) - @test field_contains( - RationalFunctionField([[R(1), x + y], [R(1), x * y], [z, (x + y)^2]]), - [(x^2 + y^2) // R(1), (x^3 + y^3) // (z - x * y), R(1) // (z + x + y), z // x], - 0.99, - ) == [true, true, true, false] + @test field_contains( + RationalFunctionField([[R(1), x + y], [R(1), x * y], [z, (x + y)^2]]), + [(x^2 + y^2) // R(1), (x^3 + y^3) // (z - x * y), R(1) // (z + x + y), z // x], + 0.99, + ) == [true, true, true, false] - @test field_contains( - RationalFunctionField([[ - x + y + z, - x^2 + y^2 + z^2, - (x + y + z)^2, - x^3 + y^3 + z^3, - ]]), - [x + y + z // 1, x * y * z // 1, x + y + 2 * z // 1, x // (y + z)], - 0.99, - ) == [true, true, false, false] + @test field_contains( + RationalFunctionField([[ + x + y + z, + x^2 + y^2 + z^2, + (x + y + z)^2, + x^3 + y^3 + z^3, + ]]), + [x + y + z // 1, x * y * z // 1, x + y + 2 * z // 1, x // (y + z)], + 0.99, + ) == [true, true, false, false] - @test field_contains( - RationalFunctionField([ - x + y + z // 1, - x * y + y * z + z * x // 1, - x * y * z // 1, - ]), - [x^2 + y^2 + z^2, x^6 + y^6 + z^6, x - y + z, x^2 - y^2 + z^2], - 0.99, - ) == [true, true, false, false] - end + @test field_contains( + RationalFunctionField([ + x + y + z // 1, + x * y + y * z + z * x // 1, + x * y * z // 1, + ]), + [x^2 + y^2 + z^2, x^6 + y^6 + z^6, x - y + z, x^2 - y^2 + z^2], + 0.99, + ) == [true, true, false, false] end + diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index 4ca876c79..ff8ed54b2 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -1,229 +1,227 @@ -if GROUP == "All" || GROUP == "Core" - #------- Auxiliary functions -------------------------------------------------- - - function diff_sol_Lie_derivatives(ode::ODE, params, ic, inputs, prec::Int) - # creating a new ring with variables for the derivatives of u - new_varnames = map(var_to_str, gens(ode.poly_ring)) - if length(ode.u_vars) > 0 - append!( - new_varnames, - [var_to_str(u) * "_$i" for u in ode.u_vars for i in 0:(prec - 1)], - ) - end - new_ring, vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_varnames) +#------- Auxiliary functions -------------------------------------------------- + +function diff_sol_Lie_derivatives(ode::ODE, params, ic, inputs, prec::Int) + # creating a new ring with variables for the derivatives of u + new_varnames = map(var_to_str, gens(ode.poly_ring)) + if length(ode.u_vars) > 0 + append!( + new_varnames, + [var_to_str(u) * "_$i" for u in ode.u_vars for i in 0:(prec - 1)], + ) + end + new_ring, vars = Nemo.polynomial_ring(base_ring(ode.poly_ring), new_varnames) - # mapping everything to the new ring - eval_point = Dict(v => switch_ring(v, new_ring) for v in gens(ode.poly_ring)) - for u in ode.u_vars - eval_point[u] = str_to_var(var_to_str(u) * "_0", new_ring) - end + # mapping everything to the new ring + eval_point = Dict(v => switch_ring(v, new_ring) for v in gens(ode.poly_ring)) + for u in ode.u_vars + eval_point[u] = str_to_var(var_to_str(u) * "_0", new_ring) + end - new_eqs = Dict() - for (x, f) in ode.x_equations - new_eqs[str_to_var(var_to_str(x), new_ring)] = eval_at_dict(f, eval_point) + new_eqs = Dict() + for (x, f) in ode.x_equations + new_eqs[str_to_var(var_to_str(x), new_ring)] = eval_at_dict(f, eval_point) + end + params, ic = map( + d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), + [params, ic], + ) + + # computing Lie derivatives + derivation = copy(new_eqs) + for u in ode.u_vars + for i in 0:(prec - 2) + derivation[str_to_var(var_to_str(u) * "_$i", new_ring)] = + str_to_var(var_to_str(u) * "_$(i + 1)", new_ring) end - params, ic = map( - d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), - [params, ic], - ) + end + Lie_derivatives = Dict() + for (y, g) in ode.y_equations + Lie_derivatives[y] = Array{Any, 1}([eval_at_dict(g, eval_point)]) + for i in 1:prec + push!( + Lie_derivatives[y], + sum( + derivative(Lie_derivatives[y][end], v) * get(derivation, v, 0) for + v in gens(new_ring) + ), + ) + end + end - # computing Lie derivatives - derivation = copy(new_eqs) - for u in ode.u_vars - for i in 0:(prec - 2) - derivation[str_to_var(var_to_str(u) * "_$i", new_ring)] = - str_to_var(var_to_str(u) * "_$(i + 1)", new_ring) - end + # producing the result + eval_point = merge(params, ic) + for u in ode.u_vars + for i in 1:prec + eval_point[str_to_var(var_to_str(u) * "_$(i - 1)", new_ring)] = + inputs[u][i] * factorial(i - 1) end - Lie_derivatives = Dict() - for (y, g) in ode.y_equations - Lie_derivatives[y] = Array{Any, 1}([eval_at_dict(g, eval_point)]) - for i in 1:prec + end + + result = Dict() + for y in ode.y_vars + result[y] = Dict() + for v in vcat(ode.x_vars, ode.parameters) + result[y][v] = [] + for j in 1:prec push!( - Lie_derivatives[y], - sum( - derivative(Lie_derivatives[y][end], v) * get(derivation, v, 0) for - v in gens(new_ring) + result[y][v], + eval_at_dict( + derivative(Lie_derivatives[y][j], str_to_var("$v", new_ring)), + eval_point, ), ) end end - - # producing the result - eval_point = merge(params, ic) - for u in ode.u_vars - for i in 1:prec - eval_point[str_to_var(var_to_str(u) * "_$(i - 1)", new_ring)] = - inputs[u][i] * factorial(i - 1) - end - end - - result = Dict() - for y in ode.y_vars - result[y] = Dict() - for v in vcat(ode.x_vars, ode.parameters) - result[y][v] = [] - for j in 1:prec - push!( - result[y][v], - eval_at_dict( - derivative(Lie_derivatives[y][j], str_to_var("$v", new_ring)), - eval_point, - ), - ) - end - end - end - - return result end - #------------------------------------------------------------------------------ - - @testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin - test_cases = [] - P = QQMPolyRingElem - DType = Union{P, Generic.Frac{P}} - - ode = @ODEmodel(x'(t) = x(t) + a, y(t) = x(t)^2) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{QQFieldElem, 1}}(), - :prec => 20, - ), - ) - - ode = @ODEmodel(x'(t) = x(t)^2 + a, y1(t) = x(t) + a^2, y2(t) = x(t)^3) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{QQFieldElem, 1}}(), - :prec => 20, - ), - ) - - ode = @ODEmodel( - x'(t) = x(t)^2 + 2 * x(t) * y(t) - 3 * a * y(t), - y'(t) = x(t)^2 + a * b - b^2 + 4 * b * x(t), - y1(t) = a * x(t), - y2(t) = b * y(t)^2 - y(t) - ) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10)), y => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10)), b => Nemo.QQ(rand(1:10))), - :inputs => Dict{P, Array{QQFieldElem, 1}}(), - :prec => 8, - ), - ) - - ode = @ODEmodel(x'(t) = u(t) + a, y(t) = x(t)) - push!( - test_cases, - Dict( - :ODE => ode, - :ic => Dict(x => Nemo.QQ(rand(1:10))), - :param_vals => Dict(a => Nemo.QQ(rand(1:10))), - :inputs => Dict(u => [Nemo.QQ(rand(-3:3)) for i in 1:20]), - :prec => 20, - ), - ) - - F = Nemo.Native.GF(2^31 - 1) - P = fpMPolyRingElem - DType = Union{P, Generic.Frac{P}} + return result +end - varnames = vcat( - ["x_$i" for i in 1:3], - ["p_$i" for i in 1:3], - ["u_$i" for i in 1:2], - ["y_$i" for i in 1:3], - ) - R, vars = Nemo.polynomial_ring(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}(vars[i] => rand_poly(1, vars[1:8]) for i in 1:3), - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), - vars[7:8], - ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), - :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), - :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), - :prec => 6, +#------------------------------------------------------------------------------ + +@testset "Partial derivatives of an output w.r.t. to initial conditions and parameters" begin + test_cases = [] + P = QQMPolyRingElem + DType = Union{P, Generic.Frac{P}} + + ode = @ODEmodel(x'(t) = x(t) + a, y(t) = x(t)^2) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), + :prec => 20, + ), + ) + + ode = @ODEmodel(x'(t) = x(t)^2 + a, y1(t) = x(t) + a^2, y2(t) = x(t)^3) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), + :prec => 20, + ), + ) + + ode = @ODEmodel( + x'(t) = x(t)^2 + 2 * x(t) * y(t) - 3 * a * y(t), + y'(t) = x(t)^2 + a * b - b^2 + 4 * b * x(t), + y1(t) = a * x(t), + y2(t) = b * y(t)^2 - y(t) + ) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10)), y => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10)), b => Nemo.QQ(rand(1:10))), + :inputs => Dict{P, Array{QQFieldElem, 1}}(), + :prec => 8, + ), + ) + + ode = @ODEmodel(x'(t) = u(t) + a, y(t) = x(t)) + push!( + test_cases, + Dict( + :ODE => ode, + :ic => Dict(x => Nemo.QQ(rand(1:10))), + :param_vals => Dict(a => Nemo.QQ(rand(1:10))), + :inputs => Dict(u => [Nemo.QQ(rand(-3:3)) for i in 1:20]), + :prec => 20, + ), + ) + + F = Nemo.Native.GF(2^31 - 1) + P = fpMPolyRingElem + DType = Union{P, Generic.Frac{P}} + + varnames = vcat( + ["x_$i" for i in 1:3], + ["p_$i" for i in 1:3], + ["u_$i" for i in 1:2], + ["y_$i" for i in 1:3], + ) + R, vars = Nemo.polynomial_ring(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}(vars[i] => rand_poly(1, vars[1:8]) for i in 1:3), + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), + vars[7:8], ), - ) - - varnames = vcat( - ["x_$i" for i in 1:3], - ["p_$i" for i in 1:3], - ["u_$i" for i in 1:2], - ["y_$i" for i in 1:3], - ) - R, vars = Nemo.polynomial_ring(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 1:3), - Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), - vars[7:8], - ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), - :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), - :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), - :prec => 6, + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), + :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), + :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), + :prec => 6, + ), + ) + + varnames = vcat( + ["x_$i" for i in 1:3], + ["p_$i" for i in 1:3], + ["u_$i" for i in 1:2], + ["y_$i" for i in 1:3], + ) + R, vars = Nemo.polynomial_ring(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 1:3), + Dict{P, DType}(vars[i] => rand_poly(2, vars[1:8]) for i in 9:11), + vars[7:8], ), - ) - - varnames = vcat(["x_$i" for i in 1:2], ["p_$i" for i in 1:2], "u", ["y_1", "y_2"]) - R, vars = Nemo.polynomial_ring(F, varnames) - push!( - test_cases, - Dict( - :ODE => ODE{P}( - Dict{P, DType}( - vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for - i in 1:2 - ), - Dict{P, DType}(vars[i] => rand_poly(1, vars[1:5]) for i in 6:7), - [vars[5]], + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:3), + :param_vals => Dict(vars[i + 3] => F(rand(1:50)) for i in 1:3), + :inputs => Dict(u => [F(rand(-30:30)) for i in 1:6] for u in vars[7:8]), + :prec => 6, + ), + ) + + varnames = vcat(["x_$i" for i in 1:2], ["p_$i" for i in 1:2], "u", ["y_1", "y_2"]) + R, vars = Nemo.polynomial_ring(F, varnames) + push!( + test_cases, + Dict( + :ODE => ODE{P}( + Dict{P, DType}( + vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for + i in 1:2 ), - :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:2), - :param_vals => Dict(vars[i + 2] => F(rand(1:50)) for i in 1:2), - :inputs => Dict(vars[5] => [F(rand(-30:30)) for i in 1:4]), - :prec => 4, + Dict{P, DType}(vars[i] => rand_poly(1, vars[1:5]) for i in 6:7), + [vars[5]], ), + :ic => Dict(vars[i] => F(rand(1:50)) for i in 1:2), + :param_vals => Dict(vars[i + 2] => F(rand(1:50)) for i in 1:2), + :inputs => Dict(vars[5] => [F(rand(-30:30)) for i in 1:4]), + :prec => 4, + ), + ) + + for case in test_cases + ode, prec = case[:ODE], case[:prec] + @time sol1 = + differentiate_output(ode, case[:param_vals], case[:ic], case[:inputs], prec) + sol2 = diff_sol_Lie_derivatives( + ode, + case[:param_vals], + case[:ic], + case[:inputs], + prec, ) - - for case in test_cases - ode, prec = case[:ODE], case[:prec] - @time sol1 = - differentiate_output(ode, case[:param_vals], case[:ic], case[:inputs], prec) - sol2 = diff_sol_Lie_derivatives( - ode, - case[:param_vals], - case[:ic], - case[:inputs], - prec, - ) - for y in ode.y_vars - for v in vcat(ode.x_vars, ode.parameters) - @test sol2[y][v] == [ - base_ring(ode.poly_ring)(coeff(sol1[y][v], j) * factorial(j)) for - j in 0:(prec - 1) - ] - end + for y in ode.y_vars + for v in vcat(ode.x_vars, ode.parameters) + @test sol2[y][v] == [ + base_ring(ode.poly_ring)(coeff(sol1[y][v], j) * factorial(j)) for + j in 0:(prec - 1) + ] end end end diff --git a/test/diffreduction.jl b/test/diffreduction.jl index 556a44d5c..82150ac28 100644 --- a/test/diffreduction.jl +++ b/test/diffreduction.jl @@ -1,166 +1,164 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Differential reduction" begin - ode = @ODEmodel(x'(t) = a * x(t), y(t) = x(t)) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4"]) - res = diffreduce(y_5, pbr) - @test res == str_to_var("a", parent(res))^5 * str_to_var("y(t)_0", parent(res)) - res = diffreduce(y_4, pbr) - @test res == str_to_var("a", parent(res))^4 * str_to_var("y(t)_0", parent(res)) - res = diffreduce(y_4^2, pbr) - @test res == str_to_var("a", parent(res))^8 * str_to_var("y(t)_0", parent(res))^2 +@testset "Differential reduction" begin + ode = @ODEmodel(x'(t) = a * x(t), y(t) = x(t)) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_5, y_4) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4"]) + res = diffreduce(y_5, pbr) + @test res == str_to_var("a", parent(res))^5 * str_to_var("y(t)_0", parent(res)) + res = diffreduce(y_4, pbr) + @test res == str_to_var("a", parent(res))^4 * str_to_var("y(t)_0", parent(res)) + res = diffreduce(y_4^2, pbr) + @test res == str_to_var("a", parent(res))^8 * str_to_var("y(t)_0", parent(res))^2 - ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = -x1(t), y(t) = x1(t) + u(t)) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_5, y_4, u_10) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) - res = diffreduce(y_4 + u_10, pbr) - @test res == - str_to_var("u(t)_10", parent(res)) + str_to_var("y(t)_0", parent(res)) - - str_to_var("u(t)_0", parent(res)) + str_to_var("u(t)_4", parent(res)) + ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = -x1(t), y(t) = x1(t) + u(t)) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_5, y_4, u_10) = Nemo.polynomial_ring(Nemo.QQ, ["y(t)_5", "y(t)_4", "u(t)_10"]) + res = diffreduce(y_4 + u_10, pbr) + @test res == + str_to_var("u(t)_10", parent(res)) + str_to_var("y(t)_0", parent(res)) - + str_to_var("u(t)_0", parent(res)) + str_to_var("u(t)_4", parent(res)) - # Next two verified with Maple - # Mizuka's example - R, (y_0, y_1, y_2, y_3, u_0, u_1, u_2) = - Nemo.polynomial_ring(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) - pbr = PBRepresentation( - ["y"], - ["u"], - Array{String, 1}(), - Dict("y" => 2), - Dict( - "y" => - 27 * y_0^9 + 27 * y_0^6 * y_1^3 - 54 * u_0 * y_0^6 + - 54 * y_0^7 + - 54 * y_0^6 * y_1 - 27 * y_0^5 * y_1^2 - 27 * y_0^4 * u_1 * y_1^2 + - 27 * y_0^4 * y_1^3 + - 27 * y_0^4 * y_1^2 * y_2 + - 27 * u_0^2 * y_0^3 - 54 * u_0 * y_0^4 - 54 * u_0 * y_0^3 * y_1 + - 27 * y_0^5 + - 54 * y_0^4 * y_1 + - 18 * y_0^3 * u_1 * y_1 + - 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + - 9 * y_0^2 * u_1^2 * y_1 - 18 * y_0^2 * u_1 * y_1^2 - - 18 * y_0^2 * u_1 * y_1 * y_2 + - 9 * y_0^2 * y_1^3 + - 18 * y_0^2 * y_1^2 * y_2 + - 9 * y_0^2 * y_1 * y_2^2 + - 4 * y_0^3 - 3 * y_0 * u_1^2 + - 6 * y_0 * u_1 * y_1 + - 6 * y_0 * u_1 * y_2 - 3 * y_0 * y_1^2 - 6 * y_0 * y_1 * y_2 - - 3 * y_0 * y_2^2 - u_1^3 + - 3 * u_1^2 * y_1 + - 3 * u_1^2 * y_2 - 3 * u_1 * y_1^2 - 6 * u_1 * y_1 * y_2 - - 3 * u_1 * y_2^2 + - y_1^3 + - 3 * y_1^2 * y_2 + - 3 * y_1 * y_2^2 + - y_2^3, - ), - ) - p = - 100 * y_0^4 + 10 * y_0^3 * y_1 + 410 // 3 * y_0 * y_2 + 10 * y_0 * y_3 - - 110 * y_1^2 - 10 * y_1 * y_2 - @time res = diffreduce(p, pbr) - expected = parent_ring_change( - -100 * y_2^2 * y_1^2 + 70 * y_2^2 * y_0^4 + 380 // 3 * y_2^2 * y_0^2 - - 200 * y_2 * y_1^3 - 180 * y_2 * y_0^7 - 380 * y_2 * y_0^5 - - 270 * y_1^4 * y_0^6 - 1080 * y_1^4 * y_0^4 - 630 * y_1^4 * y_0^2 + - 810 * y_1^3 * y_0^9 - 2520 * y_1^3 * y_0^7 - 2910 * y_1^3 * y_0^5 - - 440 * y_1^3 * y_0^3 + - 220 // 3 * y_1^3 * y_0 + - 90 * y_1^2 * y_0^8 + - 3690 * y_1^2 * y_0^6 - 1330 * y_1^2 * y_0^4 + - 380 * y_1^2 * y_0^2 + - 1080 * y_1 * y_0^9 - 6540 * y_1 * y_0^7 - 7220 * y_1 * y_0^5 + - 810 * u_0^2 * y_0^6 - 3420 * u_0^2 * y_0^4 - 1620 * u_0 * y_0^9 + - 5220 * u_0 * y_0^7 + - 6840 * u_0 * y_0^5 - 10 * u_1^3 * y_1 - 30 * u_1^3 * y_0^3 + - 380 // 3 * u_1^3 * y_0 - 80 * u_1^2 * y_1^2 + - 10 * u_1^2 * y_0^4 + - 380 * u_1^2 * y_0^2 + - 190 * u_1 * y_1^3 + - 180 * u_1 * y_0^7 + - 380 * u_1 * y_0^5 - 3300 * y_0^6 - 1520 // 3 * y_0^4 - - 20 * u_2 * y_2 * u_1 * y_0 + - 60 * u_2 * y_2 * y_1 * y_0^3 + - 20 * u_2 * y_2 * y_1 * y_0 - 60 * u_2 * u_1 * y_1 * y_0^3 - - 20 * u_2 * u_1 * y_1 * y_0 - 360 * y_2 * u_1 * y_1 * y_0^5 + - 1380 * y_2 * u_1 * y_1 * y_0^3 + - 1580 // 3 * y_2 * u_1 * y_1 * y_0 - 30 * y_2^2 * y_1^2 * y_0^2 + - 90 * y_2^2 * y_1 * y_0^5 - 340 * y_2^2 * y_1 * y_0^3 - - 380 // 3 * y_2^2 * y_1 * y_0 - 180 * y_2 * y_1^3 * y_0^4 - - 660 * y_2 * y_1^3 * y_0^2 + 540 * y_2 * y_1^2 * y_0^7 - - 1860 * y_2 * y_1^2 * y_0^5 - 1380 * y_2 * y_1^2 * y_0^3 - - 160 // 3 * y_2 * y_1^2 * y_0 + - 240 * y_2 * y_1 * y_0^6 + - 1400 * y_2 * y_1 * y_0^4 + - 1520 // 3 * y_2 * y_1 * y_0^2 + - 10 * u_2 * y_2^2 * y_0 - 20 * u_2 * y_2 * y_0^2 + - 10 * u_2 * u_1^2 * y_0 + - 20 * u_2 * u_1 * y_0^2 + - 90 * u_2 * y_1^2 * y_0^5 + - 60 * u_2 * y_1^2 * y_0^3 + - 10 * u_2 * y_1^2 * y_0 - 60 * u_2 * y_1 * y_0^4 - 20 * u_2 * y_1 * y_0^2 - - 10 * y_2^2 * u_1 * y_1 - 30 * y_2^2 * u_1 * y_0^3 + - 380 // 3 * y_2^2 * u_1 * y_0 + - 20 * y_2 * u_1^2 * y_1 + - 60 * y_2 * u_1^2 * y_0^3 - 760 // 3 * y_2 * u_1^2 * y_0 + - 180 * y_2 * u_1 * y_1^2 - 80 * y_2 * u_1 * y_0^4 - - 1520 // 3 * y_2 * u_1 * y_0^2 + - 180 * y_2 * u_0 * y_0^4 + - 30 * u_1^2 * y_1^2 * y_0^2 + - 270 * u_1^2 * y_1 * y_0^5 - 1040 * u_1^2 * y_1 * y_0^3 - - 400 * u_1^2 * y_1 * y_0 + - 90 * u_1 * y_1^3 * y_0^4 + - 600 * u_1 * y_1^3 * y_0^2 - 810 * u_1 * y_1^2 * y_0^7 + - 2820 * u_1 * y_1^2 * y_0^5 + - 2170 * u_1 * y_1^2 * y_0^3 + - 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - - 2100 * u_1 * y_1 * y_0^4 - 760 * u_1 * y_1 * y_0^2 - - 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + - 7020 * y_1 * u_0 * y_0^4 - 6030 * y_0^8 + 810 * y_0^12 - 1800 * y_0^10 - - 100 * y_1^4, - parent(res), - ) - @test total_degree(divexact(res, expected)) == 0 + # Next two verified with Maple + # Mizuka's example + R, (y_0, y_1, y_2, y_3, u_0, u_1, u_2) = + Nemo.polynomial_ring(Nemo.QQ, ["y_0", "y_1", "y_2", "y_3", "u_0", "u_1", "u_2"]) + pbr = PBRepresentation( + ["y"], + ["u"], + Array{String, 1}(), + Dict("y" => 2), + Dict( + "y" => + 27 * y_0^9 + 27 * y_0^6 * y_1^3 - 54 * u_0 * y_0^6 + + 54 * y_0^7 + + 54 * y_0^6 * y_1 - 27 * y_0^5 * y_1^2 - 27 * y_0^4 * u_1 * y_1^2 + + 27 * y_0^4 * y_1^3 + + 27 * y_0^4 * y_1^2 * y_2 + + 27 * u_0^2 * y_0^3 - 54 * u_0 * y_0^4 - 54 * u_0 * y_0^3 * y_1 + + 27 * y_0^5 + + 54 * y_0^4 * y_1 + + 18 * y_0^3 * u_1 * y_1 + + 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + + 9 * y_0^2 * u_1^2 * y_1 - 18 * y_0^2 * u_1 * y_1^2 - + 18 * y_0^2 * u_1 * y_1 * y_2 + + 9 * y_0^2 * y_1^3 + + 18 * y_0^2 * y_1^2 * y_2 + + 9 * y_0^2 * y_1 * y_2^2 + + 4 * y_0^3 - 3 * y_0 * u_1^2 + + 6 * y_0 * u_1 * y_1 + + 6 * y_0 * u_1 * y_2 - 3 * y_0 * y_1^2 - 6 * y_0 * y_1 * y_2 - + 3 * y_0 * y_2^2 - u_1^3 + + 3 * u_1^2 * y_1 + + 3 * u_1^2 * y_2 - 3 * u_1 * y_1^2 - 6 * u_1 * y_1 * y_2 - + 3 * u_1 * y_2^2 + + y_1^3 + + 3 * y_1^2 * y_2 + + 3 * y_1 * y_2^2 + + y_2^3, + ), + ) + p = + 100 * y_0^4 + 10 * y_0^3 * y_1 + 410 // 3 * y_0 * y_2 + 10 * y_0 * y_3 - + 110 * y_1^2 - 10 * y_1 * y_2 + @time res = diffreduce(p, pbr) + expected = parent_ring_change( + -100 * y_2^2 * y_1^2 + 70 * y_2^2 * y_0^4 + 380 // 3 * y_2^2 * y_0^2 - + 200 * y_2 * y_1^3 - 180 * y_2 * y_0^7 - 380 * y_2 * y_0^5 - + 270 * y_1^4 * y_0^6 - 1080 * y_1^4 * y_0^4 - 630 * y_1^4 * y_0^2 + + 810 * y_1^3 * y_0^9 - 2520 * y_1^3 * y_0^7 - 2910 * y_1^3 * y_0^5 - + 440 * y_1^3 * y_0^3 + + 220 // 3 * y_1^3 * y_0 + + 90 * y_1^2 * y_0^8 + + 3690 * y_1^2 * y_0^6 - 1330 * y_1^2 * y_0^4 + + 380 * y_1^2 * y_0^2 + + 1080 * y_1 * y_0^9 - 6540 * y_1 * y_0^7 - 7220 * y_1 * y_0^5 + + 810 * u_0^2 * y_0^6 - 3420 * u_0^2 * y_0^4 - 1620 * u_0 * y_0^9 + + 5220 * u_0 * y_0^7 + + 6840 * u_0 * y_0^5 - 10 * u_1^3 * y_1 - 30 * u_1^3 * y_0^3 + + 380 // 3 * u_1^3 * y_0 - 80 * u_1^2 * y_1^2 + + 10 * u_1^2 * y_0^4 + + 380 * u_1^2 * y_0^2 + + 190 * u_1 * y_1^3 + + 180 * u_1 * y_0^7 + + 380 * u_1 * y_0^5 - 3300 * y_0^6 - 1520 // 3 * y_0^4 - + 20 * u_2 * y_2 * u_1 * y_0 + + 60 * u_2 * y_2 * y_1 * y_0^3 + + 20 * u_2 * y_2 * y_1 * y_0 - 60 * u_2 * u_1 * y_1 * y_0^3 - + 20 * u_2 * u_1 * y_1 * y_0 - 360 * y_2 * u_1 * y_1 * y_0^5 + + 1380 * y_2 * u_1 * y_1 * y_0^3 + + 1580 // 3 * y_2 * u_1 * y_1 * y_0 - 30 * y_2^2 * y_1^2 * y_0^2 + + 90 * y_2^2 * y_1 * y_0^5 - 340 * y_2^2 * y_1 * y_0^3 - + 380 // 3 * y_2^2 * y_1 * y_0 - 180 * y_2 * y_1^3 * y_0^4 - + 660 * y_2 * y_1^3 * y_0^2 + 540 * y_2 * y_1^2 * y_0^7 - + 1860 * y_2 * y_1^2 * y_0^5 - 1380 * y_2 * y_1^2 * y_0^3 - + 160 // 3 * y_2 * y_1^2 * y_0 + + 240 * y_2 * y_1 * y_0^6 + + 1400 * y_2 * y_1 * y_0^4 + + 1520 // 3 * y_2 * y_1 * y_0^2 + + 10 * u_2 * y_2^2 * y_0 - 20 * u_2 * y_2 * y_0^2 + + 10 * u_2 * u_1^2 * y_0 + + 20 * u_2 * u_1 * y_0^2 + + 90 * u_2 * y_1^2 * y_0^5 + + 60 * u_2 * y_1^2 * y_0^3 + + 10 * u_2 * y_1^2 * y_0 - 60 * u_2 * y_1 * y_0^4 - 20 * u_2 * y_1 * y_0^2 - + 10 * y_2^2 * u_1 * y_1 - 30 * y_2^2 * u_1 * y_0^3 + + 380 // 3 * y_2^2 * u_1 * y_0 + + 20 * y_2 * u_1^2 * y_1 + + 60 * y_2 * u_1^2 * y_0^3 - 760 // 3 * y_2 * u_1^2 * y_0 + + 180 * y_2 * u_1 * y_1^2 - 80 * y_2 * u_1 * y_0^4 - + 1520 // 3 * y_2 * u_1 * y_0^2 + + 180 * y_2 * u_0 * y_0^4 + + 30 * u_1^2 * y_1^2 * y_0^2 + + 270 * u_1^2 * y_1 * y_0^5 - 1040 * u_1^2 * y_1 * y_0^3 - + 400 * u_1^2 * y_1 * y_0 + + 90 * u_1 * y_1^3 * y_0^4 + + 600 * u_1 * y_1^3 * y_0^2 - 810 * u_1 * y_1^2 * y_0^7 + + 2820 * u_1 * y_1^2 * y_0^5 + + 2170 * u_1 * y_1^2 * y_0^3 + + 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - + 2100 * u_1 * y_1 * y_0^4 - 760 * u_1 * y_1 * y_0^2 - + 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + + 7020 * y_1 * u_0 * y_0^4 - 6030 * y_0^8 + 810 * y_0^12 - 1800 * y_0^10 - + 100 * y_1^4, + parent(res), + ) + @test total_degree(divexact(res, expected)) == 0 - ode = @ODEmodel( - x1'(t) = x1(t) + 2 * x1(t) * x2(t) + u(t), - x2'(t) = x2(t) + 3 * x1(t) * x2(t), - y(t) = x1(t) - ) - pbr = PBRepresentation(ode, find_ioequations(ode)) - R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.polynomial_ring( - Nemo.QQ, - ["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "u(t)_0", "u(t)_3"], - ) - io_switch!(pbr) - @time res = diffreduce(u_3, pbr) - expected = parent_ring_change( - y_0 * ( - 27 * u_0 * y_0^4 + 27 * y_0^5 - 27 * y_0^4 * y_1 + - 27 * u_0 * y_0^3 + - 54 * u_0 * y_0^2 * y_1 + - 27 * y_0^4 + - 27 * y_0^3 * y_1 - 54 * y_0^2 * y_1^2 + - 9 * u_0 * y_0^2 + - 27 * u_0 * y_0 * y_1 + - 12 * u_0 * y_0 * y_2 + - 9 * u_0 * y_1^2 + - 9 * y_0^3 + - 18 * y_0^2 * y_1 + - 12 * y_0^2 * y_2 - 18 * y_0 * y_1^2 - 12 * y_0 * y_1 * y_2 - 9 * y_1^3 + - u_0 * y_0 + - 3 * u_0 * y_1 + - 3 * u_0 * y_2 + - u_0 * y_3 + - y_0^2 + - 2 * y_0 * y_1 + - 3 * y_0 * y_2 + - y_0 * y_4 - 3 * y_1^2 - 3 * y_1 * y_2 - y_1 * y_3 - ), - parent(res), - ) - @test total_degree(divexact(res, expected)) == 0 - end + ode = @ODEmodel( + x1'(t) = x1(t) + 2 * x1(t) * x2(t) + u(t), + x2'(t) = x2(t) + 3 * x1(t) * x2(t), + y(t) = x1(t) + ) + pbr = PBRepresentation(ode, find_ioequations(ode)) + R, (y_0, y_1, y_2, y_3, y_4, u_0, u_3) = Nemo.polynomial_ring( + Nemo.QQ, + ["y(t)_0", "y(t)_1", "y(t)_2", "y(t)_3", "y(t)_4", "u(t)_0", "u(t)_3"], + ) + io_switch!(pbr) + @time res = diffreduce(u_3, pbr) + expected = parent_ring_change( + y_0 * ( + 27 * u_0 * y_0^4 + 27 * y_0^5 - 27 * y_0^4 * y_1 + + 27 * u_0 * y_0^3 + + 54 * u_0 * y_0^2 * y_1 + + 27 * y_0^4 + + 27 * y_0^3 * y_1 - 54 * y_0^2 * y_1^2 + + 9 * u_0 * y_0^2 + + 27 * u_0 * y_0 * y_1 + + 12 * u_0 * y_0 * y_2 + + 9 * u_0 * y_1^2 + + 9 * y_0^3 + + 18 * y_0^2 * y_1 + + 12 * y_0^2 * y_2 - 18 * y_0 * y_1^2 - 12 * y_0 * y_1 * y_2 - 9 * y_1^3 + + u_0 * y_0 + + 3 * u_0 * y_1 + + 3 * u_0 * y_2 + + u_0 * y_3 + + y_0^2 + + 2 * y_0 * y_1 + + 3 * y_0 * y_2 + + y_0 * y_4 - 3 * y_1^2 - 3 * y_1 * y_2 - y_1 * y_3 + ), + parent(res), + ) + @test total_degree(divexact(res, expected)) == 0 end diff --git a/test/exp_vec_trie.jl b/test/exp_vec_trie.jl index ec09c5425..11420ecde 100644 --- a/test/exp_vec_trie.jl +++ b/test/exp_vec_trie.jl @@ -1,32 +1,30 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Trie for exponents vectors" begin - for _ in 1:10 - d = rand([i + 10 for i in 1:10]) - t = ExpVectTrie(d) - push!(t, [0 for _ in 1:d]) - vects = [rand([i for i in 1:10], d) for _ in 1:20] - for v in vects - push!(t, v) - end - for v in vects - diff, best = get_max_below(t, v) - @test diff == 0 - @test best == v - end +@testset "Trie for exponents vectors" begin + for _ in 1:10 + d = rand([i + 10 for i in 1:10]) + t = ExpVectTrie(d) + push!(t, [0 for _ in 1:d]) + vects = [rand([i for i in 1:10], d) for _ in 1:20] + for v in vects + push!(t, v) + end + for v in vects + diff, best = get_max_below(t, v) + @test diff == 0 + @test best == v + end - svects = Set(vects) - push!(svects, [0 for _ in 1:d]) + svects = Set(vects) + push!(svects, [0 for _ in 1:d]) - for _ in 1:20 - v = rand([i for i in 1:10], d) - diff, best = get_max_below(t, v) - if v in svects - @test diff == 0 - @test v == best - else - @test (best in svects) - @test diff == sum(v .- best) - end + for _ in 1:20 + v = rand([i for i in 1:10], d) + diff, best = get_max_below(t, v) + if v in svects + @test diff == 0 + @test v == best + else + @test (best in svects) + @test diff == sum(v .- best) end end end diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index 0a37b8f41..a917066dd 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -1,688 +1,686 @@ -@static if VERSION >= v"1.10.0" - if GROUP == "All" || GROUP == "ModelingToolkitExt" - @testset "Check identifiability of `ODESystem` object" begin - using ModelingToolkit - using ModelingToolkit: parameters - using Symbolics - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = - [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal( - correct, - assess_identifiability(de; measured_quantities = [y1 ~ x0]), - ) - @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) - @test isequal( - correct, - assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), - ) - - # check identifiabile functions - correct = [a01 * a12, a01 + a12 + a21] - result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - - # check identifiabile functions - @parameters V_m k_m k01 c - @variables t x(t) y1(t) [output = true] - D = Differential(t) - - eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] - de = ODESystem(eqs, t, name = :Test) - - correct = [k01, c * k_m, V_m * c] - result = find_identifiable_functions(de) - @test isequal(Set(correct), Set(result)) - - correct = [k01, c * x, k_m * c, V_m * c] - result = find_identifiable_functions(de, with_states = true) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - eqs = - [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - x0 => :globally, - x1 => :nonidentifiable, - ) - - @test isequal(correct, assess_identifiability(de)) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) [output = true] - D = Differential(t) - - 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 = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal( - correct, - assess_identifiability(de; funcs_to_check = funcs_to_check), - ) - - # -------------------------------------------------------------------------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - - eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] - measured_quantities = [y1 ~ x0] - de = ODESystem(eqs, t, name = :Test) - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = OrderedDict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # -------------------------------------------------------------------------- - @parameters μ bi bw a χ γ k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, - D(W) ~ χ * (I - W), - D(R) ~ γ * I - (μ + a) * R, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - measured_quantities = [y ~ k * I] - # check all parameters (default) - @test isequal( - true, - all( - values( - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - ), - ), - ), - ) - - # check specific parameters - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # checking identifiabile functions - correct = [a, bw, χ, bi, k, γ, μ] - result = - find_identifiable_functions(de, measured_quantities = measured_quantities) - @test isequal(Set(correct), Set(result)) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) [output = true] - - eqs = [ - D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - W), - D(R) ~ gm * I - (mu + a) * R, - y ~ k * I, - ] - de = ODESystem(eqs, t, name = :TestSIWR) - # check all parameters (default) - @test isequal(true, all(values(assess_local_identifiability(de)))) - - @test isequal( - true, - all( - values( - assess_local_identifiability(de; measured_quantities = [y ~ k * I]), +if GROUP == "All" || GROUP == "ModelingToolkitExt" + @testset "Check identifiability of `ODESystem` object" begin + using ModelingToolkit + using ModelingToolkit: parameters + using Symbolics + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] + D = Differential(t) + + eqs = + [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) + + @test isequal( + correct, + assess_identifiability(de; measured_quantities = [y1 ~ x0]), + ) + @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) + @test isequal( + correct, + assess_identifiability(de; measured_quantities = [(y1 ~ x0).rhs]), + ) + + # check identifiabile functions + correct = [a01 * a12, a01 + a12 + a21] + result = find_identifiable_functions(de, measured_quantities = [y1 ~ x0]) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + + # check identifiabile functions + @parameters V_m k_m k01 c + @variables t x(t) y1(t) [output = true] + D = Differential(t) + + eqs = [D(x) ~ (-V_m * x) / (k_m + x) + k01 * x, y1 ~ c * x] + de = ODESystem(eqs, t, name = :Test) + + correct = [k01, c * k_m, V_m * c] + result = find_identifiable_functions(de) + @test isequal(Set(correct), Set(result)) + + correct = [k01, c * x, k_m * c, V_m * c] + result = find_identifiable_functions(de, with_states = true) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] + D = Differential(t) + + eqs = + [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + x0 => :globally, + x1 => :nonidentifiable, + ) + + @test isequal(correct, assess_identifiability(de)) + + # -------------------------------------------------------------------------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) [output = true] + D = Differential(t) + + 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 = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal( + correct, + assess_identifiability(de; funcs_to_check = funcs_to_check), + ) + + # -------------------------------------------------------------------------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) + + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1] + measured_quantities = [y1 ~ x0] + de = ODESystem(eqs, t, name = :Test) + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = OrderedDict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # -------------------------------------------------------------------------- + @parameters μ bi bw a χ γ k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ μ - bi * S * I - bw * S * W - μ * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (γ + μ) * I, + D(W) ~ χ * (I - W), + D(R) ~ γ * I - (μ + a) * R, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + measured_quantities = [y ~ k * I] + # check all parameters (default) + @test isequal( + true, + all( + values( + assess_local_identifiability( + de; + measured_quantities = measured_quantities, ), ), - ) - - # check specific parameters - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability(de; funcs_to_check = funcs_to_check), - ) - - # checking ME identifiability - funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, - ), - ) - - # -------------------------------------------------------------------------- - @parameters mu bi bw a xi gm k - @variables t S(t) I(t) W(t) R(t) y(t) - - eqs = [ - D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, - D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, - D(W) ~ xi * (I - 0.6 * W), - D(R) ~ gm * I - (mu + a) * R, - ] - 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 = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - correct, - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - ), - ) - - # checking ME identifiability - funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] - correct = OrderedDict(f => true for f in funcs_to_check) - @test isequal( - (correct, 1), - assess_local_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = funcs_to_check, - prob_threshold = 0.99, - type = :ME, + ), + ) + + # check specific parameters + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ), + ) + + # checking ME identifiability + funcs_to_check = [μ, bi, bw, a, χ, γ, γ + μ, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # checking identifiabile functions + correct = [a, bw, χ, bi, k, γ, μ] + result = + find_identifiable_functions(de, measured_quantities = measured_quantities) + @test isequal(Set(correct), Set(result)) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) [output = true] + + eqs = [ + D(S) ~ mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - W), + D(R) ~ gm * I - (mu + a) * R, + y ~ k * I, + ] + de = ODESystem(eqs, t, name = :TestSIWR) + # check all parameters (default) + @test isequal(true, all(values(assess_local_identifiability(de)))) + + @test isequal( + true, + all( + values( + assess_local_identifiability(de; measured_quantities = [y ~ k * I]), ), - ) - - # ---------- - - @parameters a01 a21 a12 - @variables t x0(t) x1(t) y1(t) - D = Differential(t) - using SpecialFunctions - - eqs = [ - D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, - D(x1) ~ a21 * x0 - a12 * x1, - ] - - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y1 ~ x0] - funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] - correct = Dict( - a01 => :nonidentifiable, - a21 => :nonidentifiable, - a12 => :nonidentifiable, - a01 * a12 => :globally, - a01 + a12 + a21 => :globally, - ) - @test_throws ArgumentError assess_identifiability( + ), + ) + + # check specific parameters + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k, S, I, W, R] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability(de; funcs_to_check = funcs_to_check), + ) + + # checking ME identifiability + funcs_to_check = [mu, bi, bw, a, xi, gm, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # -------------------------------------------------------------------------- + @parameters mu bi bw a xi gm k + @variables t S(t) I(t) W(t) R(t) y(t) + + eqs = [ + D(S) ~ 2.0 * mu - bi * S * I - bw * S * W - mu * S + a * R, + D(I) ~ bw * S * W + bi * S * I - (gm + mu) * I, + D(W) ~ xi * (I - 0.6 * W), + D(R) ~ gm * I - (mu + a) * R, + ] + 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 = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + correct, + assess_local_identifiability( de; measured_quantities = measured_quantities, funcs_to_check = funcs_to_check, - ) - # ---------- - @parameters a b c - @variables t x1(t) x2(t) y(t) + ), + ) + + # checking ME identifiability + funcs_to_check = [bi, bw, a, xi, gm, mu, gm + mu, k] + correct = OrderedDict(f => true for f in funcs_to_check) + @test isequal( + (correct, 1), + assess_local_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + prob_threshold = 0.99, + type = :ME, + ), + ) + + # ---------- + + @parameters a01 a21 a12 + @variables t x0(t) x1(t) y1(t) + D = Differential(t) + using SpecialFunctions + + eqs = [ + D(x0) ~ -(a01 + a21) * SpecialFunctions.erfc(x0) + a12 * x1, + D(x1) ~ a21 * x0 - a12 * x1, + ] + + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y1 ~ x0] + funcs_to_check = [a01, a21, a12, a01 * a12, a01 + a12 + a21] + correct = Dict( + a01 => :nonidentifiable, + a21 => :nonidentifiable, + a12 => :nonidentifiable, + a01 * a12 => :globally, + a01 + a12 + a21 => :globally, + ) + @test_throws ArgumentError assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = funcs_to_check, + ) + # ---------- + @parameters a b c + @variables t x1(t) x2(t) y(t) + D = Differential(t) + + eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] + de = ODESystem(eqs, t, name = :Test) + measured_quantities = [y ~ x2] + correct = Dict(a => :globally, b => :globally, c => :locally) + to_check = [a, b, c] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, + ), + ) + + # check identifiabile functions + result = + find_identifiable_functions(de, measured_quantities = measured_quantities) + correct = [b, a, c^2] + @test isequal(Set(result), Set(correct)) + + # ---------- + @parameters a b + @variables t c(t) x1(t) x2(t) y1(t) y2(t) + D = Differential(t) + + eqs = [ + 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 = OrderedDict(a => :globally, b => :globally) + to_check = [a, b] + @test isequal( + correct, + assess_identifiability( + de; + measured_quantities = measured_quantities, + funcs_to_check = to_check, + ), + ) + + #---------------------------------- + # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) + @variables t + function rabbits_creator(; name) + ps = @parameters α = 1.5 + vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] D = Differential(t) + equs = [D(x) ~ α^2 * x + z] - eqs = [D(x1) ~ -a * x1 + x2 * b / (x1 + b / (c^2 - x2)), D(x2) ~ x2 * c^2 + x1] - de = ODESystem(eqs, t, name = :Test) - measured_quantities = [y ~ x2] - correct = Dict(a => :globally, b => :globally, c => :locally) - to_check = [a, b, c] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - # check identifiabile functions - result = - find_identifiable_functions(de, measured_quantities = measured_quantities) - correct = [b, a, c^2] - @test isequal(Set(result), Set(correct)) + ODESystem(equs, t, vars, ps; name = name) + end - # ---------- - @parameters a b - @variables t c(t) x1(t) x2(t) y1(t) y2(t) + function wolves_creator(; name) + ps = @parameters δ = 3.0 + vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] D = Differential(t) + equs = [D(y) ~ -δ * y + q] - eqs = [ - 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 = OrderedDict(a => :globally, b => :globally) - to_check = [a, b] - @test isequal( - correct, - assess_identifiability( - de; - measured_quantities = measured_quantities, - funcs_to_check = to_check, - ), - ) - - #---------------------------------- - # Composable models test (from https://github.com/SciML/StructuralIdentifiability.jl/issues/162) - @variables t - function rabbits_creator(; name) - ps = @parameters α = 1.5 - vars = @variables x(t) = 1.0 z(t) = 0.0 [input = true] - D = Differential(t) - equs = [D(x) ~ α^2 * x + z] - - ODESystem(equs, t, vars, ps; name = name) - end - - function wolves_creator(; name) - ps = @parameters δ = 3.0 - vars = @variables y(t) = 1.0 q(t) = 0.0 [input = true] - D = Differential(t) - equs = [D(y) ~ -δ * y + q] - - ODESystem(equs, t, vars, ps; name = name) - end - - function lotka_volterra_creator(; name) - @named wolves = wolves_creator() - @named rabbits = rabbits_creator() - - ps = @parameters β = 1.0 γ = 1.0 - D = Differential(t) - - eqs = [ - rabbits.z ~ -β * wolves.y * rabbits.x, - wolves.q ~ γ * wolves.y * rabbits.x, - ] - - ModelingToolkit.compose( - ODESystem(eqs, t, [], ps; name = name), - wolves, - rabbits, - ) - end - - function getbyname(sys, name) - println(name) - return first([ - v for v in vcat(states(sys), parameters(sys)) if - replace(string(v), "(t)" => "") == name - ]) - end - - @named ltk_mtk = lotka_volterra_creator() - simp_ltk_mtk = structural_simplify(ltk_mtk) - wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") - rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") - β = getbyname(simp_ltk_mtk, "β") - γ = getbyname(simp_ltk_mtk, "γ") - wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") - rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") - @variables y(t) - measured_quantities = [y ~ wolves₊y] - result = assess_identifiability( - simp_ltk_mtk, - measured_quantities = measured_quantities, - ) - correct = Dict( - rabbits₊α => :locally, - γ => :nonidentifiable, - β => :globally, - wolves₊δ => :globally, - rabbits₊x => :nonidentifiable, - wolves₊y => :globally, - ) - @test Dict(result) == correct - - #---------------------------------- - - @variables t, x(t), y(t), z(t), w(t) - @parameters a - @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) - measured_quantities = [w ~ x] - result = assess_identifiability(sys, measured_quantities = measured_quantities) - @test result[a] == :globally - - result = - find_identifiable_functions(sys, measured_quantities = measured_quantities) - @test isequal(result, [a]) - - #---------------------------------- - - # Tensor definition case as reported in - # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 - @variables t, x(t)[1:2], y(t)[1:2] - @parameters k1, k2 - - eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] - - sys = ODESystem(eqs, t, name = :example_vector) - correct = OrderedDict(x[1] => true, x[2] => true, k1 => true, k2 => true) - @test assess_local_identifiability(sys, measured_quantities = [x[1], x[2]]) == - correct + ODESystem(equs, t, vars, ps; name = name) end - @testset "Discrete local identifiability, ModelingToolkit interface" begin - cases = [] - - @parameters α β - @variables t S(t) I(t) R(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(S) ~ -β * S * I, D(I) ~ β * S * I - α * I, D(R) ~ α * I] - @named sir = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => sir, - :res => - OrderedDict(S => true, I => true, R => false, α => true, β => true), - :y => [y ~ I], - :y2 => [I], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) + function lotka_volterra_creator(; name) + @named wolves = wolves_creator() + @named rabbits = rabbits_creator() - @parameters θ - @variables t x(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x) ~ θ * x^3 - x] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x => true, θ => true), - :y => [y ~ x], - :y2 => [x], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) + ps = @parameters β = 1.0 γ = 1.0 + D = Differential(t) - @parameters θ β - @variables t x1(t) x2(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] - - @named eqs = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => eqs, - :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), - ) + eqs = [ + rabbits.z ~ -β * wolves.y * rabbits.x, + wolves.q ~ γ * wolves.y * rabbits.x, + ] - @parameters a b c d - @variables t x1(t) x2(t) u(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] - - @named lv = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - x1 => true, - x2 => false, - a => true, - b => false, - c => true, - d => true, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], - ), + ModelingToolkit.compose( + ODESystem(eqs, t, [], ps; name = name), + wolves, + rabbits, ) + end - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict(b * x2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => [b * x2], - ), - ) + function getbyname(sys, name) + println(name) + return first([ + v for v in vcat(states(sys), parameters(sys)) if + replace(string(v), "(t)" => "") == name + ]) + end - push!( - cases, - Dict( - :dds => lv, - :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{}[], - :to_check => Array{}[], - ), - ) + @named ltk_mtk = lotka_volterra_creator() + simp_ltk_mtk = structural_simplify(ltk_mtk) + wolves₊δ = getbyname(simp_ltk_mtk, "wolves₊δ") + rabbits₊α = getbyname(simp_ltk_mtk, "rabbits₊α") + β = getbyname(simp_ltk_mtk, "β") + γ = getbyname(simp_ltk_mtk, "γ") + wolves₊y = getbyname(simp_ltk_mtk, "wolves₊y") + rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") + @variables y(t) + measured_quantities = [y ~ wolves₊y] + result = assess_identifiability( + simp_ltk_mtk, + measured_quantities = measured_quantities, + ) + correct = Dict( + rabbits₊α => :locally, + γ => :nonidentifiable, + β => :globally, + wolves₊δ => :globally, + rabbits₊x => :nonidentifiable, + wolves₊y => :globally, + ) + @test Dict(result) == correct + + #---------------------------------- + + @variables t, x(t), y(t), z(t), w(t) + @parameters a + @named sys = ODESystem([D(x) ~ a * y], t, [x], [a]; observed = [y ~ z, z ~ x]) + measured_quantities = [w ~ x] + result = assess_identifiability(sys, measured_quantities = measured_quantities) + @test result[a] == :globally + + result = + find_identifiable_functions(sys, measured_quantities = measured_quantities) + @test isequal(result, [a]) + + #---------------------------------- + + # Tensor definition case as reported in + # https://github.com/SciML/StructuralIdentifiability.jl/issues/178 + @variables t, x(t)[1:2], y(t)[1:2] + @parameters k1, k2 + + eqs = [D(x[1]) ~ -k1 * x[2], D(x[2]) ~ -k2 * x[1]] + + sys = ODESystem(eqs, t, name = :example_vector) + 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 - push!( - cases, - Dict( - :dds => lv, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - substitute(x2, Dict(t => 0)) => true, - a => true, - b => true, - c => true, - d => true, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => [x2], - :to_check => Array{}[], + @testset "Discrete local identifiability, ModelingToolkit interface" begin + cases = [] + + @parameters α β + @variables t S(t) I(t) R(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(S) ~ -β * S * I, D(I) ~ β * S * I - α * I, D(R) ~ α * I] + @named sir = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => sir, + :res => + OrderedDict(S => true, I => true, R => false, α => true, β => true), + :y => [y ~ I], + :y2 => [I], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ + @variables t x(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x) ~ θ * x^3 - x] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x => true, θ => true), + :y => [y ~ x], + :y2 => [x], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters θ β + @variables t x1(t) x2(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ x1 + x2, D(x2) ~ θ + β] + + @named eqs = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => eqs, + :res => OrderedDict(x1 => true, x2 => true, θ => false, β => false), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b c d + @variables t x1(t) x2(t) u(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a * x1 - b * x1 * x2 + u, D(x2) ~ -c * x2 + d * x1 * x2] + + @named lv = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + x1 => true, + x2 => false, + a => true, + b => false, + c => true, + d => true, ), - ) - - # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 - @variables t x1(t) x2(t) u(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] - - @named abmd1 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd1, - :res => - OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict(b * x2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => [b * x2], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + x1 => true, + x2 => true, + a => true, + b => true, + c => true, + d => true, ), - ) - - # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 - @parameters theta1 theta2 theta3 - @variables t x1(t) x2(t) u(t) y(t) y2(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] - - @named abmd2 = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => abmd2, - :res => OrderedDict( - x1 => true, - x2 => false, - theta1 => true, - theta2 => false, - theta3 => false, - ), - :y => [y ~ x1], - :y2 => [x1], - :known_ic => Array{}[], - :to_check => Array{}[], + :y => [y ~ x1, y2 ~ x1 / x2], + :y2 => [x1, x1 / x2], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + push!( + cases, + Dict( + :dds => lv, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + substitute(x2, Dict(t => 0)) => true, + a => true, + b => true, + c => true, + d => true, ), - ) - push!( - cases, - Dict( - :dds => abmd2, - :res => Dict( - x1 => true, - x2 => true, - theta1 => true, - theta2 => true, - theta3 => true, - ), - :y => [y ~ x1, y2 ~ x2], - :y2 => [x1, x2], - :known_ic => Array{}[], - :to_check => Array{}[], + :y => [y ~ x1], + :y2 => [x1], + :known_ic => [x2], + :to_check => Array{}[], + ), + ) + + # Example 1 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 + @variables t x1(t) x2(t) u(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1 + x2, D(x2) ~ (1 - theta2) * x1 + x2^2 + u - x2] + + @named abmd1 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd1, + :res => + OrderedDict(x1 => true, x2 => true, theta1 => true, theta2 => true), + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + # Example 2 from https://doi.org/10.1016/j.automatica.2008.03.019 + @parameters theta1 theta2 theta3 + @variables t x1(t) x2(t) u(t) y(t) y2(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ theta1 * x1^2 + theta2 * x2 + u - x1, D(x2) ~ theta3 * x1 - x2] + + @named abmd2 = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => abmd2, + :res => OrderedDict( + x1 => true, + x2 => false, + theta1 => true, + theta2 => false, + theta3 => false, ), - ) - - @parameters a b - @variables t x1(t) y(t) - D = Difference(t; dt = 1.0) - - eqs = [D(x1) ~ a] - - @named kic = DiscreteSystem(eqs) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict(x1 => false, a => true, b => false), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => Array{}[], - :to_check => Array{}[], + :y => [y ~ x1], + :y2 => [x1], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => abmd2, + :res => Dict( + x1 => true, + x2 => true, + theta1 => true, + theta2 => true, + theta3 => true, ), - ) - push!( - cases, - Dict( - :dds => kic, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - a => true, - b => true, - ), - :y => [y ~ x1 + b], - :y2 => [x1 + b], - :known_ic => [x1], - :to_check => Array{}[], + :y => [y ~ x1, y2 ~ x2], + :y2 => [x1, x2], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + + @parameters a b + @variables t x1(t) y(t) + D = Difference(t; dt = 1.0) + + eqs = [D(x1) ~ a] + + @named kic = DiscreteSystem(eqs) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict(x1 => false, a => true, b => false), + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => Array{}[], + :to_check => Array{}[], + ), + ) + push!( + cases, + Dict( + :dds => kic, + :res => OrderedDict( + substitute(x1, Dict(t => 0)) => true, + a => true, + b => true, ), - ) - - for c in cases - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - @test assess_local_identifiability( - c[:dds]; - measured_quantities = c[:y2], - known_ic = c[:known_ic], - funcs_to_check = c[:to_check], - ) == c[:res] - end + :y => [y ~ x1 + b], + :y2 => [x1 + b], + :known_ic => [x1], + :to_check => Array{}[], + ), + ) + + for c in cases + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] + @test assess_local_identifiability( + c[:dds]; + measured_quantities = c[:y2], + known_ic = c[:known_ic], + funcs_to_check = c[:to_check], + ) == c[:res] end end end diff --git a/test/extract_coefficients.jl b/test/extract_coefficients.jl index a6092a23f..24d195db0 100644 --- a/test/extract_coefficients.jl +++ b/test/extract_coefficients.jl @@ -1,61 +1,59 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Coefficient extraction for rational fucntions" begin - R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) - C = extract_coefficients_ratfunc( - (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), - [z], - ) +@testset "Coefficient extraction for rational fucntions" begin + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) + C = extract_coefficients_ratfunc( + (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), + [z], + ) - @test Set(C) == Set([ - one(R) // 1, - (3 * x - y^2) // 1, - y // 1, - x^2 // 1, - (x + y) // 1, - (x^2 + 1) // 1, - ]) + @test Set(C) == Set([ + one(R) // 1, + (3 * x - y^2) // 1, + y // 1, + x^2 // 1, + (x + y) // 1, + (x^2 + 1) // 1, + ]) - R, (x, y) = polynomial_ring(QQ, ["x", "y"]) - f = (x^2 + y^2) // (1 - x - 3 * y) - @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.QQMPolyRingElem}())) == - Set([f, one(R) // 1]) + R, (x, y) = polynomial_ring(QQ, ["x", "y"]) + f = (x^2 + y^2) // (1 - x - 3 * y) + @test Set(extract_coefficients_ratfunc(f, Vector{Nemo.QQMPolyRingElem}())) == + Set([f, one(R) // 1]) - R, (x, y, u, v) = polynomial_ring(QQ, ["x", "y", "u", "v"]) - C = extract_coefficients_ratfunc( - (x + (y + 3) * u * v + y^2 * v^3) // (u + 3 * v - (x^2 + y^2) * u^2), - [u, v], - ) - @test Set(C) == Set([ - x // 1, - (y + 3) // 1, - y^2 // 1, - one(R) // 1, - 3 * one(R) // 1, - -(x^2 + y^2) // 1, - ]) - end + R, (x, y, u, v) = polynomial_ring(QQ, ["x", "y", "u", "v"]) + C = extract_coefficients_ratfunc( + (x + (y + 3) * u * v + y^2 * v^3) // (u + 3 * v - (x^2 + y^2) * u^2), + [u, v], + ) + @test Set(C) == Set([ + x // 1, + (y + 3) // 1, + y^2 // 1, + one(R) // 1, + 3 * one(R) // 1, + -(x^2 + y^2) // 1, + ]) +end - @testset "Coefficient extraction for polynomials" begin - R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) - C = extract_coefficients((y + z + 8), [x]) - R_coef = parent(first(values(C))) - y, z = gens(R_coef) - @test symbols(R_coef) == [:y, :z] - @test C == Dict([0] => y + z + 8) +@testset "Coefficient extraction for polynomials" begin + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) + C = extract_coefficients((y + z + 8), [x]) + R_coef = parent(first(values(C))) + y, z = gens(R_coef) + @test symbols(R_coef) == [:y, :z] + @test C == Dict([0] => y + z + 8) - R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) - C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [z]) - R_coef = parent(first(values(C))) - x, y = gens(R_coef) - @test symbols(R_coef) == [:x, :y] - @test C == Dict([3] => 3x - y^2, [1] => y, [0] => x^2) + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) + C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [z]) + R_coef = parent(first(values(C))) + x, y = gens(R_coef) + @test symbols(R_coef) == [:x, :y] + @test C == Dict([3] => 3x - y^2, [1] => y, [0] => x^2) - R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) - C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [x, z]) - R_coef = parent(first(values(C))) - y = gens(R_coef)[1] - @test symbols(R_coef) == [:y] - @test C == - Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) - end + R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) + C = extract_coefficients((x^2 + y * z - y^2 * z^3 + 3 * x * z^3), [x, z]) + R_coef = parent(first(values(C))) + y = gens(R_coef)[1] + @test symbols(R_coef) == [:y] + @test C == + Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) end diff --git a/test/find_leader.jl b/test/find_leader.jl index de9409632..5b12feed9 100644 --- a/test/find_leader.jl +++ b/test/find_leader.jl @@ -1,26 +1,24 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Finding leader" begin - ode = @ODEmodel( - x1'(t) = x3(t), - x2'(t) = a * x2(t), - x3'(t) = x1(t), - y1(t) = x1(t), - y2(t) = x2(t) + u(t) - ) - ioeqs = find_ioequations(ode) - println("IOEQS: ", ioeqs) - pbr = PBRepresentation(ode, ioeqs) +@testset "Finding leader" begin + ode = @ODEmodel( + x1'(t) = x3(t), + x2'(t) = a * x2(t), + x3'(t) = x1(t), + y1(t) = x1(t), + y2(t) = x2(t) + u(t) + ) + ioeqs = find_ioequations(ode) + println("IOEQS: ", ioeqs) + pbr = PBRepresentation(ode, ioeqs) - R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.polynomial_ring( - Nemo.QQ, - ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y2(t)_0", "y2(t)_1", "y2(t)_2"], - ) - @test find_leader([a, x1, x2, u], pbr) == nothing - @test find_leader([a, x1, y1_0, x2], pbr) == y1_0 - @test find_leader([y1_0, y1_1, y2_0, y2_1], pbr) == y2_1 - l = find_leader([y1_2, y2_1], pbr) - @test (pbr.y_names[1] == "y1(t)" && l == y2_1) || - (pbr.y_names[1] == "y2(t)" && l == y1_2) - @test find_leader([y1_2, y2_0], pbr) == y1_2 - end + R, (y1_0, y1_1, y1_2, y2_0, y2_1, y2_2) = Nemo.polynomial_ring( + Nemo.QQ, + ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y2(t)_0", "y2(t)_1", "y2(t)_2"], + ) + @test find_leader([a, x1, x2, u], pbr) == nothing + @test find_leader([a, x1, y1_0, x2], pbr) == y1_0 + @test find_leader([y1_0, y1_1, y2_0, y2_1], pbr) == y2_1 + l = find_leader([y1_2, y2_1], pbr) + @test (pbr.y_names[1] == "y1(t)" && l == y2_1) || + (pbr.y_names[1] == "y2(t)" && l == y1_2) + @test find_leader([y1_2, y2_0], pbr) == y1_2 end diff --git a/test/identifiability.jl b/test/identifiability.jl index 1b51ed5a9..cec848d14 100644 --- a/test/identifiability.jl +++ b/test/identifiability.jl @@ -1,237 +1,235 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Assessing identifiability" begin - test_cases = [] - - # 2-compartiment model - - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = - [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] - correct = [ - :nonidentifiable, - :nonidentifiable, - :nonidentifiable, - :globally, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - # No parameters no worry - - ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) - funcs_to_test = [x1, x2, x1 + x2] - correct = [:nonidentifiable, :nonidentifiable, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - # Also test when `funcs_to_test` is empty! - funcs_to_test = Vector{typeof(x1)}() - correct = OrderedDict(x1 => :nonidentifiable, x2 => :nonidentifiable) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), - x1'(t) = c * x1(t) + d * x0(t) * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [a, b, c, d, b * x1, x0, x1] - correct = [ - :globally, - :nonidentifiable, - :globally, - :globally, - :globally, - :globally, - :nonidentifiable, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), - I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), - W'(t) = xi * (I(t) - W(t)), - R'(t) = gam * I(t) - (mu + a) * R(t), - y(t) = k * I(t) - ) - funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k] - correct = [:globally for _ in funcs_to_test] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) - ) - funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] - correct = [ - :globally, - :globally, - :nonidentifiable, - :locally, - :locally, - :nonidentifiable, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = (1 + x1(t)^2) // 2, - x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), - y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), - y2(t) = x2(t) - ) - funcs_to_test = [b, x1, x2] - correct = [:globally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -a1 * x1(t) + a21 * x2(t), - x2'(t) = -a2 * x2(t) - a21 * x2(t), - y1(t) = x1(t) - ) - funcs_to_test = [a1, a2, a21] - correct = [:locally, :nonidentifiable, :nonidentifiable] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -a1 * x1(t) + a21 * x2(t), - x2'(t) = -a2 * x2(t) - a21 * x2(t) + u(t), - y1(t) = x1(t) - ) - funcs_to_test = [a1, a2, a21, a2 + a1, a2 * (a1 - a21)] - correct = [:locally, :locally, :globally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - x1'(t) = -(a21 + a31 + a01) * x1(t) + a12 * x2(t) + a13 * x3(t) + u(t), - x2'(t) = a21 * x1(t) - a12 * x2(t), - x3'(t) = a31 * x1(t) - a13 * x3(t), - y(t) = x1(t) - ) - funcs_to_test = [x1, x2, x3, a31 + a21, (a21 - a31) // (a12 - a13)] - correct = [:globally, :locally, :locally, :globally, :globally] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - ode = @ODEmodel( - S'(t) = -b * S(t) * In(t) / N, - E'(t) = b * S(t) * In(t) / N - nu * E(t), - In'(t) = nu * E(t) - a * In(t), - y1(t) = In(t), - y2(t) = N - ) - funcs_to_test = [b, N, In, a, nu, S, E, a * nu, a + nu] - correct = [ - :globally, - :globally, - :globally, - :locally, - :locally, - :locally, - :locally, - :globally, - :globally, - ] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) - - #-------------------------------------------------------------------------- - - for case in test_cases - result = assess_identifiability(case[:ode], funcs_to_check = case[:funcs]) - @test result == case[:correct] - end +@testset "Assessing identifiability" begin + test_cases = [] + + # 2-compartiment model + + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = + [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] + correct = [ + :nonidentifiable, + :nonidentifiable, + :nonidentifiable, + :globally, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + # No parameters no worry + + ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) + funcs_to_test = [x1, x2, x1 + x2] + correct = [:nonidentifiable, :nonidentifiable, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + # Also test when `funcs_to_test` is empty! + funcs_to_test = Vector{typeof(x1)}() + correct = OrderedDict(x1 => :nonidentifiable, x2 => :nonidentifiable) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), + x1'(t) = c * x1(t) + d * x0(t) * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [a, b, c, d, b * x1, x0, x1] + correct = [ + :globally, + :nonidentifiable, + :globally, + :globally, + :globally, + :globally, + :nonidentifiable, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), + I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), + W'(t) = xi * (I(t) - W(t)), + R'(t) = gam * I(t) - (mu + a) * R(t), + y(t) = k * I(t) + ) + funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k] + correct = [:globally for _ in funcs_to_test] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) + ) + funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] + correct = [ + :globally, + :globally, + :nonidentifiable, + :locally, + :locally, + :nonidentifiable, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = (1 + x1(t)^2) // 2, + x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), + y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), + y2(t) = x2(t) + ) + funcs_to_test = [b, x1, x2] + correct = [:globally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -a1 * x1(t) + a21 * x2(t), + x2'(t) = -a2 * x2(t) - a21 * x2(t), + y1(t) = x1(t) + ) + funcs_to_test = [a1, a2, a21] + correct = [:locally, :nonidentifiable, :nonidentifiable] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -a1 * x1(t) + a21 * x2(t), + x2'(t) = -a2 * x2(t) - a21 * x2(t) + u(t), + y1(t) = x1(t) + ) + funcs_to_test = [a1, a2, a21, a2 + a1, a2 * (a1 - a21)] + correct = [:locally, :locally, :globally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + x1'(t) = -(a21 + a31 + a01) * x1(t) + a12 * x2(t) + a13 * x3(t) + u(t), + x2'(t) = a21 * x1(t) - a12 * x2(t), + x3'(t) = a31 * x1(t) - a13 * x3(t), + y(t) = x1(t) + ) + funcs_to_test = [x1, x2, x3, a31 + a21, (a21 - a31) // (a12 - a13)] + correct = [:globally, :locally, :locally, :globally, :globally] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + ode = @ODEmodel( + S'(t) = -b * S(t) * In(t) / N, + E'(t) = b * S(t) * In(t) / N - nu * E(t), + In'(t) = nu * E(t) - a * In(t), + y1(t) = In(t), + y2(t) = N + ) + funcs_to_test = [b, N, In, a, nu, S, E, a * nu, a + nu] + correct = [ + :globally, + :globally, + :globally, + :locally, + :locally, + :locally, + :locally, + :globally, + :globally, + ] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) + + #-------------------------------------------------------------------------- + + for case in test_cases + result = assess_identifiability(case[:ode], funcs_to_check = case[:funcs]) + @test result == case[:correct] end end diff --git a/test/identifiable_functions.jl b/test/identifiable_functions.jl index 04376531a..4c2946cc9 100644 --- a/test/identifiable_functions.jl +++ b/test/identifiable_functions.jl @@ -1,1007 +1,1005 @@ -if GROUP == "All" || GROUP == "Core" - # For each ODE system we check the equality (in terms of fields of rational - # functions) of the true set of identifiable functions and the obtained - # simplified set - test_cases = [] - - ### - - ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) - ident_funcs = [a] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel(x1'(t) = a, x2'(t) = -a, y(t) = x1 + x2) - ident_funcs = [] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Parameter a is not identifiable, and neither are any combinations thereof. - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x2(t) - a, - x2'(t) = x1(t) + a, - y(t) = x1(t) + x2(t) - ) - ident_funcs = - Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() - - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Example 2 from - # "On Global Identifiability for Arbitrary Model Parametrizations", - # DOI: 10.1016/0005-1098(94)90029-9 - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = Θ * x2(t)^2, - x2'(t) = u(t), - y(t) = x1(t) - ) - ident_funcs = [Θ] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Example 4 from - # "On Global Identifiability for Arbitrary Model Parametrizations", - # DOI: 10.1016/0005-1098(94)90029-9 - ode = StructuralIdentifiability.@ODEmodel( - x'(t) = (-V_m * x(t)) / (k_m + x(t)) + k01 * x(t), - y(t) = c * x(t) - ) - ident_funcs = [k01, k_m // V_m, V_m * c] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Parameters b and c enter the io equations only as the product b * c. - ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) - ident_funcs = [b * c, a] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # 2-compartiment model - ode = StructuralIdentifiability.@ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - ident_funcs = [(a01 * a12), (a01 + a12 + a21)] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # TODO: uncomment when identifiability can handle models with no states - # ode = StructuralIdentifiability.@ODEmodel( - # y(t) = a*u(t) - # ) - # ident_funcs = [(a01 * a12), (a01 + a12 + a21)] - # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Example 2 from - # "On Structural Identifiability", - # DOI: https://doi.org/10.1016/0025-5564(70)90132-X - # - # More or less the same 2-compartmental model as the one given above - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -(k1 + k2) * x1(t) + k3 * x2(t) + u(t), - x2'(t) = k2 * x1(t) - (k3 + k4) * x2(t), - y(t) = x1(t) - ) - ident_funcs = [(k1 + k2), (k1 + k2 + k3 + k4), ((k1 + k2) * (k3 + k4) - k2 * k3)] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Diagonal with simple spectrum and observable states - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = λ1 * x1(t) + β1 * u1(t), - x2'(t) = λ2 * x2(t) + β2 * u2(t), - x3'(t) = λ3 * x3(t) + β3 * u3(t), - y(t) = x1(t) + x2(t) + x3(t) - ) - ident_funcs = [λ1, λ2, λ3, β1, β2, β3] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # 3 compartments: - # x1 <--> x2 <--> x3 - # If we observe x1 and control x1, then all parameters are identifiable - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -a1 * x1(t) + b1 * x2(t) + u(t), - x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), - x3'(t) = -b2 * x3(t) + a2 * x2(t), - y(t) = x1(t) - ) - ident_funcs = [a1, a2, b1, b2] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Example 3 from - # "On Structural Identifiability", - # DOI: https://doi.org/10.1016/0025-5564(70)90132-X - # - # 3 compartments: - # x1 <--> x2 <--> x3 - # If we observe x1 and control x3, then only some functions of parameters - # are identifiable - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -a1 * x1(t) + b1 * x2(t), - x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), - x3'(t) = -b2 * x3(t) + a2 * x2(t) + u(t), - y(t) = x1(t) - ) - ident_funcs = [b1 * b2, a1 + a2 + b1 + b2, a1 * a2 + a1 * b2] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Example 3 from - # "On the identifiability and distinguishability of nonlinear parametric - # models", - # DOI: https://doi.org/10.1016/0378-4754(95)00123-9 - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = p1 * x1^2 + p2 * x1 * x2, - x2'(t) = p3 * x1^2 + p4 * x1 * x2, - y(t) = x1 - ) - ident_funcs = [p1 + p4, p2 * p3 - p4 * p1] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Goowdin oscillator - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) - ) - ident_funcs = [sigma, delta + beta, c, b, delta * beta] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # SIRS forced - ode = StructuralIdentifiability.@ODEmodel( - s'(t) = mu - mu * s(t) - b0 * (1 + b1 * x1(t)) * i(t) * s(t) + g * r(t), - i'(t) = b0 * (1 + b1 * x1(t)) * i(t) * s(t) - (nu + mu) * i(t), - r'(t) = nu * i(t) - (mu + g) * r(t), - x1'(t) = -M * x2(t), - x2'(t) = M * x1(t), - y1(t) = i(t), - y2(t) = r(t) - ) - ident_funcs = [g, mu, b0, nu, M^2] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # SEIR_1_io - ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -beta * S(t) * I(t), - E'(t) = beta * S(t) * I(t) - v * E(t), - I'(t) = v * E(t) - psi * I(t) - (1 - psi) * gamma * I(t), - R'(t) = gamma * Q(t) + (1 - psi) * gamma * I(t), - Q'(t) = -gamma * Q(t) + psi * I(t), - y1(t) = Q(t) - ) - ident_funcs = [gamma, beta // psi, gamma * psi - v - psi, gamma * psi - v * psi] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Bilirubin2_io. - # Regression test: failed before, as the total degrees were being estimated - # incorrectly in the interpolation - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = - -(k21 + k31 + k41 + k01) * x1(t) + - k12 * x2(t) + - k13 * x3(t) + - k14 * x4(t) + - u(t), - x2'(t) = k21 * x1(t) - k12 * x2(t), - x3'(t) = k31 * x1(t) - k13 * x3(t), - x4'(t) = k41 * x1(t) - k14 * x4(t), - y1(t) = x1(t) - ) - ident_funcs = [ - k01 // one(k01), - k12 * k13 * k14 // one(k01), - k31 * k21 * k41 // one(k01), - k12 + k13 + k14 // one(k01), - k31 + k21 + k41 // one(k01), - k12 * k13 + k12 * k14 + k13 * k14 // one(k01), - k31 * k21 + k31 * k41 + k21 * k41 // one(k01), - k31 * k12 - 2 * k31 * k13 + k31 * k14 - 2 * k21 * k12 + - k21 * k13 + - k21 * k14 + - k12 * k41 + - k13 * k41 - 2 * k14 * k41 // one(k01), - ] - # Too slow with hybrid strategy :( - # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # Biohydrogenation_io - ode = StructuralIdentifiability.@ODEmodel( - x5'(t) = - ( - k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - - k6 * x5(t) * k7 - x5(t) * k7 * x4(t) - ) // ( - k8 * k6 + - k8 * x4(t) + - k6 * x6(t) + - k6 * x5(t) + - x6(t) * x4(t) + - x5(t) * x4(t) - ), - x7'(t) = (k9 * k10 * x6(t) - k9 * x6(t)^2) // k10, - x4'(t) = (-k5 * x4(t)) // (k6 + x4(t)), - x6'(t) = - ( - -k8 * k9 * k10 * x6(t) + k8 * k9 * x6(t)^2 - k9 * k10 * x6(t)^2 - - k9 * k10 * x6(t) * x5(t) + - k9 * x6(t)^3 + - k9 * x6(t)^2 * x5(t) + - k10 * x5(t) * k7 - ) // (k8 * k10 + k10 * x6(t) + k10 * x5(t)), - y1(t) = x4(t), - y2(t) = x5(t) - ) - ident_funcs = [k7, k6, k5, k10^2, k9 * k10, k8 + 1 // 2 * k10] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # SLIQR - ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -b * In(t) * S(t) * Ninv - S(t) * Ninv * u(t), - In'(t) = -In(t) * g + s * Q(t) + a * L(t), - L'(t) = b * In(t) * S(t) * Ninv - a * L(t), - Q'(t) = -e * In(t) * g + In(t) * g - s * Q(t), - y(t) = In(t) * Ninv - ) - ident_funcs = [ - g + a, - s + g + a, - s, - Ninv, - b, - (e * s - s + a) // (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), - e * s * g + s * a + g * a, - (e * s^2 + e * s * g - s^2 - s * g + g * a + a^2) // - (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), - e * s * g * a, - 2 * e * Ninv * s * g + 2 * Ninv * s * a + 2 * Ninv * g * a, - ] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # St. - # Regression test: - # Failed before, as the degrees of Groebner basis were too large - ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -e * S(t) - S(t) * d * W(t) + S(t) * r - S(t) * a * W(t) + R(t) * g, - R'(t) = e * S(t) + rR * R(t) + S(t) * a * W(t) - dr * R(t) * W(t) - R(t) * g, - W'(t) = T * Dd - W(t) * Dd, - y1(t) = S(t) + R(t), - y2(t) = T - ) - ident_funcs = [ - T, - Dd, - e - rR + dr * T + d * T + g - r + a * T, - (2 * rR * d - 2 * dr * r) // (dr - d), - (dr^2 + d^2 + 2 * d * a + a^2) // (dr * d + dr * a), - (e * dr - e * d + rR * a + dr * g - d * g - r * a) // (dr - d), - (e * dr^2 - e * dr * d + rR * dr * a + dr * d * g - dr * r * a - d^2 * g) // - (dr^2 + dr * a - d^2 - d * a), - ] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - # QY system. - # (this is a big one) - ode = StructuralIdentifiability.@ODEmodel( - P3'(t) = P4(t), - P0'(t) = P1(t), - P5'(t) = - ( - -P0(t) * beta_SI * phi * Mar * Ks * siga2 + - P0(t) * beta_SI * Mar * Ks * siga2 - - P0(t) * phi * M * Mar * Ks * beta_SA + - P0(t) * phi * M * Ks * siga2 * beta_SA + - P0(t) * M * Mar * Ks * beta_SA - P1(t) * beta_SI * phi * Mar * siga2 - - P1(t) * beta_SI * phi * Ks * siga2 + - P1(t) * beta_SI * Mar * siga2 + - P1(t) * beta_SI * Ks * siga2 - P1(t) * phi * M * Mar * beta_SA + - P1(t) * phi * M * siga2 * beta_SA - P1(t) * phi * Mar * Ks * beta_SA + - P1(t) * phi * Ks * siga2 * beta_SA + - P1(t) * M * Mar * beta_SA + - P1(t) * M * Ks * beta_SA + - P1(t) * Mar * Ks * beta_SA - beta_SI * phi * P2(t) * siga2 + - beta_SI * P2(t) * siga2 + - P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - - phi * M * beta * siga2 - phi * P2(t) * Mar * beta_SA + - phi * P2(t) * siga2 * beta_SA + - M * P2(t) * beta_SA + - M * Mar * P5(t) * siga2 + - M * beta * siga2 + - P2(t) * Mar * beta_SA + - P2(t) * Ks * beta_SA - ) // (phi * M * siga2 - M * siga2), - P4'(t) = - ( - -siga1 * P0(t)^2 * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + - siga1 * P0(t)^2 * beta_SI * M * Mar * Ks^2 * siga2^2 - - siga1 * P0(t)^2 * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + - siga1 * P0(t)^2 * phi * M^2 * Ks^2 * siga2^2 * beta_SA + - siga1 * P0(t)^2 * M^2 * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2 - - 2 * siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks * siga2^2 - - siga1 * P0(t) * P1(t) * beta_SI * phi * M * Ks^2 * siga2^2 - - siga1 * P0(t) * P1(t) * beta_SI * phi * Mar * Ks^2 * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2 + - 2 * siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * M * Ks^2 * siga2^2 + - siga1 * P0(t) * P1(t) * beta_SI * Mar * Ks^2 * siga2^2 - - siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * beta_SA - - 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Ks * siga2^2 * beta_SA - - 2 * siga1 * P0(t) * P1(t) * phi * M * Mar * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * phi * M * Ks^2 * siga2^2 * beta_SA + - siga1 * P0(t) * P1(t) * M^2 * Mar * Ks^2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * M^2 * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * P1(t) * M^2 * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * P1(t) * M * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 + - siga1 * P0(t) * beta_SI * P3(t) * Mar * Ks * siga2 - - siga1 * P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - - siga1 * P0(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - - siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2 - - siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 + - siga1 * P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + - siga1 * P0(t) * beta_SI * M * P2(t) * Ks * siga2^2 + - siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2 + - siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 - - siga1 * P0(t) * P3(t) * phi * M * Mar * Ks * beta_SA + - siga1 * P0(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + - siga1 * P0(t) * P3(t) * M * Mar * Ks * beta_SA + - siga1 * P0(t) * P3(t) * M * Ks * siga2 * beta_SA - - siga1 * P0(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA + - siga1 * P0(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA - - siga1 * P0(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - - siga1 * P0(t) * phi * M^2 * Ks * beta * siga2^2 - - siga1 * P0(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - - 2 * siga1 * P0(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P0(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA + - siga1 * P0(t) * M^2 * P2(t) * Mar * Ks * beta_SA + - siga1 * P0(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + - siga1 * P0(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + - siga1 * P0(t) * M^2 * Ks * beta * siga2^2 + - siga1 * P0(t) * M * P2(t) * Mar * Ks^2 * beta_SA + - 2 * siga1 * P0(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P0(t) * M * P2(t) * Ks^2 * siga2 * beta_SA - - siga1 * P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Mar * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2 - - siga1 * P1(t)^2 * beta_SI * phi * M * Ks * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * Mar * Ks * siga2^2 - - siga1 * P1(t)^2 * beta_SI * phi * Ks^2 * siga2^2 + - siga1 * P1(t)^2 * beta_SI * M * Mar * Ks * siga2 + - siga1 * P1(t)^2 * beta_SI * M * Mar * siga2^2 + - siga1 * P1(t)^2 * beta_SI * M * Ks^2 * siga2 + - siga1 * P1(t)^2 * beta_SI * M * Ks * siga2^2 + - siga1 * P1(t)^2 * beta_SI * Mar * Ks * siga2^2 + - siga1 * P1(t)^2 * beta_SI * Ks^2 * siga2^2 - - siga1 * P1(t)^2 * phi * M^2 * Mar * Ks * beta_SA - - siga1 * P1(t)^2 * phi * M^2 * Mar * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M^2 * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M^2 * siga2^2 * beta_SA - - siga1 * P1(t)^2 * phi * M * Mar * Ks^2 * beta_SA - - 2 * siga1 * P1(t)^2 * phi * M * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * M * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P1(t)^2 * phi * M * Ks * siga2^2 * beta_SA - - siga1 * P1(t)^2 * phi * Mar * Ks^2 * siga2 * beta_SA + - siga1 * P1(t)^2 * phi * Ks^2 * siga2^2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Mar * Ks * beta_SA + - siga1 * P1(t)^2 * M^2 * Mar * siga2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Ks^2 * beta_SA + - siga1 * P1(t)^2 * M^2 * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * M * Mar * Ks^2 * beta_SA + - 2 * siga1 * P1(t)^2 * M * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t)^2 * M * Ks^2 * siga2 * beta_SA + - siga1 * P1(t)^2 * Mar * Ks^2 * siga2 * beta_SA - - siga1 * P1(t) * beta_SI * P3(t) * phi * Mar * siga2 - - siga1 * P1(t) * beta_SI * P3(t) * phi * Ks * siga2 + - siga1 * P1(t) * beta_SI * P3(t) * Mar * siga2 + - siga1 * P1(t) * beta_SI * P3(t) * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2 - - 2 * siga1 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * M * P2(t) * siga2^2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * siga2^2 - - siga1 * P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2 - - 2 * siga1 * P1(t) * beta_SI * phi * P2(t) * Ks * siga2^2 + - siga1 * P1(t) * beta_SI * M * P2(t) * Mar * siga2 + - 2 * siga1 * P1(t) * beta_SI * M * P2(t) * Ks * siga2 + - siga1 * P1(t) * beta_SI * M * P2(t) * siga2^2 + - siga1 * P1(t) * beta_SI * P2(t) * Mar * Ks * siga2 + - siga1 * P1(t) * beta_SI * P2(t) * Mar * siga2^2 + - siga1 * P1(t) * beta_SI * P2(t) * Ks^2 * siga2 + - 2 * siga1 * P1(t) * beta_SI * P2(t) * Ks * siga2^2 - - siga1 * P1(t) * P3(t) * phi * M * Mar * beta_SA + - siga1 * P1(t) * P3(t) * phi * M * siga2 * beta_SA - - siga1 * P1(t) * P3(t) * phi * Mar * Ks * beta_SA + - siga1 * P1(t) * P3(t) * phi * Ks * siga2 * beta_SA + - siga1 * P1(t) * P3(t) * M * Mar * beta_SA + - 2 * siga1 * P1(t) * P3(t) * M * Ks * beta_SA + - siga1 * P1(t) * P3(t) * M * siga2 * beta_SA + - siga1 * P1(t) * P3(t) * Mar * Ks * beta_SA + - siga1 * P1(t) * P3(t) * Ks * siga2 * beta_SA - - siga1 * P1(t) * phi * M^2 * P2(t) * Mar * beta_SA + - siga1 * P1(t) * phi * M^2 * P2(t) * siga2 * beta_SA - - siga1 * P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2 - - siga1 * P1(t) * phi * M^2 * Mar * P5(t) * siga2^2 - - siga1 * P1(t) * phi * M^2 * Ks * beta * siga2 - - siga1 * P1(t) * phi * M^2 * Ks * siga2^2 - - siga1 * P1(t) * phi * M^2 * beta * siga2^2 - - 3 * siga1 * P1(t) * phi * M * P2(t) * Mar * Ks * beta_SA - - 2 * siga1 * P1(t) * phi * M * P2(t) * Mar * siga2 * beta_SA + - 3 * siga1 * P1(t) * phi * M * P2(t) * Ks * siga2 * beta_SA + - 2 * siga1 * P1(t) * phi * M * P2(t) * siga2^2 * beta_SA - - siga1 * P1(t) * phi * M * Mar * P5(t) * Ks * siga2^2 - - siga1 * P1(t) * phi * M * Ks * beta * siga2^2 - - siga1 * P1(t) * phi * P2(t) * Mar * Ks^2 * beta_SA - - 2 * siga1 * P1(t) * phi * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t) * phi * P2(t) * Ks^2 * siga2 * beta_SA + - 2 * siga1 * P1(t) * phi * P2(t) * Ks * siga2^2 * beta_SA + - siga1 * P1(t) * M^2 * P2(t) * Mar * beta_SA + - 2 * siga1 * P1(t) * M^2 * P2(t) * Ks * beta_SA + - siga1 * P1(t) * M^2 * P2(t) * siga2 * beta_SA + - siga1 * P1(t) * M^2 * Mar * P5(t) * Ks * siga2 + - siga1 * P1(t) * M^2 * Mar * P5(t) * siga2^2 + - siga1 * P1(t) * M^2 * Ks * beta * siga2 + - siga1 * P1(t) * M^2 * Ks * siga2^2 + - siga1 * P1(t) * M^2 * beta * siga2^2 + - 3 * siga1 * P1(t) * M * P2(t) * Mar * Ks * beta_SA + - 2 * siga1 * P1(t) * M * P2(t) * Mar * siga2 * beta_SA + - 2 * siga1 * P1(t) * M * P2(t) * Ks^2 * beta_SA + - 3 * siga1 * P1(t) * M * P2(t) * Ks * siga2 * beta_SA + - siga1 * P1(t) * M * Mar * P5(t) * Ks * siga2^2 + - siga1 * P1(t) * M * Ks * beta * siga2^2 + - siga1 * P1(t) * P2(t) * Mar * Ks^2 * beta_SA + - 2 * siga1 * P1(t) * P2(t) * Mar * Ks * siga2 * beta_SA + - siga1 * P1(t) * P2(t) * Ks^2 * siga2 * beta_SA - - siga1 * beta_SI * P3(t) * phi * P2(t) * siga2 + - siga1 * beta_SI * P3(t) * P2(t) * siga2 - - siga1 * beta_SI * phi * M * P2(t)^2 * siga2 - - siga1 * beta_SI * phi * P2(t)^2 * Ks * siga2 - - siga1 * beta_SI * phi * P2(t)^2 * siga2^2 + - siga1 * beta_SI * M * P2(t)^2 * siga2 + - siga1 * beta_SI * P2(t)^2 * Ks * siga2 + - siga1 * beta_SI * P2(t)^2 * siga2^2 + - siga1 * P3(t)^2 * beta_SA - siga1 * P3(t) * phi * M^2 * siga2 - - siga1 * P3(t) * phi * M * Mar * P5(t) * siga2 - - siga1 * P3(t) * phi * M * Ks * siga2 - - siga1 * P3(t) * phi * M * beta * siga2 - - siga1 * P3(t) * phi * M * siga2^2 - - siga1 * P3(t) * phi * P2(t) * Mar * beta_SA + - siga1 * P3(t) * phi * P2(t) * siga2 * beta_SA + - siga1 * P3(t) * M^2 * siga2 + - 2 * siga1 * P3(t) * M * P2(t) * beta_SA + - siga1 * P3(t) * M * Mar * P5(t) * siga2 + - siga1 * P3(t) * M * Ks * siga2 + - siga1 * P3(t) * M * beta * siga2 + - siga1 * P3(t) * M * siga2^2 + - siga1 * P3(t) * P2(t) * Mar * beta_SA + - 2 * siga1 * P3(t) * P2(t) * Ks * beta_SA + - siga1 * P3(t) * P2(t) * siga2 * beta_SA - - siga1 * phi * M^2 * P2(t) * Mar * P5(t) * siga2 - - siga1 * phi * M^2 * P2(t) * Ks * siga2 - - siga1 * phi * M^2 * P2(t) * beta * siga2 - - siga1 * phi * M^2 * P2(t) * siga2^2 - siga1 * phi * M * P4(t) * siga2 - - siga1 * phi * M * P2(t)^2 * Mar * beta_SA + - siga1 * phi * M * P2(t)^2 * siga2 * beta_SA - - siga1 * phi * M * P2(t) * Mar * P5(t) * Ks * siga2 - - siga1 * phi * M * P2(t) * Mar * P5(t) * siga2^2 - - siga1 * phi * M * P2(t) * Ks * beta * siga2 - - siga1 * phi * M * P2(t) * Ks * siga2^2 - - siga1 * phi * M * P2(t) * beta * siga2^2 - - siga1 * phi * P2(t)^2 * Mar * Ks * beta_SA - - siga1 * phi * P2(t)^2 * Mar * siga2 * beta_SA + - siga1 * phi * P2(t)^2 * Ks * siga2 * beta_SA + - siga1 * phi * P2(t)^2 * siga2^2 * beta_SA + - siga1 * M^2 * P2(t)^2 * beta_SA + - siga1 * M^2 * P2(t) * Mar * P5(t) * siga2 + - siga1 * M^2 * P2(t) * Ks * siga2 + - siga1 * M^2 * P2(t) * beta * siga2 + - siga1 * M^2 * P2(t) * siga2^2 + - siga1 * M * P4(t) * siga2 + - siga1 * M * P2(t)^2 * Mar * beta_SA + - 2 * siga1 * M * P2(t)^2 * Ks * beta_SA + - siga1 * M * P2(t)^2 * siga2 * beta_SA + - siga1 * M * P2(t) * Mar * P5(t) * Ks * siga2 + - siga1 * M * P2(t) * Mar * P5(t) * siga2^2 + - siga1 * M * P2(t) * Ks * beta * siga2 + - siga1 * M * P2(t) * Ks * siga2^2 + - siga1 * M * P2(t) * beta * siga2^2 + - siga1 * P2(t)^2 * Mar * Ks * beta_SA + - siga1 * P2(t)^2 * Mar * siga2 * beta_SA + - siga1 * P2(t)^2 * Ks^2 * beta_SA + - siga1 * P2(t)^2 * Ks * siga2 * beta_SA - - P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + - P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2^2 - - P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + - P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2^2 * beta_SA + - P0(t) * P1(t) * M^2 * Mar * Ks^2 * siga2 * beta_SA - - P0(t) * beta_SI * P3(t) * phi * M * Mar * Ks * siga2 - - P0(t) * beta_SI * P3(t) * phi * Mar * Ks^2 * siga2 - - P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2^2 + - P0(t) * beta_SI * P3(t) * M * Mar * Ks * siga2 + - P0(t) * beta_SI * P3(t) * Mar * Ks^2 * siga2 + - P0(t) * beta_SI * P3(t) * Mar * Ks * siga2^2 - - P0(t) * beta_SI * phi * alpa * Mar * Ks * siga2 - - P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks^2 * siga2 - - P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2^2 - - P0(t) * beta_SI * phi * P4(t) * Mar * Ks * siga2 - - P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2^2 + - P0(t) * beta_SI * alpa * Mar * Ks * siga2 + - P0(t) * beta_SI * M * P2(t) * Mar * Ks^2 * siga2 + - P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2^2 + - P0(t) * beta_SI * P4(t) * Mar * Ks * siga2 + - P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2^2 - - P0(t) * P3(t) * phi * M^2 * Mar * Ks * beta_SA + - P0(t) * P3(t) * phi * M^2 * Ks * siga2 * beta_SA - - P0(t) * P3(t) * phi * M * Mar * Ks^2 * beta_SA - - P0(t) * P3(t) * phi * M * Mar * Ks * siga2 * beta_SA + - P0(t) * P3(t) * phi * M * Ks^2 * siga2 * beta_SA + - P0(t) * P3(t) * phi * M * Ks * siga2^2 * beta_SA + - P0(t) * P3(t) * M^2 * Mar * Ks * beta_SA + - P0(t) * P3(t) * M * Mar * Ks^2 * beta_SA + - P0(t) * P3(t) * M * Mar * Ks * siga2 * beta_SA - - P0(t) * phi * alpa * M * Mar * Ks * beta_SA + - P0(t) * phi * alpa * M * Ks * siga2 * beta_SA - - P0(t) * phi * M^2 * P2(t) * Mar * Ks^2 * beta_SA - - P0(t) * phi * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + - P0(t) * phi * M^2 * P2(t) * Ks^2 * siga2 * beta_SA + - P0(t) * phi * M^2 * P2(t) * Ks * siga2^2 * beta_SA - - P0(t) * phi * M * P4(t) * Mar * Ks * beta_SA + - P0(t) * phi * M * P4(t) * Ks * siga2 * beta_SA - - P0(t) * phi * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA + - P0(t) * phi * M * P2(t) * Ks^2 * siga2^2 * beta_SA + - P0(t) * alpa * M * Mar * Ks * beta_SA + - P0(t) * M^2 * P2(t) * Mar * Ks^2 * beta_SA + - P0(t) * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + - P0(t) * M * P4(t) * Mar * Ks * beta_SA + - P0(t) * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA - - P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2^2 - - P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2^2 + - P1(t)^2 * beta_SI * M * Mar * Ks * siga2^2 + - P1(t)^2 * beta_SI * M * Ks^2 * siga2^2 - - P1(t)^2 * phi * M^2 * Mar * Ks * siga2 * beta_SA + - P1(t)^2 * phi * M^2 * Ks * siga2^2 * beta_SA - - P1(t)^2 * phi * M * Mar * Ks^2 * siga2 * beta_SA + - P1(t)^2 * phi * M * Ks^2 * siga2^2 * beta_SA + - P1(t)^2 * M^2 * Mar * Ks * siga2 * beta_SA + - P1(t)^2 * M^2 * Ks^2 * siga2 * beta_SA + - P1(t)^2 * M * Mar * Ks^2 * siga2 * beta_SA - - P1(t) * beta_SI * P3(t) * phi * M * Mar * siga2 - - P1(t) * beta_SI * P3(t) * phi * M * Ks * siga2 - - P1(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 - - P1(t) * beta_SI * P3(t) * phi * Mar * siga2^2 - - P1(t) * beta_SI * P3(t) * phi * Ks^2 * siga2 - - P1(t) * beta_SI * P3(t) * phi * Ks * siga2^2 + - P1(t) * beta_SI * P3(t) * M * Mar * siga2 + - P1(t) * beta_SI * P3(t) * M * Ks * siga2 + - P1(t) * beta_SI * P3(t) * Mar * Ks * siga2 + - P1(t) * beta_SI * P3(t) * Mar * siga2^2 + - P1(t) * beta_SI * P3(t) * Ks^2 * siga2 + - P1(t) * beta_SI * P3(t) * Ks * siga2^2 - - P1(t) * beta_SI * phi * alpa * Mar * siga2 - - P1(t) * beta_SI * phi * alpa * Ks * siga2 - - P1(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - - P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2^2 - - P1(t) * beta_SI * phi * M * P2(t) * Ks^2 * siga2 - - 2 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - - P1(t) * beta_SI * phi * P4(t) * Mar * siga2 - - P1(t) * beta_SI * phi * P4(t) * Ks * siga2 - - P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 - - P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2^2 + - P1(t) * beta_SI * alpa * Mar * siga2 + - P1(t) * beta_SI * alpa * Ks * siga2 + - P1(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + - P1(t) * beta_SI * M * P2(t) * Mar * siga2^2 + - P1(t) * beta_SI * M * P2(t) * Ks^2 * siga2 + - 2 * P1(t) * beta_SI * M * P2(t) * Ks * siga2^2 + - P1(t) * beta_SI * P4(t) * Mar * siga2 + - P1(t) * beta_SI * P4(t) * Ks * siga2 + - P1(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 + - P1(t) * beta_SI * P2(t) * Ks^2 * siga2^2 - - P1(t) * P3(t) * phi * M^2 * Mar * beta_SA + - P1(t) * P3(t) * phi * M^2 * siga2 * beta_SA - - 2 * P1(t) * P3(t) * phi * M * Mar * Ks * beta_SA - - P1(t) * P3(t) * phi * M * Mar * siga2 * beta_SA + - 2 * P1(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + - P1(t) * P3(t) * phi * M * siga2^2 * beta_SA - - P1(t) * P3(t) * phi * Mar * Ks^2 * beta_SA - - P1(t) * P3(t) * phi * Mar * Ks * siga2 * beta_SA + - P1(t) * P3(t) * phi * Ks^2 * siga2 * beta_SA + - P1(t) * P3(t) * phi * Ks * siga2^2 * beta_SA + - P1(t) * P3(t) * M^2 * Mar * beta_SA + - P1(t) * P3(t) * M^2 * Ks * beta_SA + - 2 * P1(t) * P3(t) * M * Mar * Ks * beta_SA + - P1(t) * P3(t) * M * Mar * siga2 * beta_SA + - P1(t) * P3(t) * M * Ks^2 * beta_SA + - 2 * P1(t) * P3(t) * M * Ks * siga2 * beta_SA + - P1(t) * P3(t) * Mar * Ks^2 * beta_SA + - P1(t) * P3(t) * Mar * Ks * siga2 * beta_SA - - P1(t) * phi * alpa * M * Mar * beta_SA + - P1(t) * phi * alpa * M * siga2 * beta_SA - - P1(t) * phi * alpa * Mar * Ks * beta_SA + - P1(t) * phi * alpa * Ks * siga2 * beta_SA - - P1(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA - - P1(t) * phi * M^2 * P2(t) * Mar * siga2 * beta_SA + - P1(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA + - P1(t) * phi * M^2 * P2(t) * siga2^2 * beta_SA - - P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - - P1(t) * phi * M^2 * Ks * beta * siga2^2 - - P1(t) * phi * M * P4(t) * Mar * beta_SA + - P1(t) * phi * M * P4(t) * siga2 * beta_SA - - P1(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - - 3 * P1(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + - P1(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + - 3 * P1(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA - - P1(t) * phi * P4(t) * Mar * Ks * beta_SA + - P1(t) * phi * P4(t) * Ks * siga2 * beta_SA - - P1(t) * phi * P2(t) * Mar * Ks^2 * siga2 * beta_SA + - P1(t) * phi * P2(t) * Ks^2 * siga2^2 * beta_SA + - P1(t) * alpa * M * Mar * beta_SA + - P1(t) * alpa * M * Ks * beta_SA + - P1(t) * alpa * Mar * Ks * beta_SA + - P1(t) * M^2 * P2(t) * Mar * Ks * beta_SA + - P1(t) * M^2 * P2(t) * Mar * siga2 * beta_SA + - P1(t) * M^2 * P2(t) * Ks^2 * beta_SA + - 2 * P1(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + - P1(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + - P1(t) * M^2 * Ks * beta * siga2^2 + - P1(t) * M * P4(t) * Mar * beta_SA + - P1(t) * M * P4(t) * Ks * beta_SA + - P1(t) * M * P2(t) * Mar * Ks^2 * beta_SA + - 3 * P1(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + - 2 * P1(t) * M * P2(t) * Ks^2 * siga2 * beta_SA + - P1(t) * P4(t) * Mar * Ks * beta_SA + - P1(t) * P2(t) * Mar * Ks^2 * siga2 * beta_SA - - beta_SI * P3(t) * phi * M * P2(t) * siga2 - - beta_SI * P3(t) * phi * P2(t) * Ks * siga2 - - beta_SI * P3(t) * phi * P2(t) * siga2^2 + - beta_SI * P3(t) * M * P2(t) * siga2 + - beta_SI * P3(t) * P2(t) * Ks * siga2 + - beta_SI * P3(t) * P2(t) * siga2^2 - - beta_SI * phi * alpa * P2(t) * siga2 - - beta_SI * phi * M * P2(t)^2 * Ks * siga2 - - beta_SI * phi * M * P2(t)^2 * siga2^2 - - beta_SI * phi * P4(t) * P2(t) * siga2 - - beta_SI * phi * P2(t)^2 * Ks * siga2^2 + - beta_SI * alpa * P2(t) * siga2 + - beta_SI * M * P2(t)^2 * Ks * siga2 + - beta_SI * M * P2(t)^2 * siga2^2 + - beta_SI * P4(t) * P2(t) * siga2 + - beta_SI * P2(t)^2 * Ks * siga2^2 + - P3(t)^2 * M * beta_SA + - P3(t)^2 * Ks * beta_SA + - P3(t)^2 * siga2 * beta_SA - P3(t) * phi * M^2 * Mar * P5(t) * siga2 - - P3(t) * phi * M^2 * Ks * siga2 - P3(t) * phi * M^2 * beta * siga2 - - P3(t) * phi * M^2 * siga2^2 - P3(t) * phi * M * P2(t) * Mar * beta_SA + - P3(t) * phi * M * P2(t) * siga2 * beta_SA - - P3(t) * phi * M * Mar * P5(t) * Ks * siga2 - - P3(t) * phi * M * Mar * P5(t) * siga2^2 - - P3(t) * phi * M * Ks * beta * siga2 - P3(t) * phi * M * Ks * siga2^2 - - P3(t) * phi * M * beta * siga2^2 - - P3(t) * phi * P2(t) * Mar * Ks * beta_SA - - P3(t) * phi * P2(t) * Mar * siga2 * beta_SA + - P3(t) * phi * P2(t) * Ks * siga2 * beta_SA + - P3(t) * phi * P2(t) * siga2^2 * beta_SA + - P3(t) * alpa * beta_SA + - P3(t) * M^2 * P2(t) * beta_SA + - P3(t) * M^2 * Mar * P5(t) * siga2 + - P3(t) * M^2 * Ks * siga2 + - P3(t) * M^2 * beta * siga2 + - P3(t) * M^2 * siga2^2 + - P3(t) * M * P2(t) * Mar * beta_SA + - 3 * P3(t) * M * P2(t) * Ks * beta_SA + - 2 * P3(t) * M * P2(t) * siga2 * beta_SA + - P3(t) * M * Mar * P5(t) * Ks * siga2 + - P3(t) * M * Mar * P5(t) * siga2^2 + - P3(t) * M * Ks * beta * siga2 + - P3(t) * M * Ks * siga2^2 + - P3(t) * M * beta * siga2^2 + - P3(t) * P4(t) * beta_SA + - P3(t) * P2(t) * Mar * Ks * beta_SA + - P3(t) * P2(t) * Mar * siga2 * beta_SA + - P3(t) * P2(t) * Ks^2 * beta_SA + - 2 * P3(t) * P2(t) * Ks * siga2 * beta_SA - - phi * alpa * M * Mar * P5(t) * siga2 - phi * alpa * M * beta * siga2 - - phi * alpa * P2(t) * Mar * beta_SA + - phi * alpa * P2(t) * siga2 * beta_SA - phi * M^2 * P4(t) * siga2 - - phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - - phi * M^2 * P2(t) * Mar * P5(t) * siga2^2 - - phi * M^2 * P2(t) * Ks * beta * siga2 - - phi * M^2 * P2(t) * Ks * siga2^2 - phi * M^2 * P2(t) * beta * siga2^2 - - phi * M * P4(t) * Mar * P5(t) * siga2 - phi * M * P4(t) * Ks * siga2 - - phi * M * P4(t) * beta * siga2 - phi * M * P4(t) * siga2^2 - - phi * M * P2(t)^2 * Mar * Ks * beta_SA - - phi * M * P2(t)^2 * Mar * siga2 * beta_SA + - phi * M * P2(t)^2 * Ks * siga2 * beta_SA + - phi * M * P2(t)^2 * siga2^2 * beta_SA - - phi * M * P2(t) * Mar * P5(t) * Ks * siga2^2 - - phi * M * P2(t) * Ks * beta * siga2^2 - - phi * P4(t) * P2(t) * Mar * beta_SA + - phi * P4(t) * P2(t) * siga2 * beta_SA - - phi * P2(t)^2 * Mar * Ks * siga2 * beta_SA + - phi * P2(t)^2 * Ks * siga2^2 * beta_SA + - alpa * M * P2(t) * beta_SA + - alpa * M * Mar * P5(t) * siga2 + - alpa * M * beta * siga2 + - alpa * P2(t) * Mar * beta_SA + - alpa * P2(t) * Ks * beta_SA + - M^2 * P4(t) * siga2 + - M^2 * P2(t)^2 * Ks * beta_SA + - M^2 * P2(t)^2 * siga2 * beta_SA + - M^2 * P2(t) * Mar * P5(t) * Ks * siga2 + - M^2 * P2(t) * Mar * P5(t) * siga2^2 + - M^2 * P2(t) * Ks * beta * siga2 + - M^2 * P2(t) * Ks * siga2^2 + - M^2 * P2(t) * beta * siga2^2 + - M * P4(t) * P2(t) * beta_SA + - M * P4(t) * Mar * P5(t) * siga2 + - M * P4(t) * Ks * siga2 + - M * P4(t) * beta * siga2 + - M * P4(t) * siga2^2 + - M * P2(t)^2 * Mar * Ks * beta_SA + - M * P2(t)^2 * Mar * siga2 * beta_SA + - M * P2(t)^2 * Ks^2 * beta_SA + - 2 * M * P2(t)^2 * Ks * siga2 * beta_SA + - M * P2(t) * Mar * P5(t) * Ks * siga2^2 + - M * P2(t) * Ks * beta * siga2^2 + - P4(t) * P2(t) * Mar * beta_SA + - P4(t) * P2(t) * Ks * beta_SA + - P2(t)^2 * Mar * Ks * siga2 * beta_SA + - P2(t)^2 * Ks^2 * siga2 * beta_SA - ) // (phi * M * siga2 - M * siga2), - P1'(t) = P2(t), - P2'(t) = P3(t), - y(t) = P0(t) - ) - ident_funcs = [ - Mar, - Ks, - alpa, - (siga1 + phi * Mar - Mar) // phi, - (siga1 * phi - siga1 - phi * Mar + Mar) // (siga1 * phi * beta_SA), +# For each ODE system we check the equality (in terms of fields of rational +# functions) of the true set of identifiable functions and the obtained +# simplified set +test_cases = [] + +### + +ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) +ident_funcs = [a] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel(x1'(t) = a, x2'(t) = -a, y(t) = x1 + x2) +ident_funcs = [] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Parameter a is not identifiable, and neither are any combinations thereof. +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x2(t) - a, + x2'(t) = x1(t) + a, + y(t) = x1(t) + x2(t) +) +ident_funcs = + Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() + +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Example 2 from +# "On Global Identifiability for Arbitrary Model Parametrizations", +# DOI: 10.1016/0005-1098(94)90029-9 +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = Θ * x2(t)^2, + x2'(t) = u(t), + y(t) = x1(t) +) +ident_funcs = [Θ] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Example 4 from +# "On Global Identifiability for Arbitrary Model Parametrizations", +# DOI: 10.1016/0005-1098(94)90029-9 +ode = StructuralIdentifiability.@ODEmodel( + x'(t) = (-V_m * x(t)) / (k_m + x(t)) + k01 * x(t), + y(t) = c * x(t) +) +ident_funcs = [k01, k_m // V_m, V_m * c] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Parameters b and c enter the io equations only as the product b * c. +ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) +ident_funcs = [b * c, a] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# 2-compartiment model +ode = StructuralIdentifiability.@ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) +) +ident_funcs = [(a01 * a12), (a01 + a12 + a21)] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# TODO: uncomment when identifiability can handle models with no states +# ode = StructuralIdentifiability.@ODEmodel( +# y(t) = a*u(t) +# ) +# ident_funcs = [(a01 * a12), (a01 + a12 + a21)] +# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Example 2 from +# "On Structural Identifiability", +# DOI: https://doi.org/10.1016/0025-5564(70)90132-X +# +# More or less the same 2-compartmental model as the one given above +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -(k1 + k2) * x1(t) + k3 * x2(t) + u(t), + x2'(t) = k2 * x1(t) - (k3 + k4) * x2(t), + y(t) = x1(t) +) +ident_funcs = [(k1 + k2), (k1 + k2 + k3 + k4), ((k1 + k2) * (k3 + k4) - k2 * k3)] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Diagonal with simple spectrum and observable states +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = λ1 * x1(t) + β1 * u1(t), + x2'(t) = λ2 * x2(t) + β2 * u2(t), + x3'(t) = λ3 * x3(t) + β3 * u3(t), + y(t) = x1(t) + x2(t) + x3(t) +) +ident_funcs = [λ1, λ2, λ3, β1, β2, β3] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# 3 compartments: +# x1 <--> x2 <--> x3 +# If we observe x1 and control x1, then all parameters are identifiable +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -a1 * x1(t) + b1 * x2(t) + u(t), + x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), + x3'(t) = -b2 * x3(t) + a2 * x2(t), + y(t) = x1(t) +) +ident_funcs = [a1, a2, b1, b2] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Example 3 from +# "On Structural Identifiability", +# DOI: https://doi.org/10.1016/0025-5564(70)90132-X +# +# 3 compartments: +# x1 <--> x2 <--> x3 +# If we observe x1 and control x3, then only some functions of parameters +# are identifiable +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -a1 * x1(t) + b1 * x2(t), + x2'(t) = -(a2 + b1) * x2(t) + a1 * x1(t) + b2 * x3(t), + x3'(t) = -b2 * x3(t) + a2 * x2(t) + u(t), + y(t) = x1(t) +) +ident_funcs = [b1 * b2, a1 + a2 + b1 + b2, a1 * a2 + a1 * b2] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Example 3 from +# "On the identifiability and distinguishability of nonlinear parametric +# models", +# DOI: https://doi.org/10.1016/0378-4754(95)00123-9 +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = p1 * x1^2 + p2 * x1 * x2, + x2'(t) = p3 * x1^2 + p4 * x1 * x2, + y(t) = x1 +) +ident_funcs = [p1 + p4, p2 * p3 - p4 * p1] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Goowdin oscillator +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) +) +ident_funcs = [sigma, delta + beta, c, b, delta * beta] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# SIRS forced +ode = StructuralIdentifiability.@ODEmodel( + s'(t) = mu - mu * s(t) - b0 * (1 + b1 * x1(t)) * i(t) * s(t) + g * r(t), + i'(t) = b0 * (1 + b1 * x1(t)) * i(t) * s(t) - (nu + mu) * i(t), + r'(t) = nu * i(t) - (mu + g) * r(t), + x1'(t) = -M * x2(t), + x2'(t) = M * x1(t), + y1(t) = i(t), + y2(t) = r(t) +) +ident_funcs = [g, mu, b0, nu, M^2] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# SEIR_1_io +ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -beta * S(t) * I(t), + E'(t) = beta * S(t) * I(t) - v * E(t), + I'(t) = v * E(t) - psi * I(t) - (1 - psi) * gamma * I(t), + R'(t) = gamma * Q(t) + (1 - psi) * gamma * I(t), + Q'(t) = -gamma * Q(t) + psi * I(t), + y1(t) = Q(t) +) +ident_funcs = [gamma, beta // psi, gamma * psi - v - psi, gamma * psi - v * psi] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Bilirubin2_io. +# Regression test: failed before, as the total degrees were being estimated +# incorrectly in the interpolation +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = + -(k21 + k31 + k41 + k01) * x1(t) + + k12 * x2(t) + + k13 * x3(t) + + k14 * x4(t) + + u(t), + x2'(t) = k21 * x1(t) - k12 * x2(t), + x3'(t) = k31 * x1(t) - k13 * x3(t), + x4'(t) = k41 * x1(t) - k14 * x4(t), + y1(t) = x1(t) +) +ident_funcs = [ + k01 // one(k01), + k12 * k13 * k14 // one(k01), + k31 * k21 * k41 // one(k01), + k12 + k13 + k14 // one(k01), + k31 + k21 + k41 // one(k01), + k12 * k13 + k12 * k14 + k13 * k14 // one(k01), + k31 * k21 + k31 * k41 + k21 * k41 // one(k01), + k31 * k12 - 2 * k31 * k13 + k31 * k14 - 2 * k21 * k12 + + k21 * k13 + + k21 * k14 + + k12 * k41 + + k13 * k41 - 2 * k14 * k41 // one(k01), +] +# Too slow with hybrid strategy :( +# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# Biohydrogenation_io +ode = StructuralIdentifiability.@ODEmodel( + x5'(t) = ( - siga1 * beta_SA + beta_SI * phi * siga2 + phi * Mar * beta_SA - - phi * siga2 * beta_SA - Mar * beta_SA - ) // (phi * M * siga2), + k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - + k6 * x5(t) * k7 - x5(t) * k7 * x4(t) + ) // ( + k8 * k6 + + k8 * x4(t) + + k6 * x6(t) + + k6 * x5(t) + + x6(t) * x4(t) + + x5(t) * x4(t) + ), + x7'(t) = (k9 * k10 * x6(t) - k9 * x6(t)^2) // k10, + x4'(t) = (-k5 * x4(t)) // (k6 + x4(t)), + x6'(t) = ( - siga1 * phi * M * siga2 - siga1 * M * siga2 - phi * M * Mar * siga2 + - M * Mar * siga2 - ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), + -k8 * k9 * k10 * x6(t) + k8 * k9 * x6(t)^2 - k9 * k10 * x6(t)^2 - + k9 * k10 * x6(t) * x5(t) + + k9 * x6(t)^3 + + k9 * x6(t)^2 * x5(t) + + k10 * x5(t) * k7 + ) // (k8 * k10 + k10 * x6(t) + k10 * x5(t)), + y1(t) = x4(t), + y2(t) = x5(t) +) +ident_funcs = [k7, k6, k5, k10^2, k9 * k10, k8 + 1 // 2 * k10] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# SLIQR +ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -b * In(t) * S(t) * Ninv - S(t) * Ninv * u(t), + In'(t) = -In(t) * g + s * Q(t) + a * L(t), + L'(t) = b * In(t) * S(t) * Ninv - a * L(t), + Q'(t) = -e * In(t) * g + In(t) * g - s * Q(t), + y(t) = In(t) * Ninv +) +ident_funcs = [ + g + a, + s + g + a, + s, + Ninv, + b, + (e * s - s + a) // (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), + e * s * g + s * a + g * a, + (e * s^2 + e * s * g - s^2 - s * g + g * a + a^2) // + (e * s^2 * g - s^2 * g - s^2 * a + s * g * a + s * a^2), + e * s * g * a, + 2 * e * Ninv * s * g + 2 * Ninv * s * a + 2 * Ninv * g * a, +] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# St. +# Regression test: +# Failed before, as the degrees of Groebner basis were too large +ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -e * S(t) - S(t) * d * W(t) + S(t) * r - S(t) * a * W(t) + R(t) * g, + R'(t) = e * S(t) + rR * R(t) + S(t) * a * W(t) - dr * R(t) * W(t) - R(t) * g, + W'(t) = T * Dd - W(t) * Dd, + y1(t) = S(t) + R(t), + y2(t) = T +) +ident_funcs = [ + T, + Dd, + e - rR + dr * T + d * T + g - r + a * T, + (2 * rR * d - 2 * dr * r) // (dr - d), + (dr^2 + d^2 + 2 * d * a + a^2) // (dr * d + dr * a), + (e * dr - e * d + rR * a + dr * g - d * g - r * a) // (dr - d), + (e * dr^2 - e * dr * d + rR * dr * a + dr * d * g - dr * r * a - d^2 * g) // + (dr^2 + dr * a - d^2 - d * a), +] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +# QY system. +# (this is a big one) +ode = StructuralIdentifiability.@ODEmodel( + P3'(t) = P4(t), + P0'(t) = P1(t), + P5'(t) = ( - siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 - - siga1 * phi * siga2 * beta_SA - siga1 * M * beta_SA - - beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - - phi * M * Mar * beta_SA + M * Mar * beta_SA - ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), + -P0(t) * beta_SI * phi * Mar * Ks * siga2 + + P0(t) * beta_SI * Mar * Ks * siga2 - + P0(t) * phi * M * Mar * Ks * beta_SA + + P0(t) * phi * M * Ks * siga2 * beta_SA + + P0(t) * M * Mar * Ks * beta_SA - P1(t) * beta_SI * phi * Mar * siga2 - + P1(t) * beta_SI * phi * Ks * siga2 + + P1(t) * beta_SI * Mar * siga2 + + P1(t) * beta_SI * Ks * siga2 - P1(t) * phi * M * Mar * beta_SA + + P1(t) * phi * M * siga2 * beta_SA - P1(t) * phi * Mar * Ks * beta_SA + + P1(t) * phi * Ks * siga2 * beta_SA + + P1(t) * M * Mar * beta_SA + + P1(t) * M * Ks * beta_SA + + P1(t) * Mar * Ks * beta_SA - beta_SI * phi * P2(t) * siga2 + + beta_SI * P2(t) * siga2 + + P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - + phi * M * beta * siga2 - phi * P2(t) * Mar * beta_SA + + phi * P2(t) * siga2 * beta_SA + + M * P2(t) * beta_SA + + M * Mar * P5(t) * siga2 + + M * beta * siga2 + + P2(t) * Mar * beta_SA + + P2(t) * Ks * beta_SA + ) // (phi * M * siga2 - M * siga2), + P4'(t) = ( - siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + siga1 * phi * Mar * beta_SA - - siga1 * phi * siga2 * beta_SA - siga1 * Mar * beta_SA + - siga1 * siga2 * beta_SA + - beta_SI * phi * M * siga2 - beta_SI * phi * Mar * siga2 + - beta_SI * phi * siga2^2 + - beta_SI * Mar * siga2 + - phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - - Mar * siga2 * beta_SA - ) // (phi * M * siga2), - ] - # Really large and takes a lot of time, so commented - # push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - ### - # Cases with states - - ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t), y(t) = x(t)) - T = typeof(x) - ident_funcs = [x] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) - - ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) - ident_funcs = [a, x] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) - ) - ident_funcs = [x1, c, d, a, b * x2] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) - ) - ident_funcs = [x1, c, d, a, b * x2] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - # Diagonal with simple spectrum and observable states - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = λ1 * x1(t) + β1 * u1(t), - x2'(t) = λ2 * x2(t) + β2 * u2(t), - x3'(t) = λ3 * x3(t) + β3 * u3(t), - y(t) = x1(t) + x2(t) + x3(t) - ) - ident_funcs = [λ1, λ2, λ3, β1, β2, β3, x1, x2, x3] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + Θ * x2(t), - x2'(t) = 0, - y(t) = x1(t) - ) - ident_funcs = [x1, x2 * Θ] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * x2(t), - x2'(t) = x3(t), - x3'(t) = C, - y(t) = x1(t) - ) - ident_funcs = [x1, α * x2, α * x3, α * C] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * (x1 - x2), - x2'(t) = α * (x1 + x2), - y(t) = (x1^2 + x2^2) // 2, - ) - ident_funcs = [α, x1^2 + x2^2] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) - ident_funcs = [b * c, a, x // b] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - # llw1987 model - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -p1 * x1(t) + p2 * u(t), - x2'(t) = -p3 * x2(t) + p4 * u(t), - x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), - y1(t) = x3(t) - ) - ident_funcs = [ - x3, - x2 * x1 // one(x1), - p3 * p1 // one(x1), - p2 * p4 // one(x1), - (p3 + p1) // one(x1), - (p2 * x2 + p4 * x1) // one(x1), - (p3 - p1) // (p2 * x2 - p4 * x1), - ] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - # Regression test: Previously failed for with_states=true because of the bug in - # `linear_relations_between_normal_forms` - # Fujita - ode = StructuralIdentifiability.@ODEmodel( - EGFR'(t) = - EGFR_turnover * pro_EGFR(t) + EGF_EGFR(t) * reaction_1_k2 - - EGFR(t) * EGFR_turnover - EGF_EGFR(t) * reaction_1_k1, - pEGFR'(t) = - EGF_EGFR(t) * reaction_9_k1 - pEGFR(t) * reaction_4_k1 + - pEGFR_Akt(t) * reaction_2_k2 + - pEGFR_Akt(t) * reaction_3_k1 - Akt(t) * pEGFR(t) * reaction_2_k1, - pEGFR_Akt'(t) = - Akt(t) * pEGFR(t) * reaction_2_k1 - pEGFR_Akt(t) * reaction_3_k1 - - pEGFR_Akt(t) * reaction_2_k2, - Akt'(t) = - pAkt(t) * reaction_7_k1 + pEGFR_Akt(t) * reaction_2_k2 - - Akt(t) * pEGFR(t) * reaction_2_k1, - pAkt'(t) = - pAkt_S6(t) * reaction_5_k2 - pAkt(t) * reaction_7_k1 + - pAkt_S6(t) * reaction_6_k1 + - pEGFR_Akt(t) * reaction_3_k1 - S6(t) * pAkt(t) * reaction_5_k1, - S6'(t) = - pAkt_S6(t) * reaction_5_k2 + pS6(t) * reaction_8_k1 - - S6(t) * pAkt(t) * reaction_5_k1, - pAkt_S6'(t) = - S6(t) * pAkt(t) * reaction_5_k1 - pAkt_S6(t) * reaction_6_k1 - - pAkt_S6(t) * reaction_5_k2, - pS6'(t) = pAkt_S6(t) * reaction_6_k1 - pS6(t) * reaction_8_k1, - EGF_EGFR'(t) = - EGF_EGFR(t) * reaction_1_k1 - EGF_EGFR(t) * reaction_9_k1 - - EGF_EGFR(t) * reaction_1_k2, - y1(t) = a1 * (pEGFR(t) + pEGFR_Akt(t)), - y2(t) = a2 * (pAkt(t) + pAkt_S6(t)), - y3(t) = a3 * pS6(t) - ) - ident_funcs = [ - reaction_8_k1, - reaction_2_k2, - reaction_5_k2, - reaction_3_k1, - reaction_6_k1, - reaction_7_k1, - reaction_4_k1, - reaction_2_k1 * pAkt_S6, - reaction_2_k1 * S6, - reaction_5_k1 * pAkt_S6, - pEGFR_Akt * reaction_2_k1, - Akt * reaction_2_k1, - a1 * pAkt_S6, - pEGFR * reaction_2_k1, - pAkt * reaction_2_k1, - a3 * pAkt_S6, - pS6 * reaction_2_k1, - a2 * pAkt_S6, - reaction_9_k1 * reaction_2_k1 * EGF_EGFR, - reaction_1_k1 - reaction_9_k1 - reaction_1_k2, - ] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - # Bruno2016 model - ode = StructuralIdentifiability.@ODEmodel( - beta'(t) = -kbeta * beta(t), - cry'(t) = -cry(t) * kcrybeta - cry(t) * kcryOH, - zea'(t) = -zea(t) * kzea, - beta10'(t) = cry(t) * kcryOH - beta10(t) * kbeta10 + kbeta * beta(t), - OHbeta10'(t) = cry(t) * kcrybeta + zea(t) * kzea - OHbeta10(t) * kOHbeta10, - betaio'(t) = cry(t) * kcrybeta + beta10(t) * kbeta10 + kbeta * beta(t), - OHbetaio'(t) = cry(t) * kcryOH + zea(t) * kzea + OHbeta10(t) * kOHbeta10, - y1(t) = beta(t), - y2(t) = beta10(t) - ) - ident_funcs = [beta10, beta, kbeta, kbeta10, cry * kcryOH, kcrybeta + kcryOH] - push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) - - # STAT-5 model from - # MODELING THE NONLINEAR DYNAMICS OF CELLULAR SIGNAL TRANSDUCTION - # DOI: https://doi.org/10.1142/S0218127404010461 - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -k1 * x1 * EpoR_A, - x2'(t) = k1 * x1 * EpoR_A - k2 * x2^2, - x3'(t) = -k3 * x3 + 0.5 * k2 * x2^2, - x4'(t) = k3 * x3, - y1(t) = k5 * (x2 + 2x3), - y2(t) = k6 * (x1 + x2 + 2x3), - y3(t) = k7 * EpoR_A - ) - ident_funcs = [k3, k1 // k7, k5 // k2, k6 // k2, k7 * EpoR_A] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) - - ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) - ident_funcs = [x1 + x2] - push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) - - # TODO: verify that Maple returns the same - @testset "Identifiable functions of parameters" begin - p = 0.99 - for case in test_cases - for simplify in [:weak, :standard] # :strong] - ode = case.ode - true_ident_funcs = case.ident_funcs - with_states = false - if haskey(case, :with_states) - with_states = case.with_states - end - result_funcs = StructuralIdentifiability.find_identifiable_functions( - ode, - simplify = simplify, - with_states = with_states, - ) - - if isempty(true_ident_funcs) - @test isempty(result_funcs) - continue - end - - @test parent(numerator(result_funcs[1])) == parent(ode) - - R = parent(numerator(result_funcs[1])) - - @info "Test, result_funcs = \n$result_funcs" case simplify R with_states - - true_ident_funcs = map(f -> f // one(f), true_ident_funcs) - true_ident_funcs = map( - f -> StructuralIdentifiability.parent_ring_change(f, R), - true_ident_funcs, - ) - - # Check inclusion in - @test StructuralIdentifiability.fields_equal( - StructuralIdentifiability.RationalFunctionField(result_funcs), - StructuralIdentifiability.RationalFunctionField(true_ident_funcs), - p, - ) + -siga1 * P0(t)^2 * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + + siga1 * P0(t)^2 * beta_SI * M * Mar * Ks^2 * siga2^2 - + siga1 * P0(t)^2 * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + + siga1 * P0(t)^2 * phi * M^2 * Ks^2 * siga2^2 * beta_SA + + siga1 * P0(t)^2 * M^2 * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2 - + 2 * siga1 * P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks * siga2^2 - + siga1 * P0(t) * P1(t) * beta_SI * phi * M * Ks^2 * siga2^2 - + siga1 * P0(t) * P1(t) * beta_SI * phi * Mar * Ks^2 * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2 + + 2 * siga1 * P0(t) * P1(t) * beta_SI * M * Mar * Ks * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * M * Ks^2 * siga2^2 + + siga1 * P0(t) * P1(t) * beta_SI * Mar * Ks^2 * siga2^2 - + siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * beta_SA - + 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * phi * M^2 * Ks * siga2^2 * beta_SA - + 2 * siga1 * P0(t) * P1(t) * phi * M * Mar * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * phi * M * Ks^2 * siga2^2 * beta_SA + + siga1 * P0(t) * P1(t) * M^2 * Mar * Ks^2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * M^2 * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * P1(t) * M^2 * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * P1(t) * M * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 + + siga1 * P0(t) * beta_SI * P3(t) * Mar * Ks * siga2 - + siga1 * P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - + siga1 * P0(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - + siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2 - + siga1 * P0(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 + + siga1 * P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + + siga1 * P0(t) * beta_SI * M * P2(t) * Ks * siga2^2 + + siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2 + + siga1 * P0(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 - + siga1 * P0(t) * P3(t) * phi * M * Mar * Ks * beta_SA + + siga1 * P0(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + + siga1 * P0(t) * P3(t) * M * Mar * Ks * beta_SA + + siga1 * P0(t) * P3(t) * M * Ks * siga2 * beta_SA - + siga1 * P0(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA + + siga1 * P0(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA - + siga1 * P0(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - + siga1 * P0(t) * phi * M^2 * Ks * beta * siga2^2 - + siga1 * P0(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - + 2 * siga1 * P0(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P0(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA + + siga1 * P0(t) * M^2 * P2(t) * Mar * Ks * beta_SA + + siga1 * P0(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + + siga1 * P0(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + + siga1 * P0(t) * M^2 * Ks * beta * siga2^2 + + siga1 * P0(t) * M * P2(t) * Mar * Ks^2 * beta_SA + + 2 * siga1 * P0(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P0(t) * M * P2(t) * Ks^2 * siga2 * beta_SA - + siga1 * P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Mar * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2 - + siga1 * P1(t)^2 * beta_SI * phi * M * Ks * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * Mar * Ks * siga2^2 - + siga1 * P1(t)^2 * beta_SI * phi * Ks^2 * siga2^2 + + siga1 * P1(t)^2 * beta_SI * M * Mar * Ks * siga2 + + siga1 * P1(t)^2 * beta_SI * M * Mar * siga2^2 + + siga1 * P1(t)^2 * beta_SI * M * Ks^2 * siga2 + + siga1 * P1(t)^2 * beta_SI * M * Ks * siga2^2 + + siga1 * P1(t)^2 * beta_SI * Mar * Ks * siga2^2 + + siga1 * P1(t)^2 * beta_SI * Ks^2 * siga2^2 - + siga1 * P1(t)^2 * phi * M^2 * Mar * Ks * beta_SA - + siga1 * P1(t)^2 * phi * M^2 * Mar * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M^2 * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M^2 * siga2^2 * beta_SA - + siga1 * P1(t)^2 * phi * M * Mar * Ks^2 * beta_SA - + 2 * siga1 * P1(t)^2 * phi * M * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * M * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P1(t)^2 * phi * M * Ks * siga2^2 * beta_SA - + siga1 * P1(t)^2 * phi * Mar * Ks^2 * siga2 * beta_SA + + siga1 * P1(t)^2 * phi * Ks^2 * siga2^2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Mar * Ks * beta_SA + + siga1 * P1(t)^2 * M^2 * Mar * siga2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Ks^2 * beta_SA + + siga1 * P1(t)^2 * M^2 * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * M * Mar * Ks^2 * beta_SA + + 2 * siga1 * P1(t)^2 * M * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t)^2 * M * Ks^2 * siga2 * beta_SA + + siga1 * P1(t)^2 * Mar * Ks^2 * siga2 * beta_SA - + siga1 * P1(t) * beta_SI * P3(t) * phi * Mar * siga2 - + siga1 * P1(t) * beta_SI * P3(t) * phi * Ks * siga2 + + siga1 * P1(t) * beta_SI * P3(t) * Mar * siga2 + + siga1 * P1(t) * beta_SI * P3(t) * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2 - + 2 * siga1 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * M * P2(t) * siga2^2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Mar * siga2^2 - + siga1 * P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2 - + 2 * siga1 * P1(t) * beta_SI * phi * P2(t) * Ks * siga2^2 + + siga1 * P1(t) * beta_SI * M * P2(t) * Mar * siga2 + + 2 * siga1 * P1(t) * beta_SI * M * P2(t) * Ks * siga2 + + siga1 * P1(t) * beta_SI * M * P2(t) * siga2^2 + + siga1 * P1(t) * beta_SI * P2(t) * Mar * Ks * siga2 + + siga1 * P1(t) * beta_SI * P2(t) * Mar * siga2^2 + + siga1 * P1(t) * beta_SI * P2(t) * Ks^2 * siga2 + + 2 * siga1 * P1(t) * beta_SI * P2(t) * Ks * siga2^2 - + siga1 * P1(t) * P3(t) * phi * M * Mar * beta_SA + + siga1 * P1(t) * P3(t) * phi * M * siga2 * beta_SA - + siga1 * P1(t) * P3(t) * phi * Mar * Ks * beta_SA + + siga1 * P1(t) * P3(t) * phi * Ks * siga2 * beta_SA + + siga1 * P1(t) * P3(t) * M * Mar * beta_SA + + 2 * siga1 * P1(t) * P3(t) * M * Ks * beta_SA + + siga1 * P1(t) * P3(t) * M * siga2 * beta_SA + + siga1 * P1(t) * P3(t) * Mar * Ks * beta_SA + + siga1 * P1(t) * P3(t) * Ks * siga2 * beta_SA - + siga1 * P1(t) * phi * M^2 * P2(t) * Mar * beta_SA + + siga1 * P1(t) * phi * M^2 * P2(t) * siga2 * beta_SA - + siga1 * P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2 - + siga1 * P1(t) * phi * M^2 * Mar * P5(t) * siga2^2 - + siga1 * P1(t) * phi * M^2 * Ks * beta * siga2 - + siga1 * P1(t) * phi * M^2 * Ks * siga2^2 - + siga1 * P1(t) * phi * M^2 * beta * siga2^2 - + 3 * siga1 * P1(t) * phi * M * P2(t) * Mar * Ks * beta_SA - + 2 * siga1 * P1(t) * phi * M * P2(t) * Mar * siga2 * beta_SA + + 3 * siga1 * P1(t) * phi * M * P2(t) * Ks * siga2 * beta_SA + + 2 * siga1 * P1(t) * phi * M * P2(t) * siga2^2 * beta_SA - + siga1 * P1(t) * phi * M * Mar * P5(t) * Ks * siga2^2 - + siga1 * P1(t) * phi * M * Ks * beta * siga2^2 - + siga1 * P1(t) * phi * P2(t) * Mar * Ks^2 * beta_SA - + 2 * siga1 * P1(t) * phi * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t) * phi * P2(t) * Ks^2 * siga2 * beta_SA + + 2 * siga1 * P1(t) * phi * P2(t) * Ks * siga2^2 * beta_SA + + siga1 * P1(t) * M^2 * P2(t) * Mar * beta_SA + + 2 * siga1 * P1(t) * M^2 * P2(t) * Ks * beta_SA + + siga1 * P1(t) * M^2 * P2(t) * siga2 * beta_SA + + siga1 * P1(t) * M^2 * Mar * P5(t) * Ks * siga2 + + siga1 * P1(t) * M^2 * Mar * P5(t) * siga2^2 + + siga1 * P1(t) * M^2 * Ks * beta * siga2 + + siga1 * P1(t) * M^2 * Ks * siga2^2 + + siga1 * P1(t) * M^2 * beta * siga2^2 + + 3 * siga1 * P1(t) * M * P2(t) * Mar * Ks * beta_SA + + 2 * siga1 * P1(t) * M * P2(t) * Mar * siga2 * beta_SA + + 2 * siga1 * P1(t) * M * P2(t) * Ks^2 * beta_SA + + 3 * siga1 * P1(t) * M * P2(t) * Ks * siga2 * beta_SA + + siga1 * P1(t) * M * Mar * P5(t) * Ks * siga2^2 + + siga1 * P1(t) * M * Ks * beta * siga2^2 + + siga1 * P1(t) * P2(t) * Mar * Ks^2 * beta_SA + + 2 * siga1 * P1(t) * P2(t) * Mar * Ks * siga2 * beta_SA + + siga1 * P1(t) * P2(t) * Ks^2 * siga2 * beta_SA - + siga1 * beta_SI * P3(t) * phi * P2(t) * siga2 + + siga1 * beta_SI * P3(t) * P2(t) * siga2 - + siga1 * beta_SI * phi * M * P2(t)^2 * siga2 - + siga1 * beta_SI * phi * P2(t)^2 * Ks * siga2 - + siga1 * beta_SI * phi * P2(t)^2 * siga2^2 + + siga1 * beta_SI * M * P2(t)^2 * siga2 + + siga1 * beta_SI * P2(t)^2 * Ks * siga2 + + siga1 * beta_SI * P2(t)^2 * siga2^2 + + siga1 * P3(t)^2 * beta_SA - siga1 * P3(t) * phi * M^2 * siga2 - + siga1 * P3(t) * phi * M * Mar * P5(t) * siga2 - + siga1 * P3(t) * phi * M * Ks * siga2 - + siga1 * P3(t) * phi * M * beta * siga2 - + siga1 * P3(t) * phi * M * siga2^2 - + siga1 * P3(t) * phi * P2(t) * Mar * beta_SA + + siga1 * P3(t) * phi * P2(t) * siga2 * beta_SA + + siga1 * P3(t) * M^2 * siga2 + + 2 * siga1 * P3(t) * M * P2(t) * beta_SA + + siga1 * P3(t) * M * Mar * P5(t) * siga2 + + siga1 * P3(t) * M * Ks * siga2 + + siga1 * P3(t) * M * beta * siga2 + + siga1 * P3(t) * M * siga2^2 + + siga1 * P3(t) * P2(t) * Mar * beta_SA + + 2 * siga1 * P3(t) * P2(t) * Ks * beta_SA + + siga1 * P3(t) * P2(t) * siga2 * beta_SA - + siga1 * phi * M^2 * P2(t) * Mar * P5(t) * siga2 - + siga1 * phi * M^2 * P2(t) * Ks * siga2 - + siga1 * phi * M^2 * P2(t) * beta * siga2 - + siga1 * phi * M^2 * P2(t) * siga2^2 - siga1 * phi * M * P4(t) * siga2 - + siga1 * phi * M * P2(t)^2 * Mar * beta_SA + + siga1 * phi * M * P2(t)^2 * siga2 * beta_SA - + siga1 * phi * M * P2(t) * Mar * P5(t) * Ks * siga2 - + siga1 * phi * M * P2(t) * Mar * P5(t) * siga2^2 - + siga1 * phi * M * P2(t) * Ks * beta * siga2 - + siga1 * phi * M * P2(t) * Ks * siga2^2 - + siga1 * phi * M * P2(t) * beta * siga2^2 - + siga1 * phi * P2(t)^2 * Mar * Ks * beta_SA - + siga1 * phi * P2(t)^2 * Mar * siga2 * beta_SA + + siga1 * phi * P2(t)^2 * Ks * siga2 * beta_SA + + siga1 * phi * P2(t)^2 * siga2^2 * beta_SA + + siga1 * M^2 * P2(t)^2 * beta_SA + + siga1 * M^2 * P2(t) * Mar * P5(t) * siga2 + + siga1 * M^2 * P2(t) * Ks * siga2 + + siga1 * M^2 * P2(t) * beta * siga2 + + siga1 * M^2 * P2(t) * siga2^2 + + siga1 * M * P4(t) * siga2 + + siga1 * M * P2(t)^2 * Mar * beta_SA + + 2 * siga1 * M * P2(t)^2 * Ks * beta_SA + + siga1 * M * P2(t)^2 * siga2 * beta_SA + + siga1 * M * P2(t) * Mar * P5(t) * Ks * siga2 + + siga1 * M * P2(t) * Mar * P5(t) * siga2^2 + + siga1 * M * P2(t) * Ks * beta * siga2 + + siga1 * M * P2(t) * Ks * siga2^2 + + siga1 * M * P2(t) * beta * siga2^2 + + siga1 * P2(t)^2 * Mar * Ks * beta_SA + + siga1 * P2(t)^2 * Mar * siga2 * beta_SA + + siga1 * P2(t)^2 * Ks^2 * beta_SA + + siga1 * P2(t)^2 * Ks * siga2 * beta_SA - + P0(t) * P1(t) * beta_SI * phi * M * Mar * Ks^2 * siga2^2 + + P0(t) * P1(t) * beta_SI * M * Mar * Ks^2 * siga2^2 - + P0(t) * P1(t) * phi * M^2 * Mar * Ks^2 * siga2 * beta_SA + + P0(t) * P1(t) * phi * M^2 * Ks^2 * siga2^2 * beta_SA + + P0(t) * P1(t) * M^2 * Mar * Ks^2 * siga2 * beta_SA - + P0(t) * beta_SI * P3(t) * phi * M * Mar * Ks * siga2 - + P0(t) * beta_SI * P3(t) * phi * Mar * Ks^2 * siga2 - + P0(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2^2 + + P0(t) * beta_SI * P3(t) * M * Mar * Ks * siga2 + + P0(t) * beta_SI * P3(t) * Mar * Ks^2 * siga2 + + P0(t) * beta_SI * P3(t) * Mar * Ks * siga2^2 - + P0(t) * beta_SI * phi * alpa * Mar * Ks * siga2 - + P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks^2 * siga2 - + P0(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2^2 - + P0(t) * beta_SI * phi * P4(t) * Mar * Ks * siga2 - + P0(t) * beta_SI * phi * P2(t) * Mar * Ks^2 * siga2^2 + + P0(t) * beta_SI * alpa * Mar * Ks * siga2 + + P0(t) * beta_SI * M * P2(t) * Mar * Ks^2 * siga2 + + P0(t) * beta_SI * M * P2(t) * Mar * Ks * siga2^2 + + P0(t) * beta_SI * P4(t) * Mar * Ks * siga2 + + P0(t) * beta_SI * P2(t) * Mar * Ks^2 * siga2^2 - + P0(t) * P3(t) * phi * M^2 * Mar * Ks * beta_SA + + P0(t) * P3(t) * phi * M^2 * Ks * siga2 * beta_SA - + P0(t) * P3(t) * phi * M * Mar * Ks^2 * beta_SA - + P0(t) * P3(t) * phi * M * Mar * Ks * siga2 * beta_SA + + P0(t) * P3(t) * phi * M * Ks^2 * siga2 * beta_SA + + P0(t) * P3(t) * phi * M * Ks * siga2^2 * beta_SA + + P0(t) * P3(t) * M^2 * Mar * Ks * beta_SA + + P0(t) * P3(t) * M * Mar * Ks^2 * beta_SA + + P0(t) * P3(t) * M * Mar * Ks * siga2 * beta_SA - + P0(t) * phi * alpa * M * Mar * Ks * beta_SA + + P0(t) * phi * alpa * M * Ks * siga2 * beta_SA - + P0(t) * phi * M^2 * P2(t) * Mar * Ks^2 * beta_SA - + P0(t) * phi * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + + P0(t) * phi * M^2 * P2(t) * Ks^2 * siga2 * beta_SA + + P0(t) * phi * M^2 * P2(t) * Ks * siga2^2 * beta_SA - + P0(t) * phi * M * P4(t) * Mar * Ks * beta_SA + + P0(t) * phi * M * P4(t) * Ks * siga2 * beta_SA - + P0(t) * phi * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA + + P0(t) * phi * M * P2(t) * Ks^2 * siga2^2 * beta_SA + + P0(t) * alpa * M * Mar * Ks * beta_SA + + P0(t) * M^2 * P2(t) * Mar * Ks^2 * beta_SA + + P0(t) * M^2 * P2(t) * Mar * Ks * siga2 * beta_SA + + P0(t) * M * P4(t) * Mar * Ks * beta_SA + + P0(t) * M * P2(t) * Mar * Ks^2 * siga2 * beta_SA - + P1(t)^2 * beta_SI * phi * M * Mar * Ks * siga2^2 - + P1(t)^2 * beta_SI * phi * M * Ks^2 * siga2^2 + + P1(t)^2 * beta_SI * M * Mar * Ks * siga2^2 + + P1(t)^2 * beta_SI * M * Ks^2 * siga2^2 - + P1(t)^2 * phi * M^2 * Mar * Ks * siga2 * beta_SA + + P1(t)^2 * phi * M^2 * Ks * siga2^2 * beta_SA - + P1(t)^2 * phi * M * Mar * Ks^2 * siga2 * beta_SA + + P1(t)^2 * phi * M * Ks^2 * siga2^2 * beta_SA + + P1(t)^2 * M^2 * Mar * Ks * siga2 * beta_SA + + P1(t)^2 * M^2 * Ks^2 * siga2 * beta_SA + + P1(t)^2 * M * Mar * Ks^2 * siga2 * beta_SA - + P1(t) * beta_SI * P3(t) * phi * M * Mar * siga2 - + P1(t) * beta_SI * P3(t) * phi * M * Ks * siga2 - + P1(t) * beta_SI * P3(t) * phi * Mar * Ks * siga2 - + P1(t) * beta_SI * P3(t) * phi * Mar * siga2^2 - + P1(t) * beta_SI * P3(t) * phi * Ks^2 * siga2 - + P1(t) * beta_SI * P3(t) * phi * Ks * siga2^2 + + P1(t) * beta_SI * P3(t) * M * Mar * siga2 + + P1(t) * beta_SI * P3(t) * M * Ks * siga2 + + P1(t) * beta_SI * P3(t) * Mar * Ks * siga2 + + P1(t) * beta_SI * P3(t) * Mar * siga2^2 + + P1(t) * beta_SI * P3(t) * Ks^2 * siga2 + + P1(t) * beta_SI * P3(t) * Ks * siga2^2 - + P1(t) * beta_SI * phi * alpa * Mar * siga2 - + P1(t) * beta_SI * phi * alpa * Ks * siga2 - + P1(t) * beta_SI * phi * M * P2(t) * Mar * Ks * siga2 - + P1(t) * beta_SI * phi * M * P2(t) * Mar * siga2^2 - + P1(t) * beta_SI * phi * M * P2(t) * Ks^2 * siga2 - + 2 * P1(t) * beta_SI * phi * M * P2(t) * Ks * siga2^2 - + P1(t) * beta_SI * phi * P4(t) * Mar * siga2 - + P1(t) * beta_SI * phi * P4(t) * Ks * siga2 - + P1(t) * beta_SI * phi * P2(t) * Mar * Ks * siga2^2 - + P1(t) * beta_SI * phi * P2(t) * Ks^2 * siga2^2 + + P1(t) * beta_SI * alpa * Mar * siga2 + + P1(t) * beta_SI * alpa * Ks * siga2 + + P1(t) * beta_SI * M * P2(t) * Mar * Ks * siga2 + + P1(t) * beta_SI * M * P2(t) * Mar * siga2^2 + + P1(t) * beta_SI * M * P2(t) * Ks^2 * siga2 + + 2 * P1(t) * beta_SI * M * P2(t) * Ks * siga2^2 + + P1(t) * beta_SI * P4(t) * Mar * siga2 + + P1(t) * beta_SI * P4(t) * Ks * siga2 + + P1(t) * beta_SI * P2(t) * Mar * Ks * siga2^2 + + P1(t) * beta_SI * P2(t) * Ks^2 * siga2^2 - + P1(t) * P3(t) * phi * M^2 * Mar * beta_SA + + P1(t) * P3(t) * phi * M^2 * siga2 * beta_SA - + 2 * P1(t) * P3(t) * phi * M * Mar * Ks * beta_SA - + P1(t) * P3(t) * phi * M * Mar * siga2 * beta_SA + + 2 * P1(t) * P3(t) * phi * M * Ks * siga2 * beta_SA + + P1(t) * P3(t) * phi * M * siga2^2 * beta_SA - + P1(t) * P3(t) * phi * Mar * Ks^2 * beta_SA - + P1(t) * P3(t) * phi * Mar * Ks * siga2 * beta_SA + + P1(t) * P3(t) * phi * Ks^2 * siga2 * beta_SA + + P1(t) * P3(t) * phi * Ks * siga2^2 * beta_SA + + P1(t) * P3(t) * M^2 * Mar * beta_SA + + P1(t) * P3(t) * M^2 * Ks * beta_SA + + 2 * P1(t) * P3(t) * M * Mar * Ks * beta_SA + + P1(t) * P3(t) * M * Mar * siga2 * beta_SA + + P1(t) * P3(t) * M * Ks^2 * beta_SA + + 2 * P1(t) * P3(t) * M * Ks * siga2 * beta_SA + + P1(t) * P3(t) * Mar * Ks^2 * beta_SA + + P1(t) * P3(t) * Mar * Ks * siga2 * beta_SA - + P1(t) * phi * alpa * M * Mar * beta_SA + + P1(t) * phi * alpa * M * siga2 * beta_SA - + P1(t) * phi * alpa * Mar * Ks * beta_SA + + P1(t) * phi * alpa * Ks * siga2 * beta_SA - + P1(t) * phi * M^2 * P2(t) * Mar * Ks * beta_SA - + P1(t) * phi * M^2 * P2(t) * Mar * siga2 * beta_SA + + P1(t) * phi * M^2 * P2(t) * Ks * siga2 * beta_SA + + P1(t) * phi * M^2 * P2(t) * siga2^2 * beta_SA - + P1(t) * phi * M^2 * Mar * P5(t) * Ks * siga2^2 - + P1(t) * phi * M^2 * Ks * beta * siga2^2 - + P1(t) * phi * M * P4(t) * Mar * beta_SA + + P1(t) * phi * M * P4(t) * siga2 * beta_SA - + P1(t) * phi * M * P2(t) * Mar * Ks^2 * beta_SA - + 3 * P1(t) * phi * M * P2(t) * Mar * Ks * siga2 * beta_SA + + P1(t) * phi * M * P2(t) * Ks^2 * siga2 * beta_SA + + 3 * P1(t) * phi * M * P2(t) * Ks * siga2^2 * beta_SA - + P1(t) * phi * P4(t) * Mar * Ks * beta_SA + + P1(t) * phi * P4(t) * Ks * siga2 * beta_SA - + P1(t) * phi * P2(t) * Mar * Ks^2 * siga2 * beta_SA + + P1(t) * phi * P2(t) * Ks^2 * siga2^2 * beta_SA + + P1(t) * alpa * M * Mar * beta_SA + + P1(t) * alpa * M * Ks * beta_SA + + P1(t) * alpa * Mar * Ks * beta_SA + + P1(t) * M^2 * P2(t) * Mar * Ks * beta_SA + + P1(t) * M^2 * P2(t) * Mar * siga2 * beta_SA + + P1(t) * M^2 * P2(t) * Ks^2 * beta_SA + + 2 * P1(t) * M^2 * P2(t) * Ks * siga2 * beta_SA + + P1(t) * M^2 * Mar * P5(t) * Ks * siga2^2 + + P1(t) * M^2 * Ks * beta * siga2^2 + + P1(t) * M * P4(t) * Mar * beta_SA + + P1(t) * M * P4(t) * Ks * beta_SA + + P1(t) * M * P2(t) * Mar * Ks^2 * beta_SA + + 3 * P1(t) * M * P2(t) * Mar * Ks * siga2 * beta_SA + + 2 * P1(t) * M * P2(t) * Ks^2 * siga2 * beta_SA + + P1(t) * P4(t) * Mar * Ks * beta_SA + + P1(t) * P2(t) * Mar * Ks^2 * siga2 * beta_SA - + beta_SI * P3(t) * phi * M * P2(t) * siga2 - + beta_SI * P3(t) * phi * P2(t) * Ks * siga2 - + beta_SI * P3(t) * phi * P2(t) * siga2^2 + + beta_SI * P3(t) * M * P2(t) * siga2 + + beta_SI * P3(t) * P2(t) * Ks * siga2 + + beta_SI * P3(t) * P2(t) * siga2^2 - + beta_SI * phi * alpa * P2(t) * siga2 - + beta_SI * phi * M * P2(t)^2 * Ks * siga2 - + beta_SI * phi * M * P2(t)^2 * siga2^2 - + beta_SI * phi * P4(t) * P2(t) * siga2 - + beta_SI * phi * P2(t)^2 * Ks * siga2^2 + + beta_SI * alpa * P2(t) * siga2 + + beta_SI * M * P2(t)^2 * Ks * siga2 + + beta_SI * M * P2(t)^2 * siga2^2 + + beta_SI * P4(t) * P2(t) * siga2 + + beta_SI * P2(t)^2 * Ks * siga2^2 + + P3(t)^2 * M * beta_SA + + P3(t)^2 * Ks * beta_SA + + P3(t)^2 * siga2 * beta_SA - P3(t) * phi * M^2 * Mar * P5(t) * siga2 - + P3(t) * phi * M^2 * Ks * siga2 - P3(t) * phi * M^2 * beta * siga2 - + P3(t) * phi * M^2 * siga2^2 - P3(t) * phi * M * P2(t) * Mar * beta_SA + + P3(t) * phi * M * P2(t) * siga2 * beta_SA - + P3(t) * phi * M * Mar * P5(t) * Ks * siga2 - + P3(t) * phi * M * Mar * P5(t) * siga2^2 - + P3(t) * phi * M * Ks * beta * siga2 - P3(t) * phi * M * Ks * siga2^2 - + P3(t) * phi * M * beta * siga2^2 - + P3(t) * phi * P2(t) * Mar * Ks * beta_SA - + P3(t) * phi * P2(t) * Mar * siga2 * beta_SA + + P3(t) * phi * P2(t) * Ks * siga2 * beta_SA + + P3(t) * phi * P2(t) * siga2^2 * beta_SA + + P3(t) * alpa * beta_SA + + P3(t) * M^2 * P2(t) * beta_SA + + P3(t) * M^2 * Mar * P5(t) * siga2 + + P3(t) * M^2 * Ks * siga2 + + P3(t) * M^2 * beta * siga2 + + P3(t) * M^2 * siga2^2 + + P3(t) * M * P2(t) * Mar * beta_SA + + 3 * P3(t) * M * P2(t) * Ks * beta_SA + + 2 * P3(t) * M * P2(t) * siga2 * beta_SA + + P3(t) * M * Mar * P5(t) * Ks * siga2 + + P3(t) * M * Mar * P5(t) * siga2^2 + + P3(t) * M * Ks * beta * siga2 + + P3(t) * M * Ks * siga2^2 + + P3(t) * M * beta * siga2^2 + + P3(t) * P4(t) * beta_SA + + P3(t) * P2(t) * Mar * Ks * beta_SA + + P3(t) * P2(t) * Mar * siga2 * beta_SA + + P3(t) * P2(t) * Ks^2 * beta_SA + + 2 * P3(t) * P2(t) * Ks * siga2 * beta_SA - + phi * alpa * M * Mar * P5(t) * siga2 - phi * alpa * M * beta * siga2 - + phi * alpa * P2(t) * Mar * beta_SA + + phi * alpa * P2(t) * siga2 * beta_SA - phi * M^2 * P4(t) * siga2 - + phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - + phi * M^2 * P2(t) * Mar * P5(t) * siga2^2 - + phi * M^2 * P2(t) * Ks * beta * siga2 - + phi * M^2 * P2(t) * Ks * siga2^2 - phi * M^2 * P2(t) * beta * siga2^2 - + phi * M * P4(t) * Mar * P5(t) * siga2 - phi * M * P4(t) * Ks * siga2 - + phi * M * P4(t) * beta * siga2 - phi * M * P4(t) * siga2^2 - + phi * M * P2(t)^2 * Mar * Ks * beta_SA - + phi * M * P2(t)^2 * Mar * siga2 * beta_SA + + phi * M * P2(t)^2 * Ks * siga2 * beta_SA + + phi * M * P2(t)^2 * siga2^2 * beta_SA - + phi * M * P2(t) * Mar * P5(t) * Ks * siga2^2 - + phi * M * P2(t) * Ks * beta * siga2^2 - + phi * P4(t) * P2(t) * Mar * beta_SA + + phi * P4(t) * P2(t) * siga2 * beta_SA - + phi * P2(t)^2 * Mar * Ks * siga2 * beta_SA + + phi * P2(t)^2 * Ks * siga2^2 * beta_SA + + alpa * M * P2(t) * beta_SA + + alpa * M * Mar * P5(t) * siga2 + + alpa * M * beta * siga2 + + alpa * P2(t) * Mar * beta_SA + + alpa * P2(t) * Ks * beta_SA + + M^2 * P4(t) * siga2 + + M^2 * P2(t)^2 * Ks * beta_SA + + M^2 * P2(t)^2 * siga2 * beta_SA + + M^2 * P2(t) * Mar * P5(t) * Ks * siga2 + + M^2 * P2(t) * Mar * P5(t) * siga2^2 + + M^2 * P2(t) * Ks * beta * siga2 + + M^2 * P2(t) * Ks * siga2^2 + + M^2 * P2(t) * beta * siga2^2 + + M * P4(t) * P2(t) * beta_SA + + M * P4(t) * Mar * P5(t) * siga2 + + M * P4(t) * Ks * siga2 + + M * P4(t) * beta * siga2 + + M * P4(t) * siga2^2 + + M * P2(t)^2 * Mar * Ks * beta_SA + + M * P2(t)^2 * Mar * siga2 * beta_SA + + M * P2(t)^2 * Ks^2 * beta_SA + + 2 * M * P2(t)^2 * Ks * siga2 * beta_SA + + M * P2(t) * Mar * P5(t) * Ks * siga2^2 + + M * P2(t) * Ks * beta * siga2^2 + + P4(t) * P2(t) * Mar * beta_SA + + P4(t) * P2(t) * Ks * beta_SA + + P2(t)^2 * Mar * Ks * siga2 * beta_SA + + P2(t)^2 * Ks^2 * siga2 * beta_SA + ) // (phi * M * siga2 - M * siga2), + P1'(t) = P2(t), + P2'(t) = P3(t), + y(t) = P0(t) +) +ident_funcs = [ + Mar, + Ks, + alpa, + (siga1 + phi * Mar - Mar) // phi, + (siga1 * phi - siga1 - phi * Mar + Mar) // (siga1 * phi * beta_SA), + ( + siga1 * beta_SA + beta_SI * phi * siga2 + phi * Mar * beta_SA - + phi * siga2 * beta_SA - Mar * beta_SA + ) // (phi * M * siga2), + ( + siga1 * phi * M * siga2 - siga1 * M * siga2 - phi * M * Mar * siga2 + + M * Mar * siga2 + ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), + ( + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 - + siga1 * phi * siga2 * beta_SA - siga1 * M * beta_SA - + beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - + phi * M * Mar * beta_SA + M * Mar * beta_SA + ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), + ( + siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + siga1 * phi * Mar * beta_SA - + siga1 * phi * siga2 * beta_SA - siga1 * Mar * beta_SA + + siga1 * siga2 * beta_SA + + beta_SI * phi * M * siga2 - beta_SI * phi * Mar * siga2 + + beta_SI * phi * siga2^2 + + beta_SI * Mar * siga2 + + phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - + Mar * siga2 * beta_SA + ) // (phi * M * siga2), +] +# Really large and takes a lot of time, so commented +# push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +### +# Cases with states + +ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t), y(t) = x(t)) +T = typeof(x) +ident_funcs = [x] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) + +ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + u(t), y(t) = x(t)) +ident_funcs = [a, x] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) +) +ident_funcs = [x1, c, d, a, b * x2] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) +) +ident_funcs = [x1, c, d, a, b * x2] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +# Diagonal with simple spectrum and observable states +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = λ1 * x1(t) + β1 * u1(t), + x2'(t) = λ2 * x2(t) + β2 * u2(t), + x3'(t) = λ3 * x3(t) + β3 * u3(t), + y(t) = x1(t) + x2(t) + x3(t) +) +ident_funcs = [λ1, λ2, λ3, β1, β2, β3, x1, x2, x3] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + Θ * x2(t), + x2'(t) = 0, + y(t) = x1(t) +) +ident_funcs = [x1, x2 * Θ] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * x2(t), + x2'(t) = x3(t), + x3'(t) = C, + y(t) = x1(t) +) +ident_funcs = [x1, α * x2, α * x3, α * C] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * (x1 - x2), + x2'(t) = α * (x1 + x2), + y(t) = (x1^2 + x2^2) // 2, +) +ident_funcs = [α, x1^2 + x2^2] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +ode = StructuralIdentifiability.@ODEmodel(x'(t) = a * x(t) + b * u(t), y(t) = c * x(t)) +ident_funcs = [b * c, a, x // b] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +# llw1987 model +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -p1 * x1(t) + p2 * u(t), + x2'(t) = -p3 * x2(t) + p4 * u(t), + x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), + y1(t) = x3(t) +) +ident_funcs = [ + x3, + x2 * x1 // one(x1), + p3 * p1 // one(x1), + p2 * p4 // one(x1), + (p3 + p1) // one(x1), + (p2 * x2 + p4 * x1) // one(x1), + (p3 - p1) // (p2 * x2 - p4 * x1), +] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +# Regression test: Previously failed for with_states=true because of the bug in +# `linear_relations_between_normal_forms` +# Fujita +ode = StructuralIdentifiability.@ODEmodel( + EGFR'(t) = + EGFR_turnover * pro_EGFR(t) + EGF_EGFR(t) * reaction_1_k2 - + EGFR(t) * EGFR_turnover - EGF_EGFR(t) * reaction_1_k1, + pEGFR'(t) = + EGF_EGFR(t) * reaction_9_k1 - pEGFR(t) * reaction_4_k1 + + pEGFR_Akt(t) * reaction_2_k2 + + pEGFR_Akt(t) * reaction_3_k1 - Akt(t) * pEGFR(t) * reaction_2_k1, + pEGFR_Akt'(t) = + Akt(t) * pEGFR(t) * reaction_2_k1 - pEGFR_Akt(t) * reaction_3_k1 - + pEGFR_Akt(t) * reaction_2_k2, + Akt'(t) = + pAkt(t) * reaction_7_k1 + pEGFR_Akt(t) * reaction_2_k2 - + Akt(t) * pEGFR(t) * reaction_2_k1, + pAkt'(t) = + pAkt_S6(t) * reaction_5_k2 - pAkt(t) * reaction_7_k1 + + pAkt_S6(t) * reaction_6_k1 + + pEGFR_Akt(t) * reaction_3_k1 - S6(t) * pAkt(t) * reaction_5_k1, + S6'(t) = + pAkt_S6(t) * reaction_5_k2 + pS6(t) * reaction_8_k1 - + S6(t) * pAkt(t) * reaction_5_k1, + pAkt_S6'(t) = + S6(t) * pAkt(t) * reaction_5_k1 - pAkt_S6(t) * reaction_6_k1 - + pAkt_S6(t) * reaction_5_k2, + pS6'(t) = pAkt_S6(t) * reaction_6_k1 - pS6(t) * reaction_8_k1, + EGF_EGFR'(t) = + EGF_EGFR(t) * reaction_1_k1 - EGF_EGFR(t) * reaction_9_k1 - + EGF_EGFR(t) * reaction_1_k2, + y1(t) = a1 * (pEGFR(t) + pEGFR_Akt(t)), + y2(t) = a2 * (pAkt(t) + pAkt_S6(t)), + y3(t) = a3 * pS6(t) +) +ident_funcs = [ + reaction_8_k1, + reaction_2_k2, + reaction_5_k2, + reaction_3_k1, + reaction_6_k1, + reaction_7_k1, + reaction_4_k1, + reaction_2_k1 * pAkt_S6, + reaction_2_k1 * S6, + reaction_5_k1 * pAkt_S6, + pEGFR_Akt * reaction_2_k1, + Akt * reaction_2_k1, + a1 * pAkt_S6, + pEGFR * reaction_2_k1, + pAkt * reaction_2_k1, + a3 * pAkt_S6, + pS6 * reaction_2_k1, + a2 * pAkt_S6, + reaction_9_k1 * reaction_2_k1 * EGF_EGFR, + reaction_1_k1 - reaction_9_k1 - reaction_1_k2, +] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +# Bruno2016 model +ode = StructuralIdentifiability.@ODEmodel( + beta'(t) = -kbeta * beta(t), + cry'(t) = -cry(t) * kcrybeta - cry(t) * kcryOH, + zea'(t) = -zea(t) * kzea, + beta10'(t) = cry(t) * kcryOH - beta10(t) * kbeta10 + kbeta * beta(t), + OHbeta10'(t) = cry(t) * kcrybeta + zea(t) * kzea - OHbeta10(t) * kOHbeta10, + betaio'(t) = cry(t) * kcrybeta + beta10(t) * kbeta10 + kbeta * beta(t), + OHbetaio'(t) = cry(t) * kcryOH + zea(t) * kzea + OHbeta10(t) * kOHbeta10, + y1(t) = beta(t), + y2(t) = beta10(t) +) +ident_funcs = [beta10, beta, kbeta, kbeta10, cry * kcryOH, kcrybeta + kcryOH] +push!(test_cases, (ode = ode, with_states = true, ident_funcs = ident_funcs)) + +# STAT-5 model from +# MODELING THE NONLINEAR DYNAMICS OF CELLULAR SIGNAL TRANSDUCTION +# DOI: https://doi.org/10.1142/S0218127404010461 +ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -k1 * x1 * EpoR_A, + x2'(t) = k1 * x1 * EpoR_A - k2 * x2^2, + x3'(t) = -k3 * x3 + 0.5 * k2 * x2^2, + x4'(t) = k3 * x3, + y1(t) = k5 * (x2 + 2x3), + y2(t) = k6 * (x1 + x2 + 2x3), + y3(t) = k7 * EpoR_A +) +ident_funcs = [k3, k1 // k7, k5 // k2, k6 // k2, k7 * EpoR_A] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) + +ode = @ODEmodel(x1'(t) = x1, x2'(t) = x2, y(t) = x1 + x2(t)) +ident_funcs = [x1 + x2] +push!(test_cases, (ode = ode, ident_funcs = ident_funcs, with_states = true)) + +# TODO: verify that Maple returns the same +@testset "Identifiable functions of parameters" begin + p = 0.99 + for case in test_cases + for simplify in [:weak, :standard] # :strong] + ode = case.ode + true_ident_funcs = case.ident_funcs + with_states = false + if haskey(case, :with_states) + with_states = case.with_states end + result_funcs = StructuralIdentifiability.find_identifiable_functions( + ode, + simplify = simplify, + with_states = with_states, + ) + + if isempty(true_ident_funcs) + @test isempty(result_funcs) + continue + end + + @test parent(numerator(result_funcs[1])) == parent(ode) + + R = parent(numerator(result_funcs[1])) + + @info "Test, result_funcs = \n$result_funcs" case simplify R with_states + + true_ident_funcs = map(f -> f // one(f), true_ident_funcs) + true_ident_funcs = map( + f -> StructuralIdentifiability.parent_ring_change(f, R), + true_ident_funcs, + ) + + # Check inclusion in + @test StructuralIdentifiability.fields_equal( + StructuralIdentifiability.RationalFunctionField(result_funcs), + StructuralIdentifiability.RationalFunctionField(true_ident_funcs), + p, + ) end end end diff --git a/test/io_cases.jl b/test/io_cases.jl index 762f1c55f..220691f81 100644 --- a/test/io_cases.jl +++ b/test/io_cases.jl @@ -1,155 +1,153 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Checking io-equations: single output" begin - test_cases = [] +@testset "Checking io-equations: single output" begin + test_cases = [] - # 2-compartiment model - R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = polynomial_ring( - QQ, - ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], - ) - correct = - y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t) + u(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ), - :correct => correct, + # 2-compartiment model + R, (y_0, y_1, y_2, u_0, u_1, a01, a12, a21) = polynomial_ring( + QQ, + ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], + ) + correct = + y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t) + u(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) ), - ) + :correct => correct, + ), + ) - #--------------------------------------- - # Chen-Lee model - R, (y1_0, y1_1, y1_2, y1_3, a, b, c) = - polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) - correct = - 9 * y1_3^2 * y1_0^2 - 18 * y1_3 * y1_2 * y1_1 * y1_0 - - 18 * y1_3 * y1_2 * y1_0^2 * a - 36 * y1_3 * y1_2 * y1_0^2 * b - - 36 * y1_3 * y1_2 * y1_0^2 * c + - 18 * y1_3 * y1_1^2 * y1_0 * a + - 18 * y1_3 * y1_1^2 * y1_0 * b + - 18 * y1_3 * y1_1^2 * y1_0 * c - 24 * y1_3 * y1_1 * y1_0^4 + - 18 * y1_3 * y1_1 * y1_0^2 * a * b + - 18 * y1_3 * y1_1 * y1_0^2 * a * c + - 18 * y1_3 * y1_1 * y1_0^2 * b^2 + - 36 * y1_3 * y1_1 * y1_0^2 * b * c + - 18 * y1_3 * y1_1 * y1_0^2 * c^2 + - 24 * y1_3 * y1_0^5 * a - 18 * y1_3 * y1_0^3 * a * b^2 - - 36 * y1_3 * y1_0^3 * a * b * c - 18 * y1_3 * y1_0^3 * a * c^2 + - 9 * y1_2^2 * y1_1^2 + - 18 * y1_2^2 * y1_1 * y1_0 * a + - 36 * y1_2^2 * y1_1 * y1_0 * b + - 36 * y1_2^2 * y1_1 * y1_0 * c + - 9 * y1_2^2 * y1_0^2 * a^2 + - 36 * y1_2^2 * y1_0^2 * a * b + - 36 * y1_2^2 * y1_0^2 * a * c + - 27 * y1_2^2 * y1_0^2 * b^2 + - 90 * y1_2^2 * y1_0^2 * b * c + - 27 * y1_2^2 * y1_0^2 * c^2 - 18 * y1_2 * y1_1^3 * a - 18 * y1_2 * y1_1^3 * b - - 18 * y1_2 * y1_1^3 * c + 24 * y1_2 * y1_1^2 * y1_0^3 - - 18 * y1_2 * y1_1^2 * y1_0 * a^2 - 72 * y1_2 * y1_1^2 * y1_0 * a * b - - 72 * y1_2 * y1_1^2 * y1_0 * a * c - 54 * y1_2 * y1_1^2 * y1_0 * b^2 - - 108 * y1_2 * y1_1^2 * y1_0 * b * c - 54 * y1_2 * y1_1^2 * y1_0 * c^2 + - 48 * y1_2 * y1_1 * y1_0^4 * b + - 48 * y1_2 * y1_1 * y1_0^4 * c - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * b - - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * c - 18 * y1_2 * y1_1 * y1_0^2 * a * b^2 - - 108 * y1_2 * y1_1 * y1_0^2 * a * b * c - 18 * y1_2 * y1_1 * y1_0^2 * a * c^2 - - 18 * y1_2 * y1_1 * y1_0^2 * b^3 - 126 * y1_2 * y1_1 * y1_0^2 * b^2 * c - - 126 * y1_2 * y1_1 * y1_0^2 * b * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * c^3 - - 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - - 48 * y1_2 * y1_0^5 * a * c + - 18 * y1_2 * y1_0^3 * a^2 * b^2 + - 36 * y1_2 * y1_0^3 * a^2 * b * c + - 18 * y1_2 * y1_0^3 * a^2 * c^2 + - 18 * y1_2 * y1_0^3 * a * b^3 + - 126 * y1_2 * y1_0^3 * a * b^2 * c + - 126 * y1_2 * y1_0^3 * a * b * c^2 + - 18 * y1_2 * y1_0^3 * a * c^3 + - 9 * y1_1^4 * a^2 + - 18 * y1_1^4 * a * b + - 18 * y1_1^4 * a * c + - 9 * y1_1^4 * b^2 + - 18 * y1_1^4 * b * c + - 9 * y1_1^4 * c^2 - 24 * y1_1^3 * y1_0^3 * a - 24 * y1_1^3 * y1_0^3 * b - - 24 * y1_1^3 * y1_0^3 * c + - 18 * y1_1^3 * y1_0 * a^2 * b + - 18 * y1_1^3 * y1_0 * a^2 * c + - 36 * y1_1^3 * y1_0 * a * b^2 + - 72 * y1_1^3 * y1_0 * a * b * c + - 36 * y1_1^3 * y1_0 * a * c^2 + - 18 * y1_1^3 * y1_0 * b^3 + - 54 * y1_1^3 * y1_0 * b^2 * c + - 54 * y1_1^3 * y1_0 * b * c^2 + - 18 * y1_1^3 * y1_0 * c^3 + - 16 * y1_1^2 * y1_0^6 + - 24 * y1_1^2 * y1_0^4 * a^2 - 12 * y1_1^2 * y1_0^4 * b^2 - - 72 * y1_1^2 * y1_0^4 * b * c - 12 * y1_1^2 * y1_0^4 * c^2 - - 18 * y1_1^2 * y1_0^2 * a^2 * b^2 - 18 * y1_1^2 * y1_0^2 * a^2 * c^2 - - 18 * y1_1^2 * y1_0^2 * a * b^3 + - 18 * y1_1^2 * y1_0^2 * a * b^2 * c + - 18 * y1_1^2 * y1_0^2 * a * b * c^2 - 18 * y1_1^2 * y1_0^2 * a * c^3 + - 36 * y1_1^2 * y1_0^2 * b^3 * c + - 72 * y1_1^2 * y1_0^2 * b^2 * c^2 + - 36 * y1_1^2 * y1_0^2 * b * c^3 - 32 * y1_1 * y1_0^7 * a + - 24 * y1_1 * y1_0^5 * a^2 * b + - 24 * y1_1 * y1_0^5 * a^2 * c + - 24 * y1_1 * y1_0^5 * a * b^2 + - 144 * y1_1 * y1_0^5 * a * b * c + - 24 * y1_1 * y1_0^5 * a * c^2 - 72 * y1_1 * y1_0^3 * a^2 * b^2 * c - - 72 * y1_1 * y1_0^3 * a^2 * b * c^2 - 72 * y1_1 * y1_0^3 * a * b^3 * c - - 144 * y1_1 * y1_0^3 * a * b^2 * c^2 - 72 * y1_1 * y1_0^3 * a * b * c^3 + - 16 * y1_0^8 * a^2 - 12 * y1_0^6 * a^2 * b^2 - 72 * y1_0^6 * a^2 * b * c - - 12 * y1_0^6 * a^2 * c^2 + - 36 * y1_0^4 * a^2 * b^3 * c + - 72 * y1_0^4 * a^2 * b^2 * c^2 + - 36 * y1_0^4 * a^2 * b * c^3 + #--------------------------------------- + # Chen-Lee model + R, (y1_0, y1_1, y1_2, y1_3, a, b, c) = + polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "y1(t)_3", "a", "b", "c"]) + correct = + 9 * y1_3^2 * y1_0^2 - 18 * y1_3 * y1_2 * y1_1 * y1_0 - + 18 * y1_3 * y1_2 * y1_0^2 * a - 36 * y1_3 * y1_2 * y1_0^2 * b - + 36 * y1_3 * y1_2 * y1_0^2 * c + + 18 * y1_3 * y1_1^2 * y1_0 * a + + 18 * y1_3 * y1_1^2 * y1_0 * b + + 18 * y1_3 * y1_1^2 * y1_0 * c - 24 * y1_3 * y1_1 * y1_0^4 + + 18 * y1_3 * y1_1 * y1_0^2 * a * b + + 18 * y1_3 * y1_1 * y1_0^2 * a * c + + 18 * y1_3 * y1_1 * y1_0^2 * b^2 + + 36 * y1_3 * y1_1 * y1_0^2 * b * c + + 18 * y1_3 * y1_1 * y1_0^2 * c^2 + + 24 * y1_3 * y1_0^5 * a - 18 * y1_3 * y1_0^3 * a * b^2 - + 36 * y1_3 * y1_0^3 * a * b * c - 18 * y1_3 * y1_0^3 * a * c^2 + + 9 * y1_2^2 * y1_1^2 + + 18 * y1_2^2 * y1_1 * y1_0 * a + + 36 * y1_2^2 * y1_1 * y1_0 * b + + 36 * y1_2^2 * y1_1 * y1_0 * c + + 9 * y1_2^2 * y1_0^2 * a^2 + + 36 * y1_2^2 * y1_0^2 * a * b + + 36 * y1_2^2 * y1_0^2 * a * c + + 27 * y1_2^2 * y1_0^2 * b^2 + + 90 * y1_2^2 * y1_0^2 * b * c + + 27 * y1_2^2 * y1_0^2 * c^2 - 18 * y1_2 * y1_1^3 * a - 18 * y1_2 * y1_1^3 * b - + 18 * y1_2 * y1_1^3 * c + 24 * y1_2 * y1_1^2 * y1_0^3 - + 18 * y1_2 * y1_1^2 * y1_0 * a^2 - 72 * y1_2 * y1_1^2 * y1_0 * a * b - + 72 * y1_2 * y1_1^2 * y1_0 * a * c - 54 * y1_2 * y1_1^2 * y1_0 * b^2 - + 108 * y1_2 * y1_1^2 * y1_0 * b * c - 54 * y1_2 * y1_1^2 * y1_0 * c^2 + + 48 * y1_2 * y1_1 * y1_0^4 * b + + 48 * y1_2 * y1_1 * y1_0^4 * c - 18 * y1_2 * y1_1 * y1_0^2 * a^2 * b - + 18 * y1_2 * y1_1 * y1_0^2 * a^2 * c - 18 * y1_2 * y1_1 * y1_0^2 * a * b^2 - + 108 * y1_2 * y1_1 * y1_0^2 * a * b * c - 18 * y1_2 * y1_1 * y1_0^2 * a * c^2 - + 18 * y1_2 * y1_1 * y1_0^2 * b^3 - 126 * y1_2 * y1_1 * y1_0^2 * b^2 * c - + 126 * y1_2 * y1_1 * y1_0^2 * b * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * c^3 - + 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - + 48 * y1_2 * y1_0^5 * a * c + + 18 * y1_2 * y1_0^3 * a^2 * b^2 + + 36 * y1_2 * y1_0^3 * a^2 * b * c + + 18 * y1_2 * y1_0^3 * a^2 * c^2 + + 18 * y1_2 * y1_0^3 * a * b^3 + + 126 * y1_2 * y1_0^3 * a * b^2 * c + + 126 * y1_2 * y1_0^3 * a * b * c^2 + + 18 * y1_2 * y1_0^3 * a * c^3 + + 9 * y1_1^4 * a^2 + + 18 * y1_1^4 * a * b + + 18 * y1_1^4 * a * c + + 9 * y1_1^4 * b^2 + + 18 * y1_1^4 * b * c + + 9 * y1_1^4 * c^2 - 24 * y1_1^3 * y1_0^3 * a - 24 * y1_1^3 * y1_0^3 * b - + 24 * y1_1^3 * y1_0^3 * c + + 18 * y1_1^3 * y1_0 * a^2 * b + + 18 * y1_1^3 * y1_0 * a^2 * c + + 36 * y1_1^3 * y1_0 * a * b^2 + + 72 * y1_1^3 * y1_0 * a * b * c + + 36 * y1_1^3 * y1_0 * a * c^2 + + 18 * y1_1^3 * y1_0 * b^3 + + 54 * y1_1^3 * y1_0 * b^2 * c + + 54 * y1_1^3 * y1_0 * b * c^2 + + 18 * y1_1^3 * y1_0 * c^3 + + 16 * y1_1^2 * y1_0^6 + + 24 * y1_1^2 * y1_0^4 * a^2 - 12 * y1_1^2 * y1_0^4 * b^2 - + 72 * y1_1^2 * y1_0^4 * b * c - 12 * y1_1^2 * y1_0^4 * c^2 - + 18 * y1_1^2 * y1_0^2 * a^2 * b^2 - 18 * y1_1^2 * y1_0^2 * a^2 * c^2 - + 18 * y1_1^2 * y1_0^2 * a * b^3 + + 18 * y1_1^2 * y1_0^2 * a * b^2 * c + + 18 * y1_1^2 * y1_0^2 * a * b * c^2 - 18 * y1_1^2 * y1_0^2 * a * c^3 + + 36 * y1_1^2 * y1_0^2 * b^3 * c + + 72 * y1_1^2 * y1_0^2 * b^2 * c^2 + + 36 * y1_1^2 * y1_0^2 * b * c^3 - 32 * y1_1 * y1_0^7 * a + + 24 * y1_1 * y1_0^5 * a^2 * b + + 24 * y1_1 * y1_0^5 * a^2 * c + + 24 * y1_1 * y1_0^5 * a * b^2 + + 144 * y1_1 * y1_0^5 * a * b * c + + 24 * y1_1 * y1_0^5 * a * c^2 - 72 * y1_1 * y1_0^3 * a^2 * b^2 * c - + 72 * y1_1 * y1_0^3 * a^2 * b * c^2 - 72 * y1_1 * y1_0^3 * a * b^3 * c - + 144 * y1_1 * y1_0^3 * a * b^2 * c^2 - 72 * y1_1 * y1_0^3 * a * b * c^3 + + 16 * y1_0^8 * a^2 - 12 * y1_0^6 * a^2 * b^2 - 72 * y1_0^6 * a^2 * b * c - + 12 * y1_0^6 * a^2 * c^2 + + 36 * y1_0^4 * a^2 * b^3 * c + + 72 * y1_0^4 * a^2 * b^2 * c^2 + + 36 * y1_0^4 * a^2 * b * c^3 - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = a * x0(t) - x1(t) * x2(t), - x1'(t) = b * x1(t) + x0(t) * x2(t), - x2'(t) = c * x2(t) + 1 // 3 * x0(t) * x1(t), - y1(t) = x0(t) - ), - :correct => correct, + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = a * x0(t) - x1(t) * x2(t), + x1'(t) = b * x1(t) + x0(t) * x2(t), + x2'(t) = c * x2(t) + 1 // 3 * x0(t) * x1(t), + y1(t) = x0(t) ), - ) + :correct => correct, + ), + ) - #--------------------------------------- + #--------------------------------------- - # predator-prey model - R, (y1_0, y1_1, y1_2, a, b, c, d) = - polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) - correct = - y1_2 * y1_0 - y1_1^2 - y1_1 * y1_0^2 * d + y1_1 * y1_0 * c + y1_0^3 * a * d - - y1_0^2 * a * c + # predator-prey model + R, (y1_0, y1_1, y1_2, a, b, c, d) = + polynomial_ring(QQ, ["y1(t)_0", "y1(t)_1", "y1(t)_2", "a", "b", "c", "d"]) + correct = + y1_2 * y1_0 - y1_1^2 - y1_1 * y1_0^2 * d + y1_1 * y1_0 * c + y1_0^3 * a * d - + y1_0^2 * a * c - push!( - test_cases, - Dict( - :ode => @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t), - x1'(t) = -c * x1(t) + d * x0(t) * x1(t), - y1(t) = x0(t) - ), - :correct => correct, + push!( + test_cases, + Dict( + :ode => @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t), + x1'(t) = -c * x1(t) + d * x0(t) * x1(t), + y1(t) = x0(t) ), - ) + :correct => correct, + ), + ) - #--------------------------------------- + #--------------------------------------- - for case in test_cases - ode = case[:ode] - io_eq = collect(values(find_ioequations(ode)))[1] - correct = parent_ring_change(case[:correct], parent(io_eq)) - divisibility, remainder = divides(io_eq, correct) - @test divisibility - @test total_degree(remainder) == 0 - end + for case in test_cases + ode = case[:ode] + io_eq = collect(values(find_ioequations(ode)))[1] + correct = parent_ring_change(case[:correct], parent(io_eq)) + divisibility, remainder = divides(io_eq, correct) + @test divisibility + @test total_degree(remainder) == 0 end end diff --git a/test/io_projections.jl b/test/io_projections.jl index 84cde582a..38f52c884 100644 --- a/test/io_projections.jl +++ b/test/io_projections.jl @@ -1,67 +1,65 @@ -if GROUP == "All" || GROUP == "Core" - @testset "IO-projections (+ extra projection)" begin - cases = [] +@testset "IO-projections (+ extra projection)" begin + cases = [] - # Example from remark 3 in https://arxiv.org/pdf/2111.00991.pdf - ode = @ODEmodel( - x1'(t) = (1 + x1(t)^2) // 2, - x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), - y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), - y2(t) = x2(t) - ) - push!(cases, ode) + # Example from remark 3 in https://arxiv.org/pdf/2111.00991.pdf + ode = @ODEmodel( + x1'(t) = (1 + x1(t)^2) // 2, + x2'(t) = (1 - x1(t)^2) // (1 + x1(t)^2), + y1(t) = 2 * x1(t) // (b * (1 + x1(t)^2)), + y2(t) = x2(t) + ) + push!(cases, ode) - #-------------------------------------------------------- + #-------------------------------------------------------- - # Example from https://github.com/SciML/StructuralIdentifiability.jl/issues/132 - ode = @ODEmodel( - Complex'(t) = - 1 / C * ( - ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) - - (ke_Complex * Complex(t) * C) - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) - ), - Complex_2'(t) = - 1 / C * ( - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) - (ke_Complex_2 * Complex_2(t) * C) - ), - Drug'(t) = - 1 / C * ( - -(ke_Drug * Drug(t) * C) - - ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + - (45 / 100 * ka * Drug_SC(t)) - ), - free_receptor'(t) = - 1 / C * ( - -((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + - (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) - ), - Drug_SC'(t) = - -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + - u_SC(t), - y1(t) = Drug(t), - y2(t) = free_receptor(t) + Complex(t) + 2 * Complex_2(t) - ) - push!(cases, ode) + # Example from https://github.com/SciML/StructuralIdentifiability.jl/issues/132 + ode = @ODEmodel( + Complex'(t) = + 1 / C * ( + ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) - + (ke_Complex * Complex(t) * C) - ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) + ), + Complex_2'(t) = + 1 / C * ( + ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) - (ke_Complex_2 * Complex_2(t) * C) + ), + Drug'(t) = + 1 / C * ( + -(ke_Drug * Drug(t) * C) - + ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + + (45 / 100 * ka * Drug_SC(t)) + ), + free_receptor'(t) = + 1 / C * ( + -((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + + (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - ( + (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C + ) + ), + Drug_SC'(t) = + -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + + u_SC(t), + y1(t) = Drug(t), + y2(t) = free_receptor(t) + Complex(t) + 2 * Complex_2(t) + ) + push!(cases, ode) - #--------------------------------------------------------- + #--------------------------------------------------------- - for ode in cases - proj, gpg, _ = find_ioprojections(ode, false) - for p in values(proj) - @test choose([p], gpg) == p - end - @test !check_primality(proj) - # taking simply a sum instead of random linear combination - extra_projection = sum(keys(proj)) - proj, gpg, projection_poly = find_ioprojections(ode, false, extra_projection) - @test choose([projection_poly], gpg) == projection_poly - @test check_primality(proj, [projection_poly]) + for ode in cases + proj, gpg, _ = find_ioprojections(ode, false) + for p in values(proj) + @test choose([p], gpg) == p end + @test !check_primality(proj) + # taking simply a sum instead of random linear combination + extra_projection = sum(keys(proj)) + proj, gpg, projection_poly = find_ioprojections(ode, false, extra_projection) + @test choose([projection_poly], gpg) == projection_poly + @test check_primality(proj, [projection_poly]) end end diff --git a/test/lc_univariate.jl b/test/lc_univariate.jl index 532b2a50c..08c2922c8 100644 --- a/test/lc_univariate.jl +++ b/test/lc_univariate.jl @@ -1,10 +1,8 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Univariate leading coefficient" begin - R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) - p = x^2 * y + x^2 * (z + z^3) + y - 5 - @test lc_univariate(p, x) == y + z + z^3 - @test lc_univariate(p, z) == x^2 - @test lc_univariate(p, y) == 1 + x^2 - @test lc_univariate(x + y, z) == x + y - end +@testset "Univariate leading coefficient" begin + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) + p = x^2 * y + x^2 * (z + z^3) + y - 5 + @test lc_univariate(p, x) == y + z + z^3 + @test lc_univariate(p, z) == x^2 + @test lc_univariate(p, y) == 1 + x^2 + @test lc_univariate(x + y, z) == x + y end diff --git a/test/lie_derivative.jl b/test/lie_derivative.jl index 5969405fc..250b22d71 100644 --- a/test/lie_derivative.jl +++ b/test/lie_derivative.jl @@ -1,24 +1,22 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Lie derivative" begin - ode = @ODEmodel( - x1'(t) = a * x1(t) + b * u(t), - x2'(t) = 1 // (1 - x1(t) - x2(t)), - y(t) = x1(t) - ) - @test lie_derivative(x1 + x2, ode) == - ((a * x1 + b * u) * (1 - x1 - x2) + 1) // (1 - x1 - x2) - @test lie_derivative(one(x1), ode) == zero(x1) - @test lie_derivative(zero(x1), ode) == zero(x1) +@testset "Lie derivative" begin + ode = @ODEmodel( + x1'(t) = a * x1(t) + b * u(t), + x2'(t) = 1 // (1 - x1(t) - x2(t)), + y(t) = x1(t) + ) + @test lie_derivative(x1 + x2, ode) == + ((a * x1 + b * u) * (1 - x1 - x2) + 1) // (1 - x1 - x2) + @test lie_derivative(one(x1), ode) == zero(x1) + @test lie_derivative(zero(x1), ode) == zero(x1) - ode = @ODEmodel( - x1'(t) = x2(t) // x1(t) + x2(t) * u(t), - x2'(t) = -1 - x1(t) * u(t), - y(t) = 1 - ) - @test lie_derivative(x1^2 + x2^2 // 1, ode) == zero(parent(ode)) // 1 + ode = @ODEmodel( + x1'(t) = x2(t) // x1(t) + x2(t) * u(t), + x2'(t) = -1 - x1(t) * u(t), + y(t) = 1 + ) + @test lie_derivative(x1^2 + x2^2 // 1, ode) == zero(parent(ode)) // 1 - ode = @ODEmodel(x1'(t) = 2x1(t) + 3a, y(t) = x1(t)) - @test lie_derivative(5x1^2, ode) == 10x1 * (2x1 + 3a) - @test lie_derivative(a, ode) == zero(a) - end + ode = @ODEmodel(x1'(t) = 2x1(t) + 3a, y(t) = x1(t)) + @test lie_derivative(5x1^2, ode) == 10x1 * (2x1 + 3a) + @test lie_derivative(a, ode) == zero(a) end diff --git a/test/linear_compartment.jl b/test/linear_compartment.jl index 1b2e48bda..d25ca9f1e 100644 --- a/test/linear_compartment.jl +++ b/test/linear_compartment.jl @@ -1,169 +1,167 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Identifiability of linear compartment models" begin - test_cases = [] - - push!( - test_cases, - Dict( - :graph => [Array{Int, 1}(), [1], [1]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [1], - :result => Dict((3, 1) => :locally, (2, 1) => :locally), +@testset "Identifiability of linear compartment models" begin + test_cases = [] + + push!( + test_cases, + Dict( + :graph => [Array{Int, 1}(), [1], [1]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [1], + :result => Dict((3, 1) => :locally, (2, 1) => :locally), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [Array{Int, 1}(), [1], [1, 2]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [1], + :result => Dict( + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, + (2, 1) => :locally, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [Array{Int, 1}(), [1], [1, 2]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [1], - :result => Dict( - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, - (2, 1) => :locally, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [1], + :leaks => Array{Int, 1}(), + :inputs => [2], + :result => Dict( + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (1, 2) => :locally, + (3, 2) => :nonidentifiable, + (2, 1) => :globally, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [1], - :leaks => Array{Int, 1}(), - :inputs => [2], - :result => Dict( - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (1, 2) => :locally, - (3, 2) => :nonidentifiable, - (2, 1) => :globally, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [2], + :leaks => Array{Int, 1}(), + :inputs => [2], + :result => Dict( + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (1, 2) => :locally, + (3, 2) => :nonidentifiable, + (2, 1) => :nonidentifiable, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [2], - :leaks => Array{Int, 1}(), - :inputs => [2], - :result => Dict( - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (1, 2) => :locally, - (3, 2) => :nonidentifiable, - (2, 1) => :nonidentifiable, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2, 3], [1, 3], [1, 2]], + :outputs => [3], + :leaks => [1], + :inputs => [1, 2], + :result => Dict( + (1, 2) => :globally, + (1, 3) => :globally, + (2, 1) => :globally, + (2, 3) => :globally, + (3, 1) => :globally, + (3, 2) => :globally, + (1, 0) => :globally, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2, 3], [1, 3], [1, 2]], - :outputs => [3], - :leaks => [1], - :inputs => [1, 2], - :result => Dict( - (1, 2) => :globally, - (1, 3) => :globally, - (2, 1) => :globally, - (2, 3) => :globally, - (3, 1) => :globally, - (3, 2) => :globally, - (1, 0) => :globally, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2, 3], [1, 3], [1, 2]], + :outputs => [3], + :leaks => Array{Int, 1}(), + :inputs => [1, 2], + :result => Dict( + (1, 2) => :nonidentifiable, + (1, 3) => :globally, + (2, 1) => :nonidentifiable, + (2, 3) => :globally, + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2, 3], [1, 3], [1, 2]], - :outputs => [3], - :leaks => Array{Int, 1}(), - :inputs => [1, 2], - :result => Dict( - (1, 2) => :nonidentifiable, - (1, 3) => :globally, - (2, 1) => :nonidentifiable, - (2, 3) => :globally, - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [3], + :leaks => [1], + :inputs => [1, 3], + :result => Dict( + (1, 2) => :globally, + (2, 1) => :globally, + (2, 3) => :globally, + (3, 1) => :globally, + (3, 2) => :globally, + (1, 0) => :globally, ), - ) - - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [3], - :leaks => [1], - :inputs => [1, 3], - :result => Dict( - (1, 2) => :globally, - (2, 1) => :globally, - (2, 3) => :globally, - (3, 1) => :globally, - (3, 2) => :globally, - (1, 0) => :globally, - ), + ), + ) + + #-------------------------------------------------------------------------- + + push!( + test_cases, + Dict( + :graph => [[2], [1, 3], [1, 2]], + :outputs => [3], + :leaks => Array{Int, 1}(), + :inputs => [1, 3], + :result => Dict( + (1, 2) => :nonidentifiable, + (2, 1) => :nonidentifiable, + (2, 3) => :nonidentifiable, + (3, 1) => :nonidentifiable, + (3, 2) => :nonidentifiable, ), - ) + ), + ) - #-------------------------------------------------------------------------- - - push!( - test_cases, - Dict( - :graph => [[2], [1, 3], [1, 2]], - :outputs => [3], - :leaks => Array{Int, 1}(), - :inputs => [1, 3], - :result => Dict( - (1, 2) => :nonidentifiable, - (2, 1) => :nonidentifiable, - (2, 3) => :nonidentifiable, - (3, 1) => :nonidentifiable, - (3, 2) => :nonidentifiable, - ), - ), - ) + #-------------------------------------------------------------------------- - #-------------------------------------------------------------------------- - - for case in test_cases - ode = linear_compartment_model( - case[:graph], - case[:inputs], - case[:outputs], - case[:leaks], - ) - bring = ode.poly_ring - correct = Dict{QQMPolyRingElem, Symbol}() - for (e, id) in case[:result] - correct[str_to_var("a_$(e[2])_$(e[1])", bring)] = id - end - result = assess_identifiability(ode, funcs_to_check = collect(keys(correct))) - @test correct == result + for case in test_cases + ode = linear_compartment_model( + case[:graph], + case[:inputs], + case[:outputs], + case[:leaks], + ) + bring = ode.poly_ring + correct = Dict{QQMPolyRingElem, Symbol}() + for (e, id) in case[:result] + correct[str_to_var("a_$(e[2])_$(e[1])", bring)] = id end + result = assess_identifiability(ode, funcs_to_check = collect(keys(correct))) + @test correct == result end end diff --git a/test/local_identifiability.jl b/test/local_identifiability.jl index 166284c81..0601baead 100644 --- a/test/local_identifiability.jl +++ b/test/local_identifiability.jl @@ -1,135 +1,133 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Assessing local identifiability" begin - test_cases = [] +@testset "Assessing local identifiability" begin + test_cases = [] - # 2-compartiment model + # 2-compartiment model - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [ - a01, - a21, - a12, - a01 * a12, - a01 + a12 + a21, - (a01 + a12 + a21) // (a01 * a12), - x0, - x1, - ] - correct = [false, false, false, true, true, true, true, false] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [ + a01, + a21, + a12, + a01 * a12, + a01 + a12 + a21, + (a01 + a12 + a21) // (a01 * a12), + x0, + x1, + ] + correct = [false, false, false, true, true, true, true, false] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), - x1'(t) = c * x1(t) + d * x0(t) * x1(t), - y(t) = x0(t) - ) - funcs_to_test = [a, b, c, d, x0, x1] - correct = [true, false, true, true, true, false] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + x0'(t) = a * x0(t) - b * x0(t) * x1(t) + u(t), + x1'(t) = c * x1(t) + d * x0(t) * x1(t), + y(t) = x0(t) + ) + funcs_to_test = [a, b, c, d, x0, x1] + correct = [true, false, true, true, true, false] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), - I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), - W'(t) = xi * (I(t) - W(t)), - R'(t) = gam * I(t) - (mu + a) * R(t), - y(t) = k * I(t) - ) - funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k, S, I, W, R] - correct = [true for _ in funcs_to_test] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => OrderedDict(funcs_to_test .=> correct), - ), - ) + ode = @ODEmodel( + S'(t) = mu - bi * S(t) * I(t) - bw * S(t) * W(t) - mu * S(t) + a * R(t), + I'(t) = bw * S(t) * W(t) + bi * S(t) * I(t) - (gam + mu) * I(t), + W'(t) = xi * (I(t) - W(t)), + R'(t) = gam * I(t) - (mu + a) * R(t), + y(t) = k * I(t) + ) + funcs_to_test = [mu, bi, bw, a, xi, gam, mu, gam + mu, k, S, I, W, R] + correct = [true for _ in funcs_to_test] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => OrderedDict(funcs_to_test .=> correct), + ), + ) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = -b * x1(t) + 1 / (c + x4(t)), - x2'(t) = alpha * x1(t) - beta * x2(t), - x3'(t) = gama * x2(t) - delta * x3(t), - x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), - y(t) = x1(t) - ) - funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] - correct = OrderedDict([ - b => true, - c => true, - alpha => false, - beta => true, - delta => true, - gama => false, - beta + delta => true, - beta * delta => true, - ]) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = -b * x1(t) + 1 / (c + x4(t)), + x2'(t) = alpha * x1(t) - beta * x2(t), + x3'(t) = gama * x2(t) - delta * x3(t), + x4'(t) = sigma * x4(t) * (gama * x2(t) - delta * x3(t)) / x3(t), + y(t) = x1(t) + ) + funcs_to_test = [b, c, alpha, beta, delta, gama, beta + delta, beta * delta] + correct = OrderedDict([ + b => true, + c => true, + alpha => false, + beta => true, + delta => true, + gama => false, + beta + delta => true, + beta * delta => true, + ]) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = a1 + a2 + a3 * a4, - x2'(t) = a4 * a5 + a6 + a7 - 8 * a8, - y(t) = x1(t) * x2(t) - ) - funcs_to_test = [a1, a2, a3, a4, a5, a6, a7, a8] - correct = OrderedDict(a => false for a in funcs_to_test) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = a1 + a2 + a3 * a4, + x2'(t) = a4 * a5 + a6 + a7 - 8 * a8, + y(t) = x1(t) * x2(t) + ) + funcs_to_test = [a1, a2, a3, a4, a5, a6, a7, a8] + correct = OrderedDict(a => false for a in funcs_to_test) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - ode = @ODEmodel( - x1'(t) = 0, - x2'(t) = 0, - x3'(t) = x3(t), - y(t) = (x1(t) + x2(t))^2, - y2(t) = x3(t) - ) - funcs_to_test = [x1, x2, x1 + x2] - correct = OrderedDict(x1 => false, x2 => false, x1 + x2 => true) - push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) + ode = @ODEmodel( + x1'(t) = 0, + x2'(t) = 0, + x3'(t) = x3(t), + y(t) = (x1(t) + x2(t))^2, + y2(t) = x3(t) + ) + funcs_to_test = [x1, x2, x1 + x2] + correct = OrderedDict(x1 => false, x2 => false, x1 + x2 => true) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => correct)) - #-------------------------------------------------------------------------- + #-------------------------------------------------------------------------- - for case in test_cases - trbasis = [] - ode = case[:ode] - result = assess_local_identifiability( - ode, - funcs_to_check = case[:funcs], - trbasis = trbasis, - ) - @test result == case[:correct] - for (i, p) in enumerate(trbasis) - res_for_p = assess_local_identifiability(ode, funcs_to_check = [p]) - @test !first(values(res_for_p)) - ode = add_outputs(ode, Dict("YYY$i" => p)) - end - @test all(values(assess_local_identifiability(ode))) + for case in test_cases + trbasis = [] + ode = case[:ode] + result = assess_local_identifiability( + ode, + funcs_to_check = case[:funcs], + trbasis = trbasis, + ) + @test result == case[:correct] + for (i, p) in enumerate(trbasis) + res_for_p = assess_local_identifiability(ode, funcs_to_check = [p]) + @test !first(values(res_for_p)) + ode = add_outputs(ode, Dict("YYY$i" => p)) end + @test all(values(assess_local_identifiability(ode))) end end diff --git a/test/local_identifiability_discrete.jl b/test/local_identifiability_discrete.jl index 9e6936844..5ae18f4f3 100644 --- a/test/local_identifiability_discrete.jl +++ b/test/local_identifiability_discrete.jl @@ -1,65 +1,63 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Discrete local identifiability, @DDSmodel interface" begin - cases = [] - - dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) - - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :none, - ), - ) - - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :all, +@testset "Discrete local identifiability, @DDSmodel interface" begin + cases = [] + + dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :none, + ), + ) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :all, + ), + ) + + #--------------------- + + cases = [] + + dds = @DDSmodel(x1(t + 1) = a * x1(t) * x2(t), x2(t + 1) = b * x1(t), y(t) = x2(t)) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict( + a => true, + b => false, + x1 => false, + x2 => true, + b * x1 => true, ), - ) - - #--------------------- - - cases = [] - - dds = @DDSmodel(x1(t + 1) = a * x1(t) * x2(t), x2(t + 1) = b * x1(t), y(t) = x2(t)) - - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict( - a => true, - b => false, - x1 => false, - x2 => true, - b * x1 => true, - ), - :known => :none, - ), - ) - - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => true, x1 => true, x2 => true), - :known => :all, - ), - ) - - #--------------------- - - for c in cases - @test assess_local_identifiability( - c[:dds]; - funcs_to_check = collect(keys(c[:res])), - known_ic = c[:known], - ) == c[:res] - end + :known => :none, + ), + ) + + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => true, x1 => true, x2 => true), + :known => :all, + ), + ) + + #--------------------- + + for c in cases + @test assess_local_identifiability( + c[:dds]; + funcs_to_check = collect(keys(c[:res])), + known_ic = c[:known], + ) == c[:res] end end diff --git a/test/local_identifiability_discrete_aux.jl b/test/local_identifiability_discrete_aux.jl index 0c41ad9d3..ac7852be6 100644 --- a/test/local_identifiability_discrete_aux.jl +++ b/test/local_identifiability_discrete_aux.jl @@ -1,87 +1,85 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Discrete local identifiability, internal function" begin - cases = [] +@testset "Discrete local identifiability, internal function" begin + cases = [] - dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) + dds = @DDSmodel(a(t + 1) = (b + c) * a(t) + 1, y(t) = a(t)) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :none, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :none, + ), + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(a => true, b => false, c => false, b + c => true), - :known => :all, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(a => true, b => false, c => false, b + c => true), + :known => :all, + ), + ) - #--------------------- + #--------------------- - dds = @DDSmodel(a(t + 1) = b(t) * a(t) + c, b(t + 1) = d * a(t), y(t) = b(t)) + dds = @DDSmodel(a(t + 1) = b(t) * a(t) + c, b(t + 1) = d * a(t), y(t) = b(t)) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict( - b => true, - a => false, - c => false, - d => false, - d * a => true, - d * c => true, - ), - :known => :none, + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict( + b => true, + a => false, + c => false, + d => false, + d * a => true, + d * c => true, ), - ) + :known => :none, + ), + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(b => true, a => true, c => true, d => true), - :known => [a], - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(b => true, a => true, c => true, d => true), + :known => [a], + ), + ) - push!( - cases, - Dict( - :dds => dds, - :res => OrderedDict(b => true, a => true, c => true, d => true), - :known => :all, - ), - ) + push!( + cases, + Dict( + :dds => dds, + :res => OrderedDict(b => true, a => true, c => true, d => true), + :known => :all, + ), + ) - # ------------------- + # ------------------- - # Example 4 from https://doi.org/10.1016/j.automatica.2016.01.054 - dds = @DDSmodel(x(t + 1) = theta^3 * x(t), y(t) = x(t)) + # Example 4 from https://doi.org/10.1016/j.automatica.2016.01.054 + dds = @DDSmodel(x(t + 1) = theta^3 * x(t), y(t) = x(t)) - push!( - cases, - OrderedDict( - :dds => dds, - :res => Dict(theta => true, x => true), - :known => :none, - ), - ) + push!( + cases, + OrderedDict( + :dds => dds, + :res => Dict(theta => true, x => true), + :known => :none, + ), + ) - # ------------------- + # ------------------- - for c in cases - @test _assess_local_identifiability_discrete_aux( - c[:dds], - collect(keys(c[:res])), - c[:known], - ) == c[:res] - end + for c in cases + @test _assess_local_identifiability_discrete_aux( + c[:dds], + collect(keys(c[:res])), + c[:known], + ) == c[:res] end end diff --git a/test/local_identifiability_me.jl b/test/local_identifiability_me.jl index e14eb2f46..4f866ef21 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -1,233 +1,231 @@ -if GROUP == "All" || GROUP == "Core" - # Copyright (c) 2021, R. Dong, C. Goodbarke, H. Harrington, G. Pogudin - # Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon - - # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - - # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - function _linear_compartment_model(graph, sinks) - """ - Input: - - graph - graph of the model network represented via adjacency lists - - sinks - the indices of nodes having a sink - Output: the corresponding ODE object where each parameter a_ij is replaced - with a_ij + b_ij * x_0, where x_0 is a constant input encoded as a constant output - """ - n = length(graph) - x_vars_names = ["x$i" for i in 0:n] - edges_vars_names = Array{String, 1}() - for i in 1:n - for j in graph[i] - push!(edges_vars_names, "a_$(i)_$(j)") - push!(edges_vars_names, "b_$(i)_$(j)") - end - end - for s in sinks - push!(edges_vars_names, "a_$(s)_0") - push!(edges_vars_names, "b_$(s)_0") +# Copyright (c) 2021, R. Dong, C. Goodbarke, H. Harrington, G. Pogudin +# Copyright (c) 2020, A. Ovchinnikov, A. Pillay, G. Pogudin, T. Scanlon + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +function _linear_compartment_model(graph, sinks) + """ + Input: + - graph - graph of the model network represented via adjacency lists + - sinks - the indices of nodes having a sink + Output: the corresponding ODE object where each parameter a_ij is replaced + with a_ij + b_ij * x_0, where x_0 is a constant input encoded as a constant output + """ + n = length(graph) + x_vars_names = ["x$i" for i in 0:n] + edges_vars_names = Array{String, 1}() + for i in 1:n + for j in graph[i] + push!(edges_vars_names, "a_$(i)_$(j)") + push!(edges_vars_names, "b_$(i)_$(j)") end - R, vars = Nemo.polynomial_ring( - Nemo.QQ, - vcat(x_vars_names, edges_vars_names, ["y1", "y2"]), + end + for s in sinks + push!(edges_vars_names, "a_$(s)_0") + push!(edges_vars_names, "b_$(s)_0") + end + R, vars = Nemo.polynomial_ring( + Nemo.QQ, + vcat(x_vars_names, edges_vars_names, ["y1", "y2"]), + ) + x_vars = vars[2:(n + 1)] + x0 = vars[1] + equations = + Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}}( + x => R(0) for x in x_vars ) - x_vars = vars[2:(n + 1)] - x0 = vars[1] - equations = - Dict{QQMPolyRingElem, Union{QQMPolyRingElem, Generic.Frac{QQMPolyRingElem}}}( - x => R(0) for x in x_vars - ) - equations[x0] = R(0) - for i in 1:n - for j in graph[i] - rate = str_to_var("a_$(i)_$(j)", R) + str_to_var("b_$(i)_$(j)", R) * x0 - - if i != j - equations[x_vars[j]] += x_vars[i] * rate - equations[x_vars[i]] -= x_vars[i] * rate - else - equations[x_vars[i]] -= x_vars[i] * rate - end - end - if i in sinks - rate = str_to_var("a_$(i)_0", R) + str_to_var("b_$(i)_0", R) * x0 - equations[x_vars[i]] += -x_vars[i] * rate + equations[x0] = R(0) + for i in 1:n + for j in graph[i] + rate = str_to_var("a_$(i)_$(j)", R) + str_to_var("b_$(i)_$(j)", R) * x0 + + if i != j + equations[x_vars[j]] += x_vars[i] * rate + equations[x_vars[i]] -= x_vars[i] * rate + else + equations[x_vars[i]] -= x_vars[i] * rate end end - return ODE{QQMPolyRingElem}( - equations, - Dict(vars[end] => x_vars[1], vars[end - 1] => x0), - Array{QQMPolyRingElem, 1}(), - ) + if i in sinks + rate = str_to_var("a_$(i)_0", R) + str_to_var("b_$(i)_0", R) * x0 + equations[x_vars[i]] += -x_vars[i] * rate + end end + return ODE{QQMPolyRingElem}( + equations, + Dict(vars[end] => x_vars[1], vars[end - 1] => x0), + Array{QQMPolyRingElem, 1}(), + ) +end - #------------------------------------------------------------------------------ - - function bicycle(n) - """ - Generates a bidirected cycle of length n - """ - graph = [] - for i in 1:n - prev = (i == 1) ? n : (i - 1) - next = (i == n) ? 1 : i + 1 - push!(graph, [prev, next]) - end - return graph +#------------------------------------------------------------------------------ + +function bicycle(n) + """ + Generates a bidirected cycle of length n + """ + graph = [] + for i in 1:n + prev = (i == 1) ? n : (i - 1) + next = (i == n) ? 1 : i + 1 + push!(graph, [prev, next]) end + return graph +end - #------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ - function cycle(n) - """ - Single directed cycle - """ - graph = [[(i == n) ? 1 : (i + 1)] for i in 1:n] - return graph - end +function cycle(n) + """ + Single directed cycle + """ + graph = [[(i == n) ? 1 : (i + 1)] for i in 1:n] + return graph +end - #------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ - function catenary(n) - """ - Bidirected chain from 1 to n - """ - graph = [[] for i in 1:n] - for i in 1:n - if i != 1 - push!(graph[i], i - 1) - end - if i != n - push!(graph[i], i + 1) - end +function catenary(n) + """ + Bidirected chain from 1 to n + """ + graph = [[] for i in 1:n] + for i in 1:n + if i != 1 + push!(graph[i], i - 1) + end + if i != n + push!(graph[i], i + 1) end - return graph end + return graph +end - #------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ - function mammilary(n) - """ - Bidirected 'star' with center at 1 and rays to 2, ..., n - """ - graph = [] - push!(graph, [i for i in 2:n]) - for i in 2:n - push!(graph, [1]) - end - return graph +function mammilary(n) + """ + Bidirected 'star' with center at 1 and rays to 2, ..., n + """ + graph = [] + push!(graph, [i for i in 2:n]) + for i in 2:n + push!(graph, [1]) end + return graph +end - #------------------------------------------------------------------------------ - - ############################################################################### - - @testset "Assessing local identifiability (multiexperiment)" begin - - # checking bounds - - test_cases = [ - Dict(:name => "Cyclic", :graph => cycle, :bound => [3]), - Dict(:name => "Catenary", :graph => catenary, :bound => [4, 5]), - Dict(:name => "Mammilary", :graph => mammilary, :bound => [4, 5]), - ] - - n_min = 3 - n_max = 8 - for case in test_cases - for n in n_min:n_max - model = _linear_compartment_model(case[:graph](n), [1]) - println(case[:name] * ", n = $n") - @time result = assess_local_identifiability(model, type = :ME) - correct = undef - if n - n_min + 1 > length(case[:bound]) - correct = case[:bound][end] - else - correct = case[:bound][n - n_min + 1] - end - @test correct == result[2] - end - end - - # checking bounds and results +#------------------------------------------------------------------------------ - test_cases = [] +############################################################################### - ode = @ODEmodel( - x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), - x1'(t) = a21 * x0(t) - a12 * x1(t), - y(t) = x0(t) - ) - funcs_to_test = - [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] - correct = [false, false, false, true, true, true] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => (OrderedDict(funcs_to_test .=> correct), 1), - ), - ) +@testset "Assessing local identifiability (multiexperiment)" begin - #-------------------------------------------------------------------------- - - # Example 7.7 from https://arxiv.org/pdf/2011.10868.pdf - ode = - @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) - funcs_to_test = [mu1, mu2] - correct = [true, true] - push!( - test_cases, - Dict( - :ode => ode, - :funcs => funcs_to_test, - :correct => (OrderedDict(funcs_to_test .=> correct), 2), - ), - ) + # checking bounds - #-------------------------------------------------------------------------- - - # Example 7.8 from https://arxiv.org/pdf/2011.10868.pdf - ode = @ODEmodel( - S'(t) = -b * S(t) * I(t) / N, - E'(t) = b * S(t) * I(t) / N - nu * E(t), - I'(t) = nu * E(t) - a * I(t), - c'(t) = 0, - y1(t) = c(t) * I(t) + d * E(t), - y2(t) = c(t), - y3(t) = N - ) - funcs_to_test = [b, nu, d, a] - correct = OrderedDict([b => true, nu => true, d => true, a => true]) - push!( - test_cases, - Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1)), - ) + test_cases = [ + Dict(:name => "Cyclic", :graph => cycle, :bound => [3]), + Dict(:name => "Catenary", :graph => catenary, :bound => [4, 5]), + Dict(:name => "Mammilary", :graph => mammilary, :bound => [4, 5]), + ] - #-------------------------------------------------------------------------- + n_min = 3 + n_max = 8 + for case in test_cases + for n in n_min:n_max + model = _linear_compartment_model(case[:graph](n), [1]) + println(case[:name] * ", n = $n") + @time result = assess_local_identifiability(model, type = :ME) + correct = undef + if n - n_min + 1 > length(case[:bound]) + correct = case[:bound][end] + else + correct = case[:bound][n - n_min + 1] + end + @test correct == result[2] + end + 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 = OrderedDict([a => false]) - push!( - test_cases, - Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0)), + # checking bounds and results + + test_cases = [] + + ode = @ODEmodel( + x0'(t) = -(a01 + a21) * x0(t) + a12 * x1(t), + x1'(t) = a21 * x0(t) - a12 * x1(t), + y(t) = x0(t) + ) + funcs_to_test = + [a01, a21, a12, a01 * a12, a01 + a12 + a21, (a01 + a12 + a21) // (a01 * a12)] + correct = [false, false, false, true, true, true] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => (OrderedDict(funcs_to_test .=> correct), 1), + ), + ) + + #-------------------------------------------------------------------------- + + # Example 7.7 from https://arxiv.org/pdf/2011.10868.pdf + ode = + @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) + funcs_to_test = [mu1, mu2] + correct = [true, true] + push!( + test_cases, + Dict( + :ode => ode, + :funcs => funcs_to_test, + :correct => (OrderedDict(funcs_to_test .=> correct), 2), + ), + ) + + #-------------------------------------------------------------------------- + + # Example 7.8 from https://arxiv.org/pdf/2011.10868.pdf + ode = @ODEmodel( + S'(t) = -b * S(t) * I(t) / N, + E'(t) = b * S(t) * I(t) / N - nu * E(t), + I'(t) = nu * E(t) - a * I(t), + c'(t) = 0, + y1(t) = c(t) * I(t) + d * E(t), + y2(t) = c(t), + y3(t) = N + ) + funcs_to_test = [b, nu, d, a] + correct = OrderedDict([b => true, nu => true, d => true, a => true]) + push!( + test_cases, + Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1)), + ) + + #-------------------------------------------------------------------------- + + # 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 = OrderedDict([a => false]) + push!( + test_cases, + Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0)), + ) + + #-------------------------------------------------------------------------- + + for case in test_cases + result = assess_local_identifiability( + case[:ode], + funcs_to_check = case[:funcs], + prob_threshold = 0.932, + type = :ME, ) - - #-------------------------------------------------------------------------- - - for case in test_cases - result = assess_local_identifiability( - case[:ode], - funcs_to_check = case[:funcs], - prob_threshold = 0.932, - type = :ME, - ) - @test result == case[:correct] - end + @test result == case[:correct] end end diff --git a/test/logging.jl b/test/logging.jl index 054a45960..b4c5e9fb0 100644 --- a/test/logging.jl +++ b/test/logging.jl @@ -1,21 +1,19 @@ -if GROUP == "All" || GROUP == "Core" - # An attempt to test logging - using Logging +# An attempt to test logging +using Logging - @testset "Logging" begin - ode = StructuralIdentifiability.@ODEmodel(x'(t) = x^42, y(t) = x) +@testset "Logging" begin + ode = StructuralIdentifiability.@ODEmodel(x'(t) = x^42, y(t) = x) - # Some logs - @test_logs StructuralIdentifiability.assess_identifiability(ode) - # No logs - @test_logs min_level = Logging.Warn StructuralIdentifiability.assess_identifiability( - ode, - loglevel = Logging.Warn, - ) - # Many logs - @test_logs StructuralIdentifiability.assess_identifiability( - ode, - loglevel = Logging.Debug, - ) - end + # Some logs + @test_logs StructuralIdentifiability.assess_identifiability(ode) + # No logs + @test_logs min_level = Logging.Warn StructuralIdentifiability.assess_identifiability( + ode, + loglevel = Logging.Warn, + ) + # Many logs + @test_logs StructuralIdentifiability.assess_identifiability( + ode, + loglevel = Logging.Debug, + ) end diff --git a/test/monomial_compress.jl b/test/monomial_compress.jl index 2eded4cc4..697d1525c 100644 --- a/test/monomial_compress.jl +++ b/test/monomial_compress.jl @@ -1,20 +1,18 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Monomial compression test" begin - R, (v1, v2, v3, v4) = Nemo.polynomial_ring(Nemo.QQ, ["v1", "v2", "v3", "v4"]) - tests = [ - v1 + v2 - 2, - v4 + v4 * v1 - 3 * v2 * v4, - (v1 + v2 + v2^2) * (v3 + v4), - (v1 + v2^2) * (v3^3 + v4) - 7 * (v1 - 3 - v2) * (v3 - v4^2 - v3^2), - ] +@testset "Monomial compression test" begin + R, (v1, v2, v3, v4) = Nemo.polynomial_ring(Nemo.QQ, ["v1", "v2", "v3", "v4"]) + tests = [ + v1 + v2 - 2, + v4 + v4 * v1 - 3 * v2 * v4, + (v1 + v2 + v2^2) * (v3 + v4), + (v1 + v2^2) * (v3^3 + v4) - 7 * (v1 - 3 - v2) * (v3 - v4^2 - v3^2), + ] - for t in tests - a, b = StructuralIdentifiability.monomial_compress(t, [v1, v2]) - s = 0 - for (x, y) in zip(a, b) - s += parent_ring_change(x, parent(t)) * parent_ring_change(y, parent(t)) - end - @test s == t + for t in tests + a, b = StructuralIdentifiability.monomial_compress(t, [v1, v2]) + s = 0 + for (x, y) in zip(a, b) + s += parent_ring_change(x, parent(t)) * parent_ring_change(y, parent(t)) end + @test s == t end end diff --git a/test/ode.jl b/test/ode.jl index 96e40ec5b..f8035a7ee 100644 --- a/test/ode.jl +++ b/test/ode.jl @@ -1,20 +1,18 @@ -if GROUP == "All" || GROUP == "Core" - @testset "ODE struct" begin - # adding outputs - ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t) + a) - ode = StructuralIdentifiability.add_outputs(ode, Dict("y" => a * x)) - @test StructuralIdentifiability.AbstractAlgebra.nvars(parent(ode)) == 3 +@testset "ODE struct" begin + # adding outputs + ode = StructuralIdentifiability.@ODEmodel(x'(t) = x(t) + a) + ode = StructuralIdentifiability.add_outputs(ode, Dict("y" => a * x)) + @test StructuralIdentifiability.AbstractAlgebra.nvars(parent(ode)) == 3 - # two or more equations on the same variable - @test_throws DomainError StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + a, - x1'(t) = x1(t) + b, - y(t) = x1 - ) - @test_throws DomainError StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t), - y(t) = x1, - y(t) = x1 - ) - end + # two or more equations on the same variable + @test_throws DomainError StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + a, + x1'(t) = x1(t) + b, + y(t) = x1 + ) + @test_throws DomainError StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t), + y(t) = x1, + y(t) = x1 + ) end diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index 1a072a7f5..ff256b275 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -1,104 +1,102 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Power series solution for an ODE system" begin - R, (x, x_dot) = Nemo.polynomial_ring(Nemo.QQ, ["x", "x_dot"]) - exp_t = ps_ode_solution( - [x_dot - x], - Dict{QQMPolyRingElem, QQFieldElem}(x => 1), - Dict{QQMPolyRingElem, Array{QQFieldElem, 1}}(), - 20, - )[x] - @test valuation(ps_diff(exp_t) - exp_t) == 19 +@testset "Power series solution for an ODE system" begin + R, (x, x_dot) = Nemo.polynomial_ring(Nemo.QQ, ["x", "x_dot"]) + exp_t = ps_ode_solution( + [x_dot - x], + Dict{QQMPolyRingElem, QQFieldElem}(x => 1), + Dict{QQMPolyRingElem, Array{QQFieldElem, 1}}(), + 20, + )[x] + @test valuation(ps_diff(exp_t) - exp_t) == 19 - R, (x, y, x_dot, y_dot, u) = - Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) - prec = 100 - eqs = [x_dot - x + 3 * x * y - u, y_dot + 2 * y - 4 * x * y] - u_coeff = [rand(1:5) for i in 1:prec] - sol = ps_ode_solution(eqs, Dict(x => 0, y => -2), Dict(u => u_coeff), prec) - @test map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) == [prec - 1, prec - 1] + R, (x, y, x_dot, y_dot, u) = + Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "x_dot", "y_dot", "u"]) + prec = 100 + eqs = [x_dot - x + 3 * x * y - u, y_dot + 2 * y - 4 * x * y] + u_coeff = [rand(1:5) for i in 1:prec] + sol = ps_ode_solution(eqs, Dict(x => 0, y => -2), Dict(u => u_coeff), prec) + @test map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) == [prec - 1, prec - 1] - F = Nemo.Native.GF(2^31 - 1) - prec = 100 + F = Nemo.Native.GF(2^31 - 1) + prec = 100 - # Testing the function ps_ode_solution by itself - for i in 1:30 - # Setting up the ring - NUMX = 5 - NUMU = 3 - varnames = vcat( - ["x_$(i)_dot" for i in 1:NUMX], - ["x_$i" for i in 1:NUMX], - ["u_$i" for i in 1:NUMU], - ) - R, vars = Nemo.polynomial_ring(F, varnames) + # Testing the function ps_ode_solution by itself + for i in 1:30 + # Setting up the ring + NUMX = 5 + NUMU = 3 + varnames = vcat( + ["x_$(i)_dot" for i in 1:NUMX], + ["x_$i" for i in 1:NUMX], + ["u_$i" for i in 1:NUMU], + ) + R, vars = Nemo.polynomial_ring(F, varnames) - # Generating the initial conditions and inputs - ic = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMX) - inputs = - Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(2 * NUMX + 1):end]) - # a dictionary for evaluation at zero (to avoid singularities) - eval_at_zero = merge(ic, Dict(u => val[1] for (u, val) in inputs)) + # Generating the initial conditions and inputs + ic = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMX) + inputs = + Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(2 * NUMX + 1):end]) + # a dictionary for evaluation at zero (to avoid singularities) + eval_at_zero = merge(ic, Dict(u => val[1] for (u, val) in inputs)) - # Generating denominators not vanishing at t = 0 + # Generating denominators not vanishing at t = 0 + denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] + while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] - while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) - denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] - end + end - eqs = [ - denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for - i in 1:NUMX - ] - @time sol = ps_ode_solution(eqs, ic, inputs, prec) - evals = map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) - for e in evals - @test e >= prec - 1 - end + eqs = [ + denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for + i in 1:NUMX + ] + @time sol = ps_ode_solution(eqs, ic, inputs, prec) + evals = map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) + for e in evals + @test e >= prec - 1 end + end - # Testing ps_ode_solution in conjuntion with the ODE class - for i in 1:30 - # Setting up the ring - NUMX = 3 - NUMP = 3 - NUMU = 2 - varnames = vcat( - ["x_$i" for i in 1:NUMX], - ["p_$i" for i in 1:NUMP], - ["u_$i" for i in 1:NUMU], - ) - R, vars = Nemo.polynomial_ring(F, varnames) - PType = fpMPolyRingElem - TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} + # Testing ps_ode_solution in conjuntion with the ODE class + for i in 1:30 + # Setting up the ring + NUMX = 3 + NUMP = 3 + NUMU = 2 + varnames = vcat( + ["x_$i" for i in 1:NUMX], + ["p_$i" for i in 1:NUMP], + ["u_$i" for i in 1:NUMU], + ) + R, vars = Nemo.polynomial_ring(F, varnames) + PType = fpMPolyRingElem + TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} - # Generating the intial conditions etc - ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) - param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) - inputs = Dict( - u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end] - ) - eval_at_zero = merge(ic, param_vals, Dict(u => val[1] for (u, val) in inputs)) + # Generating the intial conditions etc + ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) + param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) + inputs = Dict( + u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end] + ) + eval_at_zero = merge(ic, param_vals, Dict(u => val[1] for (u, val) in inputs)) - # Generating denominators not vanishing at t = 0 + # Generating denominators not vanishing at t = 0 + denominators = [rand_poly(1, vars) for i in 1:NUMX] + while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) denominators = [rand_poly(1, vars) for i in 1:NUMX] - while any([eval_at_dict(p, eval_at_zero) == 0 for p in denominators]) - denominators = [rand_poly(1, vars) for i in 1:NUMX] - end + end - eqs = TDict(vars[i] => rand_poly(2, vars) // denominators[i] for i in 1:NUMX) - ode = ODE{PType}(eqs, TDict(), vars[(NUMX + NUMP + 1):end]) - @time sol = power_series_solution(ode, param_vals, ic, inputs, prec) - ps_ring = parent(collect(values(sol))[1]) - for (p, val) in param_vals - sol[p] = ps_ring(val) - end - eval_point = [sol[v] for v in vars] - for (xd, f) in eqs - num, den = unpack_fraction(f) - lhs = divexact(evaluate(num, eval_point), evaluate(den, eval_point)) - rhs = ps_diff(sol[xd]) - @test valuation(lhs - rhs) >= prec - 1 - end + eqs = TDict(vars[i] => rand_poly(2, vars) // denominators[i] for i in 1:NUMX) + ode = ODE{PType}(eqs, TDict(), vars[(NUMX + NUMP + 1):end]) + @time sol = power_series_solution(ode, param_vals, ic, inputs, prec) + ps_ring = parent(collect(values(sol))[1]) + for (p, val) in param_vals + sol[p] = ps_ring(val) + end + eval_point = [sol[v] for v in vars] + for (xd, f) in eqs + num, den = unpack_fraction(f) + lhs = divexact(evaluate(num, eval_point), evaluate(den, eval_point)) + rhs = ps_diff(sol[xd]) + @test valuation(lhs - rhs) >= prec - 1 end end end diff --git a/test/paradigm_shift.jl b/test/paradigm_shift.jl index 6a76f2e06..553e92312 100644 --- a/test/paradigm_shift.jl +++ b/test/paradigm_shift.jl @@ -1,271 +1,269 @@ -if GROUP == "All" || GROUP == "Core" - ≡(A, B) = A ⊆ B && B ⊆ A +≡(A, B) = A ⊆ B && B ⊆ A - function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relations) - # NOTE: should this be strengthened to == ? - # @test map(string, old_ode.u_vars) ≡ map(string, new_ode.u_vars) - # @test map(string, old_ode.y_vars) ≡ map(string, new_ode.y_vars) - @test length(old_ode.y_vars) == length(new_ode.y_vars) - @test length(old_ode.u_vars) == length(new_ode.u_vars) - @test base_ring(parent(first(values(var_mapping)))) == parent(old_ode) +function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relations) + # NOTE: should this be strengthened to == ? + # @test map(string, old_ode.u_vars) ≡ map(string, new_ode.u_vars) + # @test map(string, old_ode.y_vars) ≡ map(string, new_ode.y_vars) + @test length(old_ode.y_vars) == length(new_ode.y_vars) + @test length(old_ode.u_vars) == length(new_ode.u_vars) + @test base_ring(parent(first(values(var_mapping)))) == parent(old_ode) - # We check that the PS solutions of the new_ode coincide with the solutions - # of the old_ode projected onto the new variables - ord = 5 - # NOTE: there may be an unlucky specialization point, and newton iteration - # will fail, and tests will fail as a result - bound = 100 - old_params = old_ode.parameters - old_vars = old_ode.x_vars - old_inputs = old_ode.u_vars - param_spec_point = map(Nemo.QQ, rand(1:bound, length(old_params))) - old_param_spec = Dict(old_params .=> param_spec_point) - ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) - old_var_ic = Dict(old_vars .=> ic_point) - input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) - old_input_ts = - Dict{eltype(old_vars), Vector{Nemo.QQFieldElem}}(old_inputs .=> input_ts) + # We check that the PS solutions of the new_ode coincide with the solutions + # of the old_ode projected onto the new variables + ord = 5 + # NOTE: there may be an unlucky specialization point, and newton iteration + # will fail, and tests will fail as a result + bound = 100 + old_params = old_ode.parameters + old_vars = old_ode.x_vars + old_inputs = old_ode.u_vars + param_spec_point = map(Nemo.QQ, rand(1:bound, length(old_params))) + old_param_spec = Dict(old_params .=> param_spec_point) + ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) + old_var_ic = Dict(old_vars .=> ic_point) + input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) + old_input_ts = + Dict{eltype(old_vars), Vector{Nemo.QQFieldElem}}(old_inputs .=> input_ts) - old_solutions = StructuralIdentifiability.power_series_solution( - old_ode, - old_param_spec, - old_var_ic, - old_input_ts, - ord, - ) - - new_params = new_ode.parameters - new_vars = new_ode.x_vars - new_inputs = new_ode.u_vars - new_param_spec = Dict( - new_param => StructuralIdentifiability.eval_at_dict( - var_mapping[new_param], - old_param_spec, - ) for new_param in new_params - ) - new_var_ic = Dict( - new_var => StructuralIdentifiability.eval_at_dict( - var_mapping[new_var], - merge(old_param_spec, old_var_ic), - ) for new_var in new_vars - ) - new_input_ts = Dict{eltype(new_vars), Vector{Nemo.QQFieldElem}}( - new_input => old_input_ts[numerator(var_mapping[new_input])] for - new_input in new_inputs - ) + old_solutions = StructuralIdentifiability.power_series_solution( + old_ode, + old_param_spec, + old_var_ic, + old_input_ts, + ord, + ) - new_solutions = StructuralIdentifiability.power_series_solution( - new_ode, - new_param_spec, - new_var_ic, - new_input_ts, - ord, - ) + new_params = new_ode.parameters + new_vars = new_ode.x_vars + new_inputs = new_ode.u_vars + new_param_spec = Dict( + new_param => StructuralIdentifiability.eval_at_dict( + var_mapping[new_param], + old_param_spec, + ) for new_param in new_params + ) + new_var_ic = Dict( + new_var => StructuralIdentifiability.eval_at_dict( + var_mapping[new_var], + merge(old_param_spec, old_var_ic), + ) for new_var in new_vars + ) + new_input_ts = Dict{eltype(new_vars), Vector{Nemo.QQFieldElem}}( + new_input => old_input_ts[numerator(var_mapping[new_input])] for + new_input in new_inputs + ) - # NOTE: test that y's are mapped one to one! - @test map(string, [var_mapping[output] for output in new_ode.y_vars]) ≡ map(string, old_ode.y_vars) + new_solutions = StructuralIdentifiability.power_series_solution( + new_ode, + new_param_spec, + new_var_ic, + new_input_ts, + ord, + ) - # Test IO behavior - for var in vcat(new_ode.y_vars) - new_solution = new_solutions[var] - old_solution = old_solutions[numerator(var_mapping[var])] - @test new_solution == old_solution - end + # NOTE: test that y's are mapped one to one! + @test map(string, [var_mapping[output] for output in new_ode.y_vars]) ≡ map(string, old_ode.y_vars) - # Test state dynamics - projected_solutions = Dict( - var => - StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) - for var in new_ode.x_vars - ) - for var in vcat(new_ode.x_vars) - new_solution = new_solutions[var] - prj_solution = projected_solutions[var] - @test new_solution == prj_solution - end + # Test IO behavior + for var in vcat(new_ode.y_vars) + new_solution = new_solutions[var] + old_solution = old_solutions[numerator(var_mapping[var])] + @test new_solution == old_solution + end - # Test relations - for relation in implicit_relations - @test iszero(StructuralIdentifiability.eval_at_dict(relation, var_mapping)) - end + # Test state dynamics + projected_solutions = Dict( + var => + StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) + for var in new_ode.x_vars + ) + for var in vcat(new_ode.x_vars) + new_solution = new_solutions[var] + prj_solution = projected_solutions[var] + @test new_solution == prj_solution + end - return nothing + # Test relations + for relation in implicit_relations + @test iszero(StructuralIdentifiability.eval_at_dict(relation, var_mapping)) end - cases = [ - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + a + u1(t), - x2'(t) = x2(t) + b + u2(t), - y1(t) = x1(t), - y2(t) = x2(t), - ), + return nothing +end + +cases = [ + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + a + u1(t), + x2'(t) = x2(t) + b + u2(t), + y1(t) = x1(t), + y2(t) = x2(t), ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1, - x2'(t) = b * x2, - y(t) = x1 + x2 - ), + ), + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1, + x2'(t) = b * x2, + y(t) = x1 + x2 ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = (a + b) * x1 // x2 + a, - x2'(t) = x1 // (a + c) + c, - y(t) = x1 - ), + ), + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = (a + b) * x1 // x2 + a, + x2'(t) = x1 // (a + c) + c, + y(t) = x1 ), - ( - ode = StructuralIdentifiability.@ODEmodel( - T1'(t) = a * T1 - b * T1 * T2 + u(t), - T2'(t) = -c * T2 + d * T1 * T2, - y(t) = T1 - ), + ), + ( + ode = StructuralIdentifiability.@ODEmodel( + T1'(t) = a * T1 - b * T1 * T2 + u(t), + T2'(t) = -c * T2 + d * T1 * T2, + y(t) = T1 ), - ( - #= - Identifiable functions: - x1, Θ * x2 + ), + ( + #= + Identifiable functions: + x1, Θ * x2 - Under - T1 = x1, - T2 = Θ * x2 + Under + T1 = x1, + T2 = Θ * x2 - Reparametrizes into: - T1' = T1 + T2, - T2' = 0, - y = T1 - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1(t) + Θ * x2(t), - x2'(t) = 0, - y(t) = x1(t) - ), + Reparametrizes into: + T1' = T1 + T2, + T2' = 0, + y = T1 + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1(t) + Θ * x2(t), + x2'(t) = 0, + y(t) = x1(t) ), - ( - #= - Identifiable functions: - x1, α * x2, α * x3, α * C + ), + ( + #= + Identifiable functions: + x1, α * x2, α * x3, α * C - Under - T1 = α * x2, - T2 = α * x3, - T3 = x1, - T4 = α * C + Under + T1 = α * x2, + T2 = α * x3, + T3 = x1, + T4 = α * C - Reparametrizes into: - T3'(t) = T1(t) - T2'(t) = T4 - T1'(t) = T2(t) - y(t) = T3(t) - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * x2(t), - x2'(t) = x3(t), - x3'(t) = C, - y(t) = x1(t) - ), + Reparametrizes into: + T3'(t) = T1(t) + T2'(t) = T4 + T1'(t) = T2(t) + y(t) = T3(t) + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * x2(t), + x2'(t) = x3(t), + x3'(t) = C, + y(t) = x1(t) ), - ( - #= - Identifiable functions: - x1^2 + x2^2, α + ), + ( + #= + Identifiable functions: + x1^2 + x2^2, α - Under - T1 = x1^2 + x2^2, - T2 = α + Under + T1 = x1^2 + x2^2, + T2 = α - Reparametrizes into: - T1' = 2*T1*T2 - y = 1//2*T1 - =# - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = α * (x1 - x2), - x2'(t) = α * (x1 + x2), - y(t) = (x1^2 + x2^2) // 2, - ), + Reparametrizes into: + T1' = 2*T1*T2 + y = 1//2*T1 + =# + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = α * (x1 - x2), + x2'(t) = α * (x1 + x2), + y(t) = (x1^2 + x2^2) // 2, ), - ( - # Pivastatin - # Reparametrizes into a 4-dimensional nonlinear model with no algebraic - # relations -- how is this even legal?? - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = - k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), - x2'(t) = k1 * x1(t) * (T0 - x2(t)) - (r1 + k2) * x2(t), - x3'(t) = r3 * x1(t) - (k3 + k4) * x3(t) + k2 * x2(t), - y1(t) = k * (x2(t) + x3(t)) - ), + ), + ( + # Pivastatin + # Reparametrizes into a 4-dimensional nonlinear model with no algebraic + # relations -- how is this even legal?? + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = + k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), + x2'(t) = k1 * x1(t) * (T0 - x2(t)) - (r1 + k2) * x2(t), + x3'(t) = r3 * x1(t) - (k3 + k4) * x3(t) + k2 * x2(t), + y1(t) = k * (x2(t) + x3(t)) ), - ( - # Transfection_4State - ode = StructuralIdentifiability.@ODEmodel( - mRNA'(t) = -d1 * mRNA(t) - d2 * mRNA(t) * enz(t), - GFP'(t) = kTL * mRNA(t) - b * GFP(t), - enz'(t) = d3 * mRNAenz(t) - d2 * mRNA(t) * enz(t), - mRNAenz'(t) = -d3 * mRNAenz(t) + d2 * mRNA(t) * enz(t), - y1(t) = GFP(t) - ), + ), + ( + # Transfection_4State + ode = StructuralIdentifiability.@ODEmodel( + mRNA'(t) = -d1 * mRNA(t) - d2 * mRNA(t) * enz(t), + GFP'(t) = kTL * mRNA(t) - b * GFP(t), + enz'(t) = d3 * mRNAenz(t) - d2 * mRNA(t) * enz(t), + mRNAenz'(t) = -d3 * mRNAenz(t) + d2 * mRNA(t) * enz(t), + y1(t) = GFP(t) ), - ( - # LLW1987_io - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = -p1 * x1(t) + p2 * u(t), - x2'(t) = -p3 * x2(t) + p4 * u(t), - x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), - y1(t) = x3(t) - ), + ), + ( + # LLW1987_io + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = -p1 * x1(t) + p2 * u(t), + x2'(t) = -p3 * x2(t) + p4 * u(t), + x3'(t) = -(p1 + p3) * x3(t) + (p4 * x1(t) + p2 * x2(t)) * u(t), + y1(t) = x3(t) ), - ( - # Take care of monomial orders in GB! - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = x1 + x2^2 + a^2, - x2'(t) = x2 + a * d^3, - y(t) = x1 - ), + ), + ( + # Take care of monomial orders in GB! + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = x1 + x2^2 + a^2, + x2'(t) = x2 + a * d^3, + y(t) = x1 ), - ( - # SLIQR system - ode = StructuralIdentifiability.@ODEmodel( - S'(t) = -b * In(t) * S(t) * Ninv - u(t) * S(t) * Ninv, - L'(t) = b * In(t) * S(t) * Ninv - a * L(t), - In'(t) = a * L(t) - g * In(t) + s * Q(t), - Q'(t) = (1 - e) * g * In(t) - s * Q(t), - y(t) = In(t) * Ninv - ), + ), + ( + # SLIQR system + ode = StructuralIdentifiability.@ODEmodel( + S'(t) = -b * In(t) * S(t) * Ninv - u(t) * S(t) * Ninv, + L'(t) = b * In(t) * S(t) * Ninv - a * L(t), + In'(t) = a * L(t) - g * In(t) + s * Q(t), + Q'(t) = (1 - e) * g * In(t) - s * Q(t), + y(t) = In(t) * Ninv ), - ( - ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = a * x1 + b * x2 + u(t), - x2'(t) = b * x1 + c * x2, - y(t) = x1 - ), + ), + ( + ode = StructuralIdentifiability.@ODEmodel( + x1'(t) = a * x1 + b * x2 + u(t), + x2'(t) = b * x1 + c * x2, + y(t) = x1 ), - ] + ), +] - @testset "Global reparametrizations" begin - # Test that variables are mapped properly - ode = cases[1].ode - (new_ode, new_vars, implicit_relations) = - StructuralIdentifiability.reparametrize_global(ode) - @test length(new_vars) == length(gens(parent(new_ode))) == 8 - @test length(new_ode.x_vars) == 2 - # @test map(string, new_ode.y_vars) ≡ map(string, ode.y_vars) - # @test map(string, new_ode.u_vars) ≡ map(string, ode.u_vars) - @test length(new_ode.parameters) == 2 - @test isempty(implicit_relations) +@testset "Global reparametrizations" begin + # Test that variables are mapped properly + ode = cases[1].ode + (new_ode, new_vars, implicit_relations) = + StructuralIdentifiability.reparametrize_global(ode) + @test length(new_vars) == length(gens(parent(new_ode))) == 8 + @test length(new_ode.x_vars) == 2 + # @test map(string, new_ode.y_vars) ≡ map(string, ode.y_vars) + # @test map(string, new_ode.u_vars) ≡ map(string, ode.u_vars) + @test length(new_ode.parameters) == 2 + @test isempty(implicit_relations) - # Test that relations are functional - ode = cases[2].ode + # Test that relations are functional + ode = cases[2].ode + (new_ode, new_vars, implicit_relations) = + StructuralIdentifiability.reparametrize_global(ode) + @test length(implicit_relations) == 1 + + for case in cases + ode = case.ode (new_ode, new_vars, implicit_relations) = StructuralIdentifiability.reparametrize_global(ode) - @test length(implicit_relations) == 1 - - for case in cases - ode = case.ode - (new_ode, new_vars, implicit_relations) = - StructuralIdentifiability.reparametrize_global(ode) - test_reparametrization(ode, new_ode, new_vars, implicit_relations) - end + test_reparametrization(ode, new_ode, new_vars, implicit_relations) end end diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index 9e9403012..6b44201c1 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -1,55 +1,53 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Parent ring change" begin - for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] - R, (z, x, y) = polynomial_ring(QQ, ["z", "x", "y"], ordering = :degrevlex) - R_, (y_, x_) = polynomial_ring(QQ, ["y", "x"], ordering = :lex) - R__, (x__, t__, y__, z__) = - polynomial_ring(QQ, ["x", "t", "y", "z"], ordering = :deglex) +@testset "Parent ring change" begin + for field in [Nemo.QQ, Nemo.Native.GF(2^31 - 1)] + R, (z, x, y) = polynomial_ring(QQ, ["z", "x", "y"], ordering = :degrevlex) + R_, (y_, x_) = polynomial_ring(QQ, ["y", "x"], ordering = :lex) + R__, (x__, t__, y__, z__) = + polynomial_ring(QQ, ["x", "t", "y", "z"], ordering = :deglex) - f = 2x + 3y + x^7 * y - @test f == - StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) - @test f == - StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) + f = 2x + 3y + x^7 * y + @test f == + StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) + @test f == + StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) - f_ = StructuralIdentifiability.parent_ring_change(f, R_, matching = :byname) - f__ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) - @test f_ == 2x_ + 3y_ + x_^7 * y_ - @test f__ == 2x__ + 3y__ + x__^7 * y__ - @test f == - StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) - @test f == - StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) + f_ = StructuralIdentifiability.parent_ring_change(f, R_, matching = :byname) + f__ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) + @test f_ == 2x_ + 3y_ + x_^7 * y_ + @test f__ == 2x__ + 3y__ + x__^7 * y__ + @test f == + StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) + @test f == + StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) - @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( - x + z, - R_, - matching = :byname, - ) + @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( + x + z, + R_, + matching = :byname, + ) - f = 3y - f_ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) - @test f_ == 3y__ + f = 3y + f_ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) + @test f_ == 3y__ - f__ = 2x__ + 5t__^3 + 3y__^2 - f = StructuralIdentifiability.parent_ring_change(f__, R, matching = :byindex) - @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( - f__, - R_, - matching = :byindex, - ) - @test f == 2z + 5x^3 + 3y^2 - @test f__ == - StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) + f__ = 2x__ + 5t__^3 + 3y__^2 + f = StructuralIdentifiability.parent_ring_change(f__, R, matching = :byindex) + @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( + f__, + R_, + matching = :byindex, + ) + @test f == 2z + 5x^3 + 3y^2 + @test f__ == + StructuralIdentifiability.parent_ring_change(f, R__, matching = :byindex) - R, (x,) = polynomial_ring(field, ["x"]) - R2, (x2,) = polynomial_ring(Nemo.fraction_field(R), ["x"]) - R3, (x3,) = polynomial_ring(field, ["x"]) - f = x3^2 - f_ = StructuralIdentifiability.parent_ring_change(f, R2) - @test parent(f) == R3 - @test parent(f_) == R2 - @test repr(f_) == "x^2" - end + R, (x,) = polynomial_ring(field, ["x"]) + R2, (x2,) = polynomial_ring(Nemo.fraction_field(R), ["x"]) + R3, (x3,) = polynomial_ring(field, ["x"]) + f = x3^2 + f_ = StructuralIdentifiability.parent_ring_change(f, R2) + @test parent(f) == R3 + @test parent(f_) == R2 + @test repr(f_) == "x^2" end end diff --git a/test/pb_representation.jl b/test/pb_representation.jl index ad89fcf83..245c0a71e 100644 --- a/test/pb_representation.jl +++ b/test/pb_representation.jl @@ -1,24 +1,22 @@ -if GROUP == "All" || GROUP == "Core" - @testset "PB-representation - creation" begin - ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - @test pbr.profile == Dict("y(t)" => 2) - @test pbr.u_names == Array{String, 1}() - @test pbr.y_names == ["y(t)"] - @test pbr.param_names == ["a"] +@testset "PB-representation - creation" begin + ode = @ODEmodel(x1'(t) = x2(t), x2'(t) = a * x1(t), y(t) = x1(t)) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + @test pbr.profile == Dict("y(t)" => 2) + @test pbr.u_names == Array{String, 1}() + @test pbr.y_names == ["y(t)"] + @test pbr.param_names == ["a"] - ode = @ODEmodel( - x1'(t) = x1(t), - x2'(t) = a * x2(t), - y(t) = x1(t), - y2(t) = x2(t) + u(t) - ) - ioeqs = find_ioequations(ode) - pbr = PBRepresentation(ode, ioeqs) - @test pbr.profile == Dict("y(t)" => 1, "y2(t)" => 1) - @test pbr.u_names == ["u(t)"] - @test pbr.y_names == ["y(t)", "y2(t)"] - @test pbr.param_names == ["a"] - end + ode = @ODEmodel( + x1'(t) = x1(t), + x2'(t) = a * x2(t), + y(t) = x1(t), + y2(t) = x2(t) + u(t) + ) + ioeqs = find_ioequations(ode) + pbr = PBRepresentation(ode, ioeqs) + @test pbr.profile == Dict("y(t)" => 1, "y2(t)" => 1) + @test pbr.u_names == ["u(t)"] + @test pbr.y_names == ["y(t)", "y2(t)"] + @test pbr.param_names == ["a"] end diff --git a/test/ps_diff.jl b/test/ps_diff.jl index 9368ac858..1e40ed23d 100644 --- a/test/ps_diff.jl +++ b/test/ps_diff.jl @@ -1,16 +1,14 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Power Series Differentiation" begin - T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) +@testset "Power Series Differentiation" begin + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) - @test ps_diff(zero(T)) == zero(T) + @test ps_diff(zero(T)) == zero(T) - @test ps_diff(one(T)) == zero(T) + @test ps_diff(one(T)) == zero(T) - @test ps_diff(gen(T)) == one(T) + @test ps_diff(gen(T)) == one(T) - @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == - 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 + @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == + 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 - @test ps_diff((1 + t)^1000) == truncate(1000 * (1 + t)^999, 299) - end + @test ps_diff((1 + t)^1000) == truncate(1000 * (1 + t)^999, 299) end diff --git a/test/ps_integrate.jl b/test/ps_integrate.jl index 79ae5b692..0bfc5c457 100644 --- a/test/ps_integrate.jl +++ b/test/ps_integrate.jl @@ -1,17 +1,15 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Power series integration" begin - T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) +@testset "Power series integration" begin + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) - @test ps_integrate(zero(T)) == zero(T) + @test ps_integrate(zero(T)) == zero(T) - @test ps_integrate(one(T)) == gen(T) + @test ps_integrate(one(T)) == gen(T) - @test ps_integrate(gen(T)) == gen(T)^2 * (1 // 2) + @test ps_integrate(gen(T)) == gen(T)^2 * (1 // 2) - @test ps_integrate(t^3 - t^4 * (1 // 8) + 5 * t^10) == - t^4 * (1 // 4) - t^5 * (1 // 40) + t^11 * (5 // 11) + @test ps_integrate(t^3 - t^4 * (1 // 8) + 5 * t^10) == + t^4 * (1 // 4) - t^5 * (1 // 40) + t^11 * (5 // 11) - @test ps_integrate((1 + t)^1000) == - truncate((1 + t)^1001 * (1 // 1001) - 1 // 1001, 300) - end + @test ps_integrate((1 + t)^1000) == + truncate((1 + t)^1001 * (1 // 1001) - 1 // 1001, 300) end diff --git a/test/ps_inverse.jl b/test/ps_inverse.jl index 076e891ad..361f38584 100644 --- a/test/ps_inverse.jl +++ b/test/ps_inverse.jl @@ -1,28 +1,26 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Power series matrix inverse" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 50, - "t"; - model = :capped_absolute, - ) +@testset "Power series matrix inverse" begin + T, t = Nemo.power_series_ring( + Nemo.Native.GF(2^31 - 1), + 50, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - S = Nemo.matrix_space(T, d, d) - for case in 1:20 + for d in 1:5 + S = Nemo.matrix_space(T, d, d) + for case in 1:20 + M = S([random_ps(T) for i in 1:d, j in 1:d]) + while isequal( + StructuralIdentifiability.LinearAlgebra.det( + StructuralIdentifiability.ps_matrix_const_term(M), + ), + 0, + ) M = S([random_ps(T) for i in 1:d, j in 1:d]) - while isequal( - StructuralIdentifiability.LinearAlgebra.det( - StructuralIdentifiability.ps_matrix_const_term(M), - ), - 0, - ) - M = S([random_ps(T) for i in 1:d, j in 1:d]) - end - invM = ps_matrix_inv(M) - prod = invM * M - @test prod == one(S) end + invM = ps_matrix_inv(M) + prod = invM * M + @test prod == one(S) end end end diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index dfb3a916f..26b6ac49c 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,25 +1,23 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Homogeneous linear differential equations" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 300, - "t"; - model = :capped_absolute, - ) +@testset "Homogeneous linear differential equations" begin + T, t = Nemo.power_series_ring( + Nemo.Native.GF(2^31 - 1), + 300, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - for c in 1:5 - S = Nemo.matrix_space(T, d, d) - A = random_ps_matrix(T, S) - Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) + for d in 1:5 + for c in 1:5 + S = Nemo.matrix_space(T, d, d) + A = random_ps_matrix(T, S) + Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) + Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) + while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) - while StructuralIdentifiability.LinearAlgebra.det(Y0) == 0 - Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) - end - @time sol, sol_inv = ps_matrix_homlinear_de(A, Y0) - to_be_zero = map(ps_diff, sol) - A * sol - @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) end + @time sol, sol_inv = ps_matrix_homlinear_de(A, Y0) + to_be_zero = map(ps_diff, sol) - A * sol + @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) end end end diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index ead2add44..caddfc979 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,23 +1,21 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Linear differential equations" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 300, - "t"; - model = :capped_absolute, - ) +@testset "Linear differential equations" begin + T, t = Nemo.power_series_ring( + Nemo.Native.GF(2^31 - 1), + 300, + "t"; + model = :capped_absolute, + ) - for d in 1:5 - for c in 1:5 - S = Nemo.matrix_space(T, d, d) - A = random_ps_matrix(T, S) - B = random_ps_matrix(T, S) - Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) - Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) - @time sol = ps_matrix_linear_de(A, B, Y0) - to_be_zero = map(ps_diff, sol) - A * sol - B - @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) - end + for d in 1:5 + for c in 1:5 + S = Nemo.matrix_space(T, d, d) + A = random_ps_matrix(T, S) + B = random_ps_matrix(T, S) + Sconst = Nemo.matrix_space(Nemo.Native.GF(2^31 - 1), d, d) + Y0 = Sconst([rand(Int) % 100 for i in 1:d, j in 1:d]) + @time sol = ps_matrix_linear_de(A, B, Y0) + to_be_zero = map(ps_diff, sol) - A * sol - B + @test truncate_matrix(to_be_zero, 299) == truncate_matrix(zero(S), 299) end end end diff --git a/test/ps_matrix_log.jl b/test/ps_matrix_log.jl index b01a95ad5..6a32943ab 100644 --- a/test/ps_matrix_log.jl +++ b/test/ps_matrix_log.jl @@ -1,44 +1,42 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Logarith of power series matrices" begin - T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) +@testset "Logarith of power series matrices" begin + T, t = Nemo.power_series_ring(Nemo.QQ, 300, "t"; model = :capped_absolute) - for d in 1:5 - diag_elements = [1 + t * random_ps(T) for i in 1:d] - S = Nemo.matrix_space(T, d, d) - M = S([(i == j ? diag_elements[i] : T(0)) for i in 1:d, j in 1:d]) - result = ps_matrix_log(M) - correct = S([(i == j ? log(diag_elements[i]) : T(0)) for i in 1:d, j in 1:d]) - @test result == correct - end + for d in 1:5 + diag_elements = [1 + t * random_ps(T) for i in 1:d] + S = Nemo.matrix_space(T, d, d) + M = S([(i == j ? diag_elements[i] : T(0)) for i in 1:d, j in 1:d]) + result = ps_matrix_log(M) + correct = S([(i == j ? log(diag_elements[i]) : T(0)) for i in 1:d, j in 1:d]) + @test result == correct + end - for c in 1:5 - f = random_ps(T) * t - S = Nemo.matrix_space(T, 2, 2) - m = S([ - T(1) f - T(0) T(1) - ]) - correct = S([ - T(0) f - T(0) T(0) - ]) - @test ps_matrix_log(m) == correct - end + for c in 1:5 + f = random_ps(T) * t + S = Nemo.matrix_space(T, 2, 2) + m = S([ + T(1) f + T(0) T(1) + ]) + correct = S([ + T(0) f + T(0) T(0) + ]) + @test ps_matrix_log(m) == correct + end - for c in 1:5 - f, g = random_ps(T) * t, random_ps(T) * t - S = Nemo.matrix_space(T, 3, 3) - m = S([ - T(1) f g - T(0) T(1) f - T(0) T(0) T(1) - ]) - correct = S([ - T(0) f (-1 // 2) * f^2+g - T(0) T(0) f - T(0) T(0) T(0) - ]) - @test ps_matrix_log(m) == correct - end + for c in 1:5 + f, g = random_ps(T) * t, random_ps(T) * t + S = Nemo.matrix_space(T, 3, 3) + m = S([ + T(1) f g + T(0) T(1) f + T(0) T(0) T(1) + ]) + correct = S([ + T(0) f (-1 // 2) * f^2+g + T(0) T(0) f + T(0) T(0) T(0) + ]) + @test ps_matrix_log(m) == correct end end diff --git a/test/pseudodivision.jl b/test/pseudodivision.jl index dec930243..895300ece 100644 --- a/test/pseudodivision.jl +++ b/test/pseudodivision.jl @@ -1,8 +1,6 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Pseudodivision" begin - R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) +@testset "Pseudodivision" begin + R, (x, y, z) = Nemo.polynomial_ring(Nemo.QQ, ["x", "y", "z"]) - @test iszero(pseudodivision(x^2 - y^2, x - y, x)) - @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x - end + @test iszero(pseudodivision(x^2 - y^2, x - y, x)) + @test pseudodivision(y * x^4 + x^3 + x, z * x^2, x) == x end diff --git a/test/reduce_ode_mod_p.jl b/test/reduce_ode_mod_p.jl index 55fff7b4a..b81b57bb0 100644 --- a/test/reduce_ode_mod_p.jl +++ b/test/reduce_ode_mod_p.jl @@ -1,16 +1,14 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Reducing ODE mod p" begin - ode = @ODEmodel( - x'(t) = 1 // 2 * x(t) - 5 * y(t), - y'(t) = (3 // 5 * x(t) + 17 * y(t)) // (y(t) - 1), - z(t) = y(t) - ) +@testset "Reducing ODE mod p" begin + ode = @ODEmodel( + x'(t) = 1 // 2 * x(t) - 5 * y(t), + y'(t) = (3 // 5 * x(t) + 17 * y(t)) // (y(t) - 1), + z(t) = y(t) + ) - ode_red = reduce_ode_mod_p(ode, 17) + ode_red = reduce_ode_mod_p(ode, 17) - x = str_to_var("x(t)", ode_red.poly_ring) - y = str_to_var("y(t)", ode_red.poly_ring) - @test ode_red.x_equations[x] == 9 * x + 12 * y - @test ode_red.x_equations[y] == 4 * x // (y + 16) - end + x = str_to_var("x(t)", ode_red.poly_ring) + y = str_to_var("y(t)", ode_red.poly_ring) + @test ode_red.x_equations[x] == 9 * x + 12 * y + @test ode_red.x_equations[y] == 4 * x // (y + 16) end diff --git a/test/sequence_solution.jl b/test/sequence_solution.jl index 0065d8356..5ebeba3b2 100644 --- a/test/sequence_solution.jl +++ b/test/sequence_solution.jl @@ -1,27 +1,25 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Sequence solutions in the discrete case" begin - # Fibonacci example - ode = @DDSmodel(f1(t + 1) = f1(t) + f0(t), f0(t + 1) = f1(t), y(t) = f1(t)) +@testset "Sequence solutions in the discrete case" begin + # Fibonacci example + ode = @DDSmodel(f1(t + 1) = f1(t) + f0(t), f0(t + 1) = f1(t), y(t) = f1(t)) - sol = sequence_solution( - ode, - Dict{QQMPolyRingElem, Int}(), - Dict(f0 => 1, f1 => 1), - Dict{QQMPolyRingElem, Array{Int, 1}}(), - 10, - ) - @test sol[f1] == [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] - @test sol[f0] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] + sol = sequence_solution( + ode, + Dict{QQMPolyRingElem, Int}(), + Dict(f0 => 1, f1 => 1), + Dict{QQMPolyRingElem, Array{Int, 1}}(), + 10, + ) + @test sol[f1] == [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] + @test sol[f0] == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55] - # Sum of powers - ode = @DDSmodel(a(t + 1) = 2 * a(t) + b * u(t), y(t) = a(t)) - sol = sequence_solution( - ode, - Dict(b => 3), - Dict(a => 1), - Dict(u => [3^i for i in 0:9]), - 10, - ) - @test sol[a] == [-2^i + 3^i for i in 1:10] - end + # Sum of powers + ode = @DDSmodel(a(t + 1) = 2 * a(t) + b * u(t), y(t) = a(t)) + sol = sequence_solution( + ode, + Dict(b => 3), + Dict(a => 1), + Dict(u => [3^i for i in 0:9]), + 10, + ) + @test sol[a] == [-2^i + 3^i for i in 1:10] end diff --git a/test/set_parameter_values.jl b/test/set_parameter_values.jl index 5c2d26cac..046e4a817 100644 --- a/test/set_parameter_values.jl +++ b/test/set_parameter_values.jl @@ -1,21 +1,19 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Set parameter values" begin - sys = @ODEmodel(x'(t) = a * x(t) + a^2, y(t) = x(t)) +@testset "Set parameter values" begin + sys = @ODEmodel(x'(t) = a * x(t) + a^2, y(t) = x(t)) - new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 2))) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(5, 4) + new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 2))) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(5, 4) - new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 20))) - @test evaluate(first(values(new_sys.x_equations)), [5, 5]) == Nemo.QQ(101, 400) + new_sys = set_parameter_values(sys, Dict(a => Nemo.QQ(1, 20))) + @test evaluate(first(values(new_sys.x_equations)), [5, 5]) == Nemo.QQ(101, 400) - new_sys = set_parameter_values(sys, Dict(a => 0.1)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(21, 100) + new_sys = set_parameter_values(sys, Dict(a => 0.1)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(21, 100) - new_sys = set_parameter_values(sys, Dict(a => 2.1)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(861, 100) + new_sys = set_parameter_values(sys, Dict(a => 2.1)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(861, 100) - sys = @ODEmodel(x'(t) = a + b + c, y(t) = x(t)) - new_sys = set_parameter_values(sys, Dict(a => 2.1, b => 5, c => 3 // 10)) - @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(74, 10) - end + sys = @ODEmodel(x'(t) = a + b + c, y(t) = x(t)) + new_sys = set_parameter_values(sys, Dict(a => 2.1, b => 5, c => 3 // 10)) + @test evaluate(first(values(new_sys.x_equations)), [2, 2]) == Nemo.QQ(74, 10) end diff --git a/test/state_generators.jl b/test/state_generators.jl index 9072f0de4..b9ece3e48 100644 --- a/test/state_generators.jl +++ b/test/state_generators.jl @@ -1,34 +1,32 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Generators of observable states" begin - ode = @ODEmodel( - x1'(t) = a * x1(t) - b * x1(t) * x2(t), - x2'(t) = -c * x2(t) + d * x1(t) * x2(t), - y(t) = x1(t) - ) +@testset "Generators of observable states" begin + ode = @ODEmodel( + x1'(t) = a * x1(t) - b * x1(t) * x2(t), + x2'(t) = -c * x2(t) + d * x1(t) * x2(t), + y(t) = x1(t) + ) - sg = states_generators(ode, find_ioequations(ode)) - @test sg == [ - x1 // 1, - (a * x1 - b * x1 * x2) // 1, - ((a - b * x2) * (a * x1 - b * x1 * x2) - b * x1 * (-c * x2 + d * x1 * x2)) // 1, - ] + sg = states_generators(ode, find_ioequations(ode)) + @test sg == [ + x1 // 1, + (a * x1 - b * x1 * x2) // 1, + ((a - b * x2) * (a * x1 - b * x1 * x2) - b * x1 * (-c * x2 + d * x1 * x2)) // 1, + ] - ode = @ODEmodel( - x1'(t) = x2(t), - x2'(t) = x3(t), - x3'(t) = x4(t), - x4'(t) = 1 // x2(t), - y(t) = x1(t) - ) - sg = states_generators(ode, find_ioequations(ode)) - @test sg == [x1 // 1, x2 // 1, x3 // 1, x4 // 1, x2 // 1] + ode = @ODEmodel( + x1'(t) = x2(t), + x2'(t) = x3(t), + x3'(t) = x4(t), + x4'(t) = 1 // x2(t), + y(t) = x1(t) + ) + sg = states_generators(ode, find_ioequations(ode)) + @test sg == [x1 // 1, x2 // 1, x3 // 1, x4 // 1, x2 // 1] - ode = @ODEmodel( - x1'(t) = x2(t), - x2'(t) = (x1(t) + a * x2(t) * u(t)) // (u(t)^2 + x1(t)), - y(t) = x1(t) - ) - sg = states_generators(ode, find_ioequations(ode)) - @test Set(sg) == Set([x1 // 1, x2 // 1, x1 // 1, a * x2 // 1, x1 // 1]) - end + ode = @ODEmodel( + x1'(t) = x2(t), + x2'(t) = (x1(t) + a * x2(t) * u(t)) // (u(t)^2 + x1(t)), + y(t) = x1(t) + ) + sg = states_generators(ode, find_ioequations(ode)) + @test Set(sg) == Set([x1 // 1, x2 // 1, x1 // 1, a * x2 // 1, x1 // 1]) end diff --git a/test/submodels.jl b/test/submodels.jl index 8fcef809f..782889cb4 100644 --- a/test/submodels.jl +++ b/test/submodels.jl @@ -1,148 +1,146 @@ -if GROUP == "All" || GROUP == "Core" - @testset "Finding submodels" begin - cases = [ - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t)^2, - x2'(t) = x1(t) * x2(t), - y1(t) = x1(t), - y2(t) = x2(t) - ), - :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), +@testset "Finding submodels" begin + cases = [ + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t)^2, + x2'(t) = x1(t) * x2(t), + y1(t) = x1(t), + y2(t) = x2(t) ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t), - x2'(t) = x2(t) + x1(t), - y1(t) = x1(t), - y2(t) = x1(t)^2, - y3(t) = x2(t) - ), - :submodels => Set([(["x1(t)"], ["y1(t)", "y2(t)"], Vector{String}())]), + :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), + ), + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t), + x2'(t) = x2(t) + x1(t), + y1(t) = x1(t), + y2(t) = x1(t)^2, + y3(t) = x2(t) ), - Dict( - :ode => @ODEmodel( - x1'(t) = 0, - x2'(t) = 0, - x3'(t) = 0, - y1(t) = x1(t) + x2(t), - y2(t) = x2(t) + x3(t), - y3(t) = x1(t) + x3(t) - ), - :submodels => Set([ - (["x1(t)", "x2(t)"], ["y1(t)"], Vector{String}()), - (["x2(t)", "x3(t)"], ["y2(t)"], Vector{String}()), - (["x1(t)", "x3(t)"], ["y3(t)"], Vector{String}()), - ]), + :submodels => Set([(["x1(t)"], ["y1(t)", "y2(t)"], Vector{String}())]), + ), + Dict( + :ode => @ODEmodel( + x1'(t) = 0, + x2'(t) = 0, + x3'(t) = 0, + y1(t) = x1(t) + x2(t), + y2(t) = x2(t) + x3(t), + y3(t) = x1(t) + x3(t) ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t)^2, - x2'(t) = x1(t) / x2(t), - y1(t) = x1(t), - y2(t) = x2(t) - ), - :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), + :submodels => Set([ + (["x1(t)", "x2(t)"], ["y1(t)"], Vector{String}()), + (["x2(t)", "x3(t)"], ["y2(t)"], Vector{String}()), + (["x1(t)", "x3(t)"], ["y3(t)"], Vector{String}()), + ]), + ), + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t)^2, + x2'(t) = x1(t) / x2(t), + y1(t) = x1(t), + y2(t) = x2(t) ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t) + a(t) * x2(t)^2, - x2'(t) = x2(t) - b(t) * x1(t) * x2(t), - x3'(t) = x3(t) + x1(t) + x2(t), - y1(t) = d(t) * x1(t), - y2(t) = x3(t) - ), - :submodels => - Set([(["x1(t)", "x2(t)"], ["y1(t)"], ["a(t)", "b(t)", "d(t)"])]), + :submodels => Set([(["x1(t)"], ["y1(t)"], Vector{String}())]), + ), + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t) + a(t) * x2(t)^2, + x2'(t) = x2(t) - b(t) * x1(t) * x2(t), + x3'(t) = x3(t) + x1(t) + x2(t), + y1(t) = d(t) * x1(t), + y2(t) = x3(t) ), - Dict( - :ode => @ODEmodel( - x1'(t) = x1(t), - x2'(t) = x1(t) - c * a(t) * x2(t) * x2(t), - x3'(t) = x3(t) - x1(t) * x2(t) * x3(t), - y1(t) = x1(t), - y2(t) = x1(t) + d(t) * x2(t), - y3(t) = x3(t) - ), - :submodels => Set([ - (["x1(t)", "x2(t)"], ["y1(t)", "y2(t)"], ["a(t)", "d(t)"]), - (["x1(t)"], ["y1(t)"], Vector{String}()), - ]), + :submodels => + Set([(["x1(t)", "x2(t)"], ["y1(t)"], ["a(t)", "b(t)", "d(t)"])]), + ), + Dict( + :ode => @ODEmodel( + x1'(t) = x1(t), + x2'(t) = x1(t) - c * a(t) * x2(t) * x2(t), + x3'(t) = x3(t) - x1(t) * x2(t) * x3(t), + y1(t) = x1(t), + y2(t) = x1(t) + d(t) * x2(t), + y3(t) = x3(t) ), - Dict( - :ode => @ODEmodel( - o1'(t) = r1 * o1(t), - x0'(t) = o1(t) - d * x0(t), - x1'(t) = x0(t) - x1(t), - x2'(t) = x1(t) - x2(t), - x3'(t) = x2(t) - x3(t), - x4'(t) = x3(t) - x4(t), - x5'(t) = x4(t) - x5(t), - o12'(t) = r2 * o12(t), - x02'(t) = o12(t) + x2(t) + x3(t) + x22(t) + x32(t) + x02(t), - x12'(t) = x2(t) + x3(t) + x22(t) + x32(t) + x02(t) - x12(t), - x22'(t) = x12(t) - x22(t), - x32'(t) = x22(t) - x32(t), - x42'(t) = x32(t) - x42(t), - x52'(t) = x42(t) - x52(t), - y0(t) = x0(t) + x02(t), - y1(t) = x1(t) + x12(t), - y2(t) = x2(t) + x22(t), - y3(t) = x3(t) + x32(t), - y4(t) = x4(t) + x42(t) + x5(t) + x52(t), - y5(t) = - x1(t) + x2(t) + x3(t) + x4(t) + x12(t) + x22(t) + x32(t) + x42(t), - y6(t) = x5(t) + x52(t) - ), - :submodels => Set([ - ( - [ - "o1(t)", - "x0(t)", - "x1(t)", - "x2(t)", - "x3(t)", - "o12(t)", - "x02(t)", - "x12(t)", - "x22(t)", - "x32(t)", - ], - ["y0(t)", "y1(t)", "y2(t)", "y3(t)"], - Vector{String}(), - ), - ( - [ - "o1(t)", - "x0(t)", - "x1(t)", - "x2(t)", - "x3(t)", - "x4(t)", - "o12(t)", - "x02(t)", - "x12(t)", - "x22(t)", - "x32(t)", - "x42(t)", - ], - ["y0(t)", "y1(t)", "y2(t)", "y3(t)", "y5(t)"], - Vector{String}(), - ), - ]), + :submodels => Set([ + (["x1(t)", "x2(t)"], ["y1(t)", "y2(t)"], ["a(t)", "d(t)"]), + (["x1(t)"], ["y1(t)"], Vector{String}()), + ]), + ), + Dict( + :ode => @ODEmodel( + o1'(t) = r1 * o1(t), + x0'(t) = o1(t) - d * x0(t), + x1'(t) = x0(t) - x1(t), + x2'(t) = x1(t) - x2(t), + x3'(t) = x2(t) - x3(t), + x4'(t) = x3(t) - x4(t), + x5'(t) = x4(t) - x5(t), + o12'(t) = r2 * o12(t), + x02'(t) = o12(t) + x2(t) + x3(t) + x22(t) + x32(t) + x02(t), + x12'(t) = x2(t) + x3(t) + x22(t) + x32(t) + x02(t) - x12(t), + x22'(t) = x12(t) - x22(t), + x32'(t) = x22(t) - x32(t), + x42'(t) = x32(t) - x42(t), + x52'(t) = x42(t) - x52(t), + y0(t) = x0(t) + x02(t), + y1(t) = x1(t) + x12(t), + y2(t) = x2(t) + x22(t), + y3(t) = x3(t) + x32(t), + y4(t) = x4(t) + x42(t) + x5(t) + x52(t), + y5(t) = + x1(t) + x2(t) + x3(t) + x4(t) + x12(t) + x22(t) + x32(t) + x42(t), + y6(t) = x5(t) + x52(t) ), - ] - - for c in cases - submodels = find_submodels(c[:ode]) - submodels = Set([ + :submodels => Set([ + ( + [ + "o1(t)", + "x0(t)", + "x1(t)", + "x2(t)", + "x3(t)", + "o12(t)", + "x02(t)", + "x12(t)", + "x22(t)", + "x32(t)", + ], + ["y0(t)", "y1(t)", "y2(t)", "y3(t)"], + Vector{String}(), + ), ( - collect(map(var_to_str, ode.x_vars)), - collect(map(var_to_str, ode.y_vars)), - collect(map(var_to_str, ode.u_vars)), - ) for ode in submodels - ]) - @test submodels == c[:submodels] - end + [ + "o1(t)", + "x0(t)", + "x1(t)", + "x2(t)", + "x3(t)", + "x4(t)", + "o12(t)", + "x02(t)", + "x12(t)", + "x22(t)", + "x32(t)", + "x42(t)", + ], + ["y0(t)", "y1(t)", "y2(t)", "y3(t)", "y5(t)"], + Vector{String}(), + ), + ]), + ), + ] + + for c in cases + submodels = find_submodels(c[:ode]) + submodels = Set([ + ( + collect(map(var_to_str, ode.x_vars)), + collect(map(var_to_str, ode.y_vars)), + collect(map(var_to_str, ode.u_vars)), + ) for ode in submodels + ]) + @test submodels == c[:submodels] end end From aad9b56b221ed976004333252fabae1afb51c15f Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Fri, 26 Jan 2024 21:41:34 +0100 Subject: [PATCH 64/82] test reogranized a bit --- .../RationalFunctionField.jl | 8 +-- test/check_field_membership.jl | 7 +- test/differentiate_output.jl | 18 ++--- test/diffreduction.jl | 10 ++- test/extensions/modelingtoolkit.jl | 63 +++++------------- test/extract_coefficients.jl | 3 +- test/identifiable_functions.jl | 65 +++++++------------ test/io_cases.jl | 6 +- test/io_projections.jl | 17 ++--- test/local_identifiability_discrete.jl | 9 +-- test/local_identifiability_discrete_aux.jl | 6 +- test/local_identifiability_me.jl | 19 ++---- test/ode_ps_solution.jl | 11 ++-- test/paradigm_shift.jl | 9 +-- test/parent_ring_change.jl | 12 ++-- test/pb_representation.jl | 7 +- test/ps_diff.jl | 3 +- test/ps_inverse.jl | 8 +-- test/ps_matrix_homlinear.jl | 8 +-- test/ps_matrix_linear.jl | 8 +-- test/runtests.jl | 34 ++++++++-- 21 files changed, 115 insertions(+), 216 deletions(-) diff --git a/test/RationalFunctionFields/RationalFunctionField.jl b/test/RationalFunctionFields/RationalFunctionField.jl index e63afe145..350887b77 100644 --- a/test/RationalFunctionFields/RationalFunctionField.jl +++ b/test/RationalFunctionFields/RationalFunctionField.jl @@ -14,12 +14,8 @@ s1, s2 = a + b + c, a^2 + b^2 + c^2 f1 = [s1, s2] rff1 = StructuralIdentifiability.RationalFunctionField(f1) - @test !any( - StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p), - ) - @test all( - StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p), - ) + @test !any(StructuralIdentifiability.field_contains(rff1, [a, b + c, a * b + b * c], p)) + @test all(StructuralIdentifiability.field_contains(rff1, [a * b + b * c + a * c], p)) @test all(StructuralIdentifiability.field_contains(rff1, [(s1)^8 - (s2)^9 + 89], p)) # Example in Section 5 from diff --git a/test/check_field_membership.jl b/test/check_field_membership.jl index 8be30604b..a72ed5d23 100644 --- a/test/check_field_membership.jl +++ b/test/check_field_membership.jl @@ -19,13 +19,8 @@ ) == [true, true, false, false] @test field_contains( - RationalFunctionField([ - x + y + z // 1, - x * y + y * z + z * x // 1, - x * y * z // 1, - ]), + RationalFunctionField([x + y + z // 1, x * y + y * z + z * x // 1, x * y * z // 1]), [x^2 + y^2 + z^2, x^6 + y^6 + z^6, x - y + z, x^2 - y^2 + z^2], 0.99, ) == [true, true, false, false] end - diff --git a/test/differentiate_output.jl b/test/differentiate_output.jl index ff8ed54b2..3c8ef8662 100644 --- a/test/differentiate_output.jl +++ b/test/differentiate_output.jl @@ -21,10 +21,8 @@ function diff_sol_Lie_derivatives(ode::ODE, params, ic, inputs, prec::Int) for (x, f) in ode.x_equations new_eqs[str_to_var(var_to_str(x), new_ring)] = eval_at_dict(f, eval_point) end - params, ic = map( - d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), - [params, ic], - ) + params, ic = + map(d -> Dict(str_to_var(string(k), new_ring) => v for (k, v) in d), [params, ic]) # computing Lie derivatives derivation = copy(new_eqs) @@ -192,8 +190,7 @@ end Dict( :ODE => ODE{P}( Dict{P, DType}( - vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for - i in 1:2 + vars[i] => rand_poly(1, vars[1:5]) // (vars[1] + vars[3]) for i in 1:2 ), Dict{P, DType}(vars[i] => rand_poly(1, vars[1:5]) for i in 6:7), [vars[5]], @@ -209,13 +206,8 @@ end ode, prec = case[:ODE], case[:prec] @time sol1 = differentiate_output(ode, case[:param_vals], case[:ic], case[:inputs], prec) - sol2 = diff_sol_Lie_derivatives( - ode, - case[:param_vals], - case[:ic], - case[:inputs], - prec, - ) + sol2 = + diff_sol_Lie_derivatives(ode, case[:param_vals], case[:ic], case[:inputs], prec) for y in ode.y_vars for v in vcat(ode.x_vars, ode.parameters) @test sol2[y][v] == [ diff --git a/test/diffreduction.jl b/test/diffreduction.jl index 82150ac28..9b8d11101 100644 --- a/test/diffreduction.jl +++ b/test/diffreduction.jl @@ -37,9 +37,8 @@ 27 * y_0^5 + 54 * y_0^4 * y_1 + 18 * y_0^3 * u_1 * y_1 + - 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + - 9 * y_0^2 * u_1^2 * y_1 - 18 * y_0^2 * u_1 * y_1^2 - - 18 * y_0^2 * u_1 * y_1 * y_2 + + 9 * y_0^3 * y_1^2 - 18 * y_0^3 * y_1 * y_2 + 9 * y_0^2 * u_1^2 * y_1 - + 18 * y_0^2 * u_1 * y_1^2 - 18 * y_0^2 * u_1 * y_1 * y_2 + 9 * y_0^2 * y_1^3 + 18 * y_0^2 * y_1^2 * y_2 + 9 * y_0^2 * y_1 * y_2^2 + @@ -114,9 +113,8 @@ 600 * u_1 * y_1^3 * y_0^2 - 810 * u_1 * y_1^2 * y_0^7 + 2820 * u_1 * y_1^2 * y_0^5 + 2170 * u_1 * y_1^2 * y_0^3 + - 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - - 2100 * u_1 * y_1 * y_0^4 - 760 * u_1 * y_1 * y_0^2 - - 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + + 200 * u_1 * y_1^2 * y_0 - 60 * u_1 * y_1 * y_0^6 - 2100 * u_1 * y_1 * y_0^4 - + 760 * u_1 * y_1 * y_0^2 - 180 * u_1 * u_0 * y_0^4 - 1080 * y_1 * u_0 * y_0^6 + 7020 * y_1 * u_0 * y_0^4 - 6030 * y_0^8 + 810 * y_0^12 - 1800 * y_0^10 - 100 * y_1^4, parent(res), diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index a917066dd..7753e5651 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -8,8 +8,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - eqs = - [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] de = ODESystem(eqs, t, name = :Test) correct = OrderedDict( @@ -20,10 +19,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" x1 => :nonidentifiable, ) - @test isequal( - correct, - assess_identifiability(de; measured_quantities = [y1 ~ x0]), - ) + @test isequal(correct, assess_identifiability(de; measured_quantities = [y1 ~ x0])) @test isequal(correct, assess_identifiability(de; measured_quantities = [x0])) @test isequal( correct, @@ -58,8 +54,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - eqs = - [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + eqs = [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] de = ODESystem(eqs, t, name = :Test) correct = OrderedDict( @@ -78,8 +73,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" @variables t x0(t) x1(t) y1(t) [output = true] D = Differential(t) - eqs = - [D(x0) ~ -(a01 + a21) * x0 + a12 * x1, D(x1) ~ a21 * x0 - a12 * x1, y1 ~ x0] + 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 = OrderedDict( @@ -89,10 +83,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" a01 * a12 => :globally, a01 + a12 + a21 => :globally, ) - @test isequal( - correct, - assess_identifiability(de; funcs_to_check = funcs_to_check), - ) + @test isequal(correct, assess_identifiability(de; funcs_to_check = funcs_to_check)) # -------------------------------------------------------------------------- @@ -173,8 +164,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" # checking identifiabile functions correct = [a, bw, χ, bi, k, γ, μ] - result = - find_identifiable_functions(de, measured_quantities = measured_quantities) + result = find_identifiable_functions(de, measured_quantities = measured_quantities) @test isequal(Set(correct), Set(result)) # -------------------------------------------------------------------------- @@ -195,9 +185,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" @test isequal( true, all( - values( - assess_local_identifiability(de; measured_quantities = [y ~ k * I]), - ), + values(assess_local_identifiability(de; measured_quantities = [y ~ k * I])), ), ) @@ -306,8 +294,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" ) # check identifiabile functions - result = - find_identifiable_functions(de, measured_quantities = measured_quantities) + result = find_identifiable_functions(de, measured_quantities = measured_quantities) correct = [b, a, c^2] @test isequal(Set(result), Set(correct)) @@ -362,16 +349,10 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" ps = @parameters β = 1.0 γ = 1.0 D = Differential(t) - eqs = [ - rabbits.z ~ -β * wolves.y * rabbits.x, - wolves.q ~ γ * wolves.y * rabbits.x, - ] + eqs = + [rabbits.z ~ -β * wolves.y * rabbits.x, wolves.q ~ γ * wolves.y * rabbits.x] - ModelingToolkit.compose( - ODESystem(eqs, t, [], ps; name = name), - wolves, - rabbits, - ) + ModelingToolkit.compose(ODESystem(eqs, t, [], ps; name = name), wolves, rabbits) end function getbyname(sys, name) @@ -392,10 +373,8 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" rabbits₊x = getbyname(simp_ltk_mtk, "rabbits₊x") @variables y(t) measured_quantities = [y ~ wolves₊y] - result = assess_identifiability( - simp_ltk_mtk, - measured_quantities = measured_quantities, - ) + result = + assess_identifiability(simp_ltk_mtk, measured_quantities = measured_quantities) correct = Dict( rabbits₊α => :locally, γ => :nonidentifiable, @@ -415,8 +394,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" result = assess_identifiability(sys, measured_quantities = measured_quantities) @test result[a] == :globally - result = - find_identifiable_functions(sys, measured_quantities = measured_quantities) + result = find_identifiable_functions(sys, measured_quantities = measured_quantities) @test isequal(result, [a]) #---------------------------------- @@ -447,8 +425,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" cases, Dict( :dds => sir, - :res => - OrderedDict(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{}[], @@ -582,8 +559,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" cases, Dict( :dds => abmd1, - :res => - OrderedDict(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{}[], @@ -656,11 +632,8 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" cases, Dict( :dds => kic, - :res => OrderedDict( - substitute(x1, Dict(t => 0)) => true, - a => true, - b => true, - ), + :res => + OrderedDict(substitute(x1, Dict(t => 0)) => true, a => true, b => true), :y => [y ~ x1 + b], :y2 => [x1 + b], :known_ic => [x1], diff --git a/test/extract_coefficients.jl b/test/extract_coefficients.jl index 24d195db0..7a66fd8a7 100644 --- a/test/extract_coefficients.jl +++ b/test/extract_coefficients.jl @@ -54,6 +54,5 @@ end R_coef = parent(first(values(C))) y = gens(R_coef)[1] @test symbols(R_coef) == [:y] - @test C == - Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) + @test C == Dict([1, 3] => R_coef(3), [2, 0] => R_coef(1), [0, 3] => -y^2, [0, 1] => y) end diff --git a/test/identifiable_functions.jl b/test/identifiable_functions.jl index 4c2946cc9..6adcd51ca 100644 --- a/test/identifiable_functions.jl +++ b/test/identifiable_functions.jl @@ -19,19 +19,14 @@ ode = StructuralIdentifiability.@ODEmodel( x2'(t) = x1(t) + a, y(t) = x1(t) + x2(t) ) -ident_funcs = - Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() +ident_funcs = Vector{StructuralIdentifiability.AbstractAlgebra.Generic.Frac{typeof(x1)}}() push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) # Example 2 from # "On Global Identifiability for Arbitrary Model Parametrizations", # DOI: 10.1016/0005-1098(94)90029-9 -ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = Θ * x2(t)^2, - x2'(t) = u(t), - y(t) = x1(t) -) +ode = StructuralIdentifiability.@ODEmodel(x1'(t) = Θ * x2(t)^2, x2'(t) = u(t), y(t) = x1(t)) ident_funcs = [Θ] push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) @@ -171,11 +166,7 @@ push!(test_cases, (ode = ode, ident_funcs = ident_funcs)) # incorrectly in the interpolation ode = StructuralIdentifiability.@ODEmodel( x1'(t) = - -(k21 + k31 + k41 + k01) * x1(t) + - k12 * x2(t) + - k13 * x3(t) + - k14 * x4(t) + - u(t), + -(k21 + k31 + k41 + k01) * x1(t) + k12 * x2(t) + k13 * x3(t) + k14 * x4(t) + u(t), x2'(t) = k21 * x1(t) - k12 * x2(t), x3'(t) = k31 * x1(t) - k13 * x3(t), x4'(t) = k41 * x1(t) - k14 * x4(t), @@ -202,15 +193,9 @@ ident_funcs = [ ode = StructuralIdentifiability.@ODEmodel( x5'(t) = ( - k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - - k6 * x5(t) * k7 - x5(t) * k7 * x4(t) + k5 * k8 * x4(t) + k5 * x6(t) * x4(t) + k5 * x5(t) * x4(t) - k6 * x5(t) * k7 - x5(t) * k7 * x4(t) ) // ( - k8 * k6 + - k8 * x4(t) + - k6 * x6(t) + - k6 * x5(t) + - x6(t) * x4(t) + - x5(t) * x4(t) + k8 * k6 + k8 * x4(t) + k6 * x6(t) + k6 * x5(t) + x6(t) * x4(t) + x5(t) * x4(t) ), x7'(t) = (k9 * k10 * x6(t) - k9 * x6(t)^2) // k10, x4'(t) = (-k5 * x4(t)) // (k6 + x4(t)), @@ -280,9 +265,7 @@ ode = StructuralIdentifiability.@ODEmodel( P0'(t) = P1(t), P5'(t) = ( - -P0(t) * beta_SI * phi * Mar * Ks * siga2 + - P0(t) * beta_SI * Mar * Ks * siga2 - - P0(t) * phi * M * Mar * Ks * beta_SA + + -P0(t) * beta_SI * phi * Mar * Ks * siga2 + P0(t) * beta_SI * Mar * Ks * siga2 - P0(t) * phi * M * Mar * Ks * beta_SA + P0(t) * phi * M * Ks * siga2 * beta_SA + P0(t) * M * Mar * Ks * beta_SA - P1(t) * beta_SI * phi * Mar * siga2 - P1(t) * beta_SI * phi * Ks * siga2 + @@ -294,8 +277,8 @@ ode = StructuralIdentifiability.@ODEmodel( P1(t) * M * Ks * beta_SA + P1(t) * Mar * Ks * beta_SA - beta_SI * phi * P2(t) * siga2 + beta_SI * P2(t) * siga2 + - P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - - phi * M * beta * siga2 - phi * P2(t) * Mar * beta_SA + + P3(t) * beta_SA - phi * M * Mar * P5(t) * siga2 - phi * M * beta * siga2 - + phi * P2(t) * Mar * beta_SA + phi * P2(t) * siga2 * beta_SA + M * P2(t) * beta_SA + M * Mar * P5(t) * siga2 + @@ -459,8 +442,7 @@ ode = StructuralIdentifiability.@ODEmodel( siga1 * P3(t)^2 * beta_SA - siga1 * P3(t) * phi * M^2 * siga2 - siga1 * P3(t) * phi * M * Mar * P5(t) * siga2 - siga1 * P3(t) * phi * M * Ks * siga2 - - siga1 * P3(t) * phi * M * beta * siga2 - - siga1 * P3(t) * phi * M * siga2^2 - + siga1 * P3(t) * phi * M * beta * siga2 - siga1 * P3(t) * phi * M * siga2^2 - siga1 * P3(t) * phi * P2(t) * Mar * beta_SA + siga1 * P3(t) * phi * P2(t) * siga2 * beta_SA + siga1 * P3(t) * M^2 * siga2 + @@ -652,8 +634,7 @@ ode = StructuralIdentifiability.@ODEmodel( beta_SI * P3(t) * phi * P2(t) * siga2^2 + beta_SI * P3(t) * M * P2(t) * siga2 + beta_SI * P3(t) * P2(t) * Ks * siga2 + - beta_SI * P3(t) * P2(t) * siga2^2 - - beta_SI * phi * alpa * P2(t) * siga2 - + beta_SI * P3(t) * P2(t) * siga2^2 - beta_SI * phi * alpa * P2(t) * siga2 - beta_SI * phi * M * P2(t)^2 * Ks * siga2 - beta_SI * phi * M * P2(t)^2 * siga2^2 - beta_SI * phi * P4(t) * P2(t) * siga2 - @@ -697,15 +678,13 @@ ode = StructuralIdentifiability.@ODEmodel( P3(t) * P2(t) * Ks^2 * beta_SA + 2 * P3(t) * P2(t) * Ks * siga2 * beta_SA - phi * alpa * M * Mar * P5(t) * siga2 - phi * alpa * M * beta * siga2 - - phi * alpa * P2(t) * Mar * beta_SA + - phi * alpa * P2(t) * siga2 * beta_SA - phi * M^2 * P4(t) * siga2 - - phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - + phi * alpa * P2(t) * Mar * beta_SA + phi * alpa * P2(t) * siga2 * beta_SA - + phi * M^2 * P4(t) * siga2 - phi * M^2 * P2(t) * Mar * P5(t) * Ks * siga2 - phi * M^2 * P2(t) * Mar * P5(t) * siga2^2 - - phi * M^2 * P2(t) * Ks * beta * siga2 - - phi * M^2 * P2(t) * Ks * siga2^2 - phi * M^2 * P2(t) * beta * siga2^2 - - phi * M * P4(t) * Mar * P5(t) * siga2 - phi * M * P4(t) * Ks * siga2 - - phi * M * P4(t) * beta * siga2 - phi * M * P4(t) * siga2^2 - - phi * M * P2(t)^2 * Mar * Ks * beta_SA - + phi * M^2 * P2(t) * Ks * beta * siga2 - phi * M^2 * P2(t) * Ks * siga2^2 - + phi * M^2 * P2(t) * beta * siga2^2 - phi * M * P4(t) * Mar * P5(t) * siga2 - + phi * M * P4(t) * Ks * siga2 - phi * M * P4(t) * beta * siga2 - + phi * M * P4(t) * siga2^2 - phi * M * P2(t)^2 * Mar * Ks * beta_SA - phi * M * P2(t)^2 * Mar * siga2 * beta_SA + phi * M * P2(t)^2 * Ks * siga2 * beta_SA + phi * M * P2(t)^2 * siga2^2 * beta_SA - @@ -765,18 +744,18 @@ ident_funcs = [ ( siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 - siga1 * phi * siga2 * beta_SA - siga1 * M * beta_SA - - beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - - phi * M * Mar * beta_SA + M * Mar * beta_SA + beta_SI * phi * Mar * siga2 + beta_SI * Mar * siga2 - phi * M * Mar * beta_SA + + M * Mar * beta_SA ) // (siga1 * beta_SA + phi * Mar * beta_SA - Mar * beta_SA), ( - siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + siga1 * phi * Mar * beta_SA - - siga1 * phi * siga2 * beta_SA - siga1 * Mar * beta_SA + + siga1^2 * beta_SA + siga1 * beta_SI * phi * siga2 - siga1 * beta_SI * siga2 + + siga1 * phi * Mar * beta_SA - siga1 * phi * siga2 * beta_SA - + siga1 * Mar * beta_SA + siga1 * siga2 * beta_SA + beta_SI * phi * M * siga2 - beta_SI * phi * Mar * siga2 + beta_SI * phi * siga2^2 + beta_SI * Mar * siga2 + - phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - - Mar * siga2 * beta_SA + phi * Mar * siga2 * beta_SA - phi * siga2^2 * beta_SA - Mar * siga2 * beta_SA ) // (phi * M * siga2), ] # Really large and takes a lot of time, so commented diff --git a/test/io_cases.jl b/test/io_cases.jl index 220691f81..9710334cc 100644 --- a/test/io_cases.jl +++ b/test/io_cases.jl @@ -6,8 +6,7 @@ QQ, ["y(t)_0", "y(t)_1", "y(t)_2", "u(t)_0", "u(t)_1", "a01", "a12", "a21"], ) - correct = - y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 + correct = y_2 + y_1 * a01 + y_1 * a21 + y_1 * a12 + y_0 * a01 * a12 - u_0 * a12 - u_1 push!( test_cases, Dict( @@ -58,8 +57,7 @@ 108 * y1_2 * y1_1 * y1_0^2 * a * b * c - 18 * y1_2 * y1_1 * y1_0^2 * a * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * b^3 - 126 * y1_2 * y1_1 * y1_0^2 * b^2 * c - 126 * y1_2 * y1_1 * y1_0^2 * b * c^2 - 18 * y1_2 * y1_1 * y1_0^2 * c^3 - - 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - - 48 * y1_2 * y1_0^5 * a * c + + 24 * y1_2 * y1_0^5 * a^2 - 48 * y1_2 * y1_0^5 * a * b - 48 * y1_2 * y1_0^5 * a * c + 18 * y1_2 * y1_0^3 * a^2 * b^2 + 36 * y1_2 * y1_0^3 * a^2 * b * c + 18 * y1_2 * y1_0^3 * a^2 * c^2 + diff --git a/test/io_projections.jl b/test/io_projections.jl index 38f52c884..edc33c71d 100644 --- a/test/io_projections.jl +++ b/test/io_projections.jl @@ -17,15 +17,12 @@ Complex'(t) = 1 / C * ( ((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) - - (ke_Complex * Complex(t) * C) - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) + (ke_Complex * Complex(t) * C) - + ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) ), Complex_2'(t) = 1 / C * ( - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) - (ke_Complex_2 * Complex_2(t) * C) + ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) - (ke_Complex_2 * Complex_2(t) * C) ), Drug'(t) = 1 / C * ( @@ -36,13 +33,11 @@ free_receptor'(t) = 1 / C * ( -((2 * kon * free_receptor(t) * Drug(t) - koff * Complex(t)) * C) + - (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - ( - (kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C - ) + (66 / 2500 * C) - (kdeg_free_receptor * free_receptor(t) * C) - + ((kon_2 * free_receptor(t) * Complex(t) - 2 * koff * Complex_2(t)) * C) ), Drug_SC'(t) = - -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + - u_SC(t), + -(45 / 100 * ka * Drug_SC(t)) - ((1 - 45 / 100) * ka * Drug_SC(t)) + u_SC(t), y1(t) = Drug(t), y2(t) = free_receptor(t) + Complex(t) + 2 * Complex_2(t) ) diff --git a/test/local_identifiability_discrete.jl b/test/local_identifiability_discrete.jl index 5ae18f4f3..adeeaf8be 100644 --- a/test/local_identifiability_discrete.jl +++ b/test/local_identifiability_discrete.jl @@ -31,13 +31,8 @@ cases, Dict( :dds => dds, - :res => OrderedDict( - a => true, - b => false, - x1 => false, - x2 => true, - b * x1 => true, - ), + :res => + OrderedDict(a => true, b => false, x1 => false, x2 => true, b * x1 => true), :known => :none, ), ) diff --git a/test/local_identifiability_discrete_aux.jl b/test/local_identifiability_discrete_aux.jl index ac7852be6..b2566719e 100644 --- a/test/local_identifiability_discrete_aux.jl +++ b/test/local_identifiability_discrete_aux.jl @@ -66,11 +66,7 @@ push!( cases, - OrderedDict( - :dds => dds, - :res => Dict(theta => true, x => true), - :known => :none, - ), + 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 4f866ef21..07065365d 100644 --- a/test/local_identifiability_me.jl +++ b/test/local_identifiability_me.jl @@ -28,10 +28,8 @@ function _linear_compartment_model(graph, sinks) push!(edges_vars_names, "a_$(s)_0") push!(edges_vars_names, "b_$(s)_0") end - R, vars = Nemo.polynomial_ring( - Nemo.QQ, - vcat(x_vars_names, edges_vars_names, ["y1", "y2"]), - ) + R, vars = + Nemo.polynomial_ring(Nemo.QQ, vcat(x_vars_names, edges_vars_names, ["y1", "y2"])) x_vars = vars[2:(n + 1)] x0 = vars[1] equations = @@ -174,8 +172,7 @@ end #-------------------------------------------------------------------------- # Example 7.7 from https://arxiv.org/pdf/2011.10868.pdf - ode = - @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) + ode = @ODEmodel(x0'(t) = 0, x1'(t) = x0(t) * x1(t) + mu1 * x0(t) + mu2, y(t) = x1(t)) funcs_to_test = [mu1, mu2] correct = [true, true] push!( @@ -201,10 +198,7 @@ end ) funcs_to_test = [b, nu, d, a] correct = OrderedDict([b => true, nu => true, d => true, a => true]) - push!( - test_cases, - Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1)), - ) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 1))) #-------------------------------------------------------------------------- @@ -212,10 +206,7 @@ end ode = @ODEmodel(x'(t) = a * z(t), z'(t) = a * z(t)^2, y(t) = x(t)) funcs_to_test = [a] correct = OrderedDict([a => false]) - push!( - test_cases, - Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0)), - ) + push!(test_cases, Dict(:ode => ode, :funcs => funcs_to_test, :correct => (correct, 0))) #-------------------------------------------------------------------------- diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index ff256b275..dcaa60739 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -44,10 +44,8 @@ denominators = [rand_poly(1, vars[(NUMX + 1):end]) for i in 1:NUMX] end - eqs = [ - denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for - i in 1:NUMX - ] + eqs = + [denominators[i] * vars[i] - rand_poly(2, vars[(NUMX + 1):end]) for i in 1:NUMX] @time sol = ps_ode_solution(eqs, ic, inputs, prec) evals = map(e -> valuation(evaluate(e, [sol[v] for v in gens(R)])), eqs) for e in evals @@ -73,9 +71,8 @@ # Generating the intial conditions etc ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) - inputs = Dict( - u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end] - ) + inputs = + Dict(u => [F(rand(-3:3)) for i in 1:prec] for u in vars[(NUMX + NUMP + 1):end]) eval_at_zero = merge(ic, param_vals, Dict(u => val[1] for (u, val) in inputs)) # Generating denominators not vanishing at t = 0 diff --git a/test/paradigm_shift.jl b/test/paradigm_shift.jl index 553e92312..5c3125c6b 100644 --- a/test/paradigm_shift.jl +++ b/test/paradigm_shift.jl @@ -22,8 +22,7 @@ function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relation ic_point = map(Nemo.QQ, rand(1:bound, length(old_vars))) old_var_ic = Dict(old_vars .=> ic_point) input_ts = map(_ -> [Nemo.QQ(rand(1:bound))], 1:length(old_inputs)) - old_input_ts = - Dict{eltype(old_vars), Vector{Nemo.QQFieldElem}}(old_inputs .=> input_ts) + old_input_ts = Dict{eltype(old_vars), Vector{Nemo.QQFieldElem}}(old_inputs .=> input_ts) old_solutions = StructuralIdentifiability.power_series_solution( old_ode, @@ -73,8 +72,7 @@ function test_reparametrization(old_ode, new_ode, var_mapping, implicit_relation # Test state dynamics projected_solutions = Dict( - var => - StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) + var => StructuralIdentifiability.eval_at_dict(var_mapping[var], old_solutions) for var in new_ode.x_vars ) for var in vcat(new_ode.x_vars) @@ -189,8 +187,7 @@ cases = [ # Reparametrizes into a 4-dimensional nonlinear model with no algebraic # relations -- how is this even legal?? ode = StructuralIdentifiability.@ODEmodel( - x1'(t) = - k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), + x1'(t) = k3 * x3(t) - r3 * x1(t) - k1 * x1(t) * (T0 - x2(t)) + r1 * x2(t), x2'(t) = k1 * x1(t) * (T0 - x2(t)) - (r1 + k2) * x2(t), x3'(t) = r3 * x1(t) - (k3 + k4) * x3(t) + k2 * x2(t), y1(t) = k * (x2(t) + x3(t)) diff --git a/test/parent_ring_change.jl b/test/parent_ring_change.jl index 6b44201c1..327d2cae8 100644 --- a/test/parent_ring_change.jl +++ b/test/parent_ring_change.jl @@ -6,19 +6,15 @@ polynomial_ring(QQ, ["x", "t", "y", "z"], ordering = :deglex) f = 2x + 3y + x^7 * y - @test f == - StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) - @test f == - StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) + @test f == StructuralIdentifiability.parent_ring_change(f, R, matching = :byname) + @test f == StructuralIdentifiability.parent_ring_change(f, R, matching = :byindex) f_ = StructuralIdentifiability.parent_ring_change(f, R_, matching = :byname) f__ = StructuralIdentifiability.parent_ring_change(f, R__, matching = :byname) @test f_ == 2x_ + 3y_ + x_^7 * y_ @test f__ == 2x__ + 3y__ + x__^7 * y__ - @test f == - StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) - @test f == - StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) + @test f == StructuralIdentifiability.parent_ring_change(f_, R, matching = :byname) + @test f == StructuralIdentifiability.parent_ring_change(f__, R, matching = :byname) @test_throws ArgumentError StructuralIdentifiability.parent_ring_change( x + z, diff --git a/test/pb_representation.jl b/test/pb_representation.jl index 245c0a71e..35a92f872 100644 --- a/test/pb_representation.jl +++ b/test/pb_representation.jl @@ -7,12 +7,7 @@ @test pbr.y_names == ["y(t)"] @test pbr.param_names == ["a"] - ode = @ODEmodel( - x1'(t) = x1(t), - x2'(t) = a * x2(t), - y(t) = x1(t), - y2(t) = x2(t) + u(t) - ) + ode = @ODEmodel(x1'(t) = x1(t), x2'(t) = a * x2(t), y(t) = x1(t), y2(t) = x2(t) + u(t)) ioeqs = find_ioequations(ode) pbr = PBRepresentation(ode, ioeqs) @test pbr.profile == Dict("y(t)" => 1, "y2(t)" => 1) diff --git a/test/ps_diff.jl b/test/ps_diff.jl index 1e40ed23d..974459614 100644 --- a/test/ps_diff.jl +++ b/test/ps_diff.jl @@ -7,8 +7,7 @@ @test ps_diff(gen(T)) == one(T) - @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == - 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 + @test ps_diff(t^3 - t^4 * (1 // 8) + 5 * t^10) == 3 * t^2 - t^3 * (1 // 2) + 50 * t^9 @test ps_diff((1 + t)^1000) == truncate(1000 * (1 + t)^999, 299) end diff --git a/test/ps_inverse.jl b/test/ps_inverse.jl index 361f38584..0f9aed191 100644 --- a/test/ps_inverse.jl +++ b/test/ps_inverse.jl @@ -1,10 +1,6 @@ @testset "Power series matrix inverse" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 50, - "t"; - model = :capped_absolute, - ) + T, t = + Nemo.power_series_ring(Nemo.Native.GF(2^31 - 1), 50, "t"; model = :capped_absolute) for d in 1:5 S = Nemo.matrix_space(T, d, d) diff --git a/test/ps_matrix_homlinear.jl b/test/ps_matrix_homlinear.jl index 26b6ac49c..ebdd9fc0b 100644 --- a/test/ps_matrix_homlinear.jl +++ b/test/ps_matrix_homlinear.jl @@ -1,10 +1,6 @@ @testset "Homogeneous linear differential equations" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 300, - "t"; - model = :capped_absolute, - ) + T, t = + Nemo.power_series_ring(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 diff --git a/test/ps_matrix_linear.jl b/test/ps_matrix_linear.jl index caddfc979..410e2cf76 100644 --- a/test/ps_matrix_linear.jl +++ b/test/ps_matrix_linear.jl @@ -1,10 +1,6 @@ @testset "Linear differential equations" begin - T, t = Nemo.power_series_ring( - Nemo.Native.GF(2^31 - 1), - 300, - "t"; - model = :capped_absolute, - ) + T, t = + Nemo.power_series_ring(Nemo.Native.GF(2^31 - 1), 300, "t"; model = :capped_absolute) for d in 1:5 for c in 1:5 diff --git a/test/runtests.jl b/test/runtests.jl index 966448b2b..a2e3dc781 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -114,18 +114,38 @@ function rand_poly(deg, vars) return result end +function get_test_files(group) + result = Vector{String}() + for (dir, _, files) in walkdir("./") + for fname in files + if fname != "runtests.jl" && endswith(fname, ".jl") + if group == "All" || + (group == "Core" && dir != "./extensions") || + ( + group == "ModelingToolkitExt" && + dir == "./extensions" && + VERSION >= v"1.10.0" + ) + push!(result, dir * "/" * fname) + end + end + end + end + return result +end + @info "Testing started" @test isempty(Test.detect_ambiguities(StructuralIdentifiability)) @test isempty(Test.detect_unbound_args(StructuralIdentifiability)) +all_tests = get_test_files(GROUP) +if !isempty(ARGS) + all_tests = ARGS +end + @time @testset "All the tests" verbose = true begin - @includetests ARGS - include("RationalFunctionFields/RationalFunctionField.jl") - include("RationalFunctionFields/normalforms.jl") - @static if VERSION >= v"1.10.0" - if GROUP == "All" || GROUP == "ModelingToolkitExt" - include("extensions//modelingtoolkit.jl") - end + for test_file in all_tests + include(test_file) end end From 2531469577e80b1731c35747d92bac4f3860636d Mon Sep 17 00:00:00 2001 From: Gleb Pogudin Date: Fri, 26 Jan 2024 23:56:04 +0100 Subject: [PATCH 65/82] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8513a0819..0b709fb78 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StructuralIdentifiability" uuid = "220ca800-aa68-49bb-acd8-6037fa93a544" authors = ["Alexander Demin, Ruiwen Dong, Christian Goodbrake, Heather Harrington, Gleb Pogudin "] -version = "0.5.1" +version = "0.5.2" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" From 281727cf8c60ca3ab9cb84230149350bc0b6c35d Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 27 Jan 2024 10:55:51 +0100 Subject: [PATCH 66/82] removing Requires from the deps --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index 8513a0819..83439bd08 100644 --- a/Project.toml +++ b/Project.toml @@ -19,7 +19,6 @@ ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -Requires = "ae029012-a4dd-5104-9daa-d747884805df" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" From 9d6a0bc540b66e8efaf83901e35568981c388c61 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sat, 27 Jan 2024 22:19:26 +0100 Subject: [PATCH 67/82] Aqua + typos --- Project.toml | 9 ++++++--- README.md | 2 +- .../IdentifiableFunctions/experiments.jl | 2 +- .../IdentifiableFunctions/homogenization.jl | 2 +- docs/src/tutorials/creating_ode.md | 2 +- .../RationalFunctionField.jl | 20 +++++++++---------- src/RationalFunctionFields/normalforms.jl | 2 +- src/discrete.jl | 6 +++--- src/global_identifiability.jl | 2 +- src/input_macro.jl | 2 +- src/states.jl | 2 +- src/submodels.jl | 2 +- src/util.jl | 10 +++++----- test/extract_coefficients.jl | 2 +- test/ode_ps_solution.jl | 4 ++-- test/runtests.jl | 4 +--- 16 files changed, 37 insertions(+), 36 deletions(-) diff --git a/Project.toml b/Project.toml index b89994672..9d3d09d91 100644 --- a/Project.toml +++ b/Project.toml @@ -5,7 +5,6 @@ version = "0.5.2" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d" -BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" @@ -32,8 +31,9 @@ ModelingToolkitExt = ["ModelingToolkit", "SymbolicUtils", "Symbolics"] [compat] AbstractAlgebra = "0.34.5, 0.35" -BenchmarkTools = "1" +Aqua = "0.8" Combinatorics = "1" +CPUSummary = "0.2" DataStructures = "0.18" Dates = "1.6, 1.7" Groebner = "0.6.3" @@ -44,17 +44,20 @@ MacroTools = "0.5" ModelingToolkit = "8.74" Nemo = "0.38.3, 0.39" ParamPunPam = "0.3.1" +Pkg = "1.6, 1.7" PrecompileTools = "1.2" Primes = "0.5" Random = "1.6, 1.7" SpecialFunctions = "2" SymbolicUtils = "1.4, 1.5" Symbolics = "5.16" +Test = "1.6, 1.7" TestSetExtensions = "2" TimerOutputs = "0.5" julia = "1.6, 1.7" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" @@ -64,4 +67,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" [targets] -test = ["CPUSummary", "Pkg", "Test", "TestSetExtensions"] +test = ["Aqua", "CPUSummary", "Pkg", "SpecialFunctions", "Test", "TestSetExtensions"] diff --git a/README.md b/README.md index 605339a9a..0a10eb27f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ In this example: - `y(t)` is the **output variable** which is assumed to be observed in the experiments and, thus, known; - `a01, a21, a12, b` are unknown scalar **parameters**. -Note that there may be mulitple inputs and outputs. +Note that there may be multiple inputs and outputs. ### Assessing identifiability diff --git a/benchmarking/IdentifiableFunctions/experiments.jl b/benchmarking/IdentifiableFunctions/experiments.jl index f5b3077a9..136421b58 100644 --- a/benchmarking/IdentifiableFunctions/experiments.jl +++ b/benchmarking/IdentifiableFunctions/experiments.jl @@ -1116,7 +1116,7 @@ end #! format: off new_rff = StructuralIdentifiability.RationalFunctionField(funcs1) -cfs = StructuralIdentifiability.beautifuly_generators(new_rff) +cfs = StructuralIdentifiability.beautiful_generators(new_rff) gb_rff = StructuralIdentifiability.RationalFunctionField(cfs) K = GF(2^31 - 1) diff --git a/benchmarking/IdentifiableFunctions/homogenization.jl b/benchmarking/IdentifiableFunctions/homogenization.jl index 94fafc0bd..630b585d9 100644 --- a/benchmarking/IdentifiableFunctions/homogenization.jl +++ b/benchmarking/IdentifiableFunctions/homogenization.jl @@ -15,7 +15,7 @@ Bilirubin2_io = @ODEmodel( funcs = find_identifiable_functions(Bilirubin2_io, with_states = true, strategy = (:gb,)) rff = StructuralIdentifiability.RationalFunctionField(funcs) -cfs = StructuralIdentifiability.beautifuly_generators(rff) +cfs = StructuralIdentifiability.beautiful_generators(rff) rff = StructuralIdentifiability.RationalFunctionField(cfs) K = GF(2^31 - 1) diff --git a/docs/src/tutorials/creating_ode.md b/docs/src/tutorials/creating_ode.md index 00d362846..199e38156 100644 --- a/docs/src/tutorials/creating_ode.md +++ b/docs/src/tutorials/creating_ode.md @@ -12,7 +12,7 @@ which involves - a vector $\mathbf{x}(t)$ of the state variables of the system, - - a vector $\mathbf{u}(t)$ of extermal inputs, + - a vector $\mathbf{u}(t)$ of external inputs, - a vector $\mathbf{p}$ of scalar parameters, - a vector $\mathbf{y}(t)$ of outputs (i.e., observations), - and vectors of rational functions $\mathbf{f}$ and $\mathbf{g}$ (for discussion of the non-rational case, see this [issue](https://github.com/SciML/StructuralIdentifiability.jl/issues/144)). diff --git a/src/RationalFunctionFields/RationalFunctionField.jl b/src/RationalFunctionFields/RationalFunctionField.jl index 100252e17..fbfea6666 100644 --- a/src/RationalFunctionFields/RationalFunctionField.jl +++ b/src/RationalFunctionFields/RationalFunctionField.jl @@ -212,7 +212,7 @@ end # ------------------------------------------------------------------------------ """ - beautifuly_generators(rff::RationalFunctionField) + beautiful_generators(rff::RationalFunctionField) Given a field of rational functions `rff` returns a set of "simpler" and standardized generators for `rff`. @@ -221,7 +221,7 @@ Applies the following passes: 1. Filter constants, 2. Remove redundant generators. """ -@timeit _to function beautifuly_generators( +@timeit _to function beautiful_generators( rff::RationalFunctionField; discard_redundant = true, reversed_order = false, @@ -402,7 +402,7 @@ Returns a set of Groebner bases for multiple different rankings of variables. # The first basis in some ordering ord = InputOrdering() new_rff = groebner_basis_coeffs(rff, seed = seed, ordering = ord) - cfs = beautifuly_generators(new_rff) + cfs = beautiful_generators(new_rff) ordering_to_generators[ord] = cfs if isempty(cfs) return ordering_to_generators @@ -427,7 +427,7 @@ Returns a set of Groebner bases for multiple different rankings of variables. ordering = ord, up_to_degree = up_to_degree, ) - cfs = beautifuly_generators(new_rff, discard_redundant = false) + cfs = beautiful_generators(new_rff, discard_redundant = false) ordering_to_generators[ord] = cfs end end @@ -444,7 +444,7 @@ Returns a set of Groebner bases for multiple different rankings of variables. ordering = ord, up_to_degree = up_to_degree, ) - cfs = beautifuly_generators(new_rff, discard_redundant = false) + cfs = beautiful_generators(new_rff, discard_redundant = false) ordering_to_generators[ord] = cfs end end @@ -462,7 +462,7 @@ Returns a set of Groebner bases for multiple different rankings of variables. ordering = ord, up_to_degree = up_to_degree, ) - cfs = beautifuly_generators(new_rff, discard_redundant = false) + cfs = beautiful_generators(new_rff, discard_redundant = false) ordering_to_generators[ord] = cfs end end @@ -479,7 +479,7 @@ function monomial_generators_up_to_degree( ) where {T} @assert strategy in (:monte_carlo,) relations = linear_relations_between_normal_forms( - beautifuly_generators(rff), + beautiful_generators(rff), up_to_degree, seed = seed, ) @@ -544,7 +544,7 @@ Result is correct (in the Monte-Carlo sense) with probability at least `prob_thr seed = seed, rational_interpolator = rational_interpolator, ) - new_fracs = beautifuly_generators(new_rff) + new_fracs = beautiful_generators(new_rff) if isempty(new_fracs) return new_fracs end @@ -568,7 +568,7 @@ Result is correct (in the Monte-Carlo sense) with probability at least `prob_thr Final cleaning and simplification of generators. Out of $(length(new_fracs)) fractions $(length(new_fracs_unique)) are syntactically unique.""" runtime = - @elapsed new_fracs = beautifuly_generators(RationalFunctionField(new_fracs_unique)) + @elapsed new_fracs = beautiful_generators(RationalFunctionField(new_fracs_unique)) @debug "Checking inclusion with probability $prob_threshold" runtime = @elapsed result = issubfield(rff, RationalFunctionField(new_fracs), prob_threshold) @@ -578,7 +578,7 @@ Out of $(length(new_fracs)) fractions $(length(new_fracs_unique)) are syntactica throw("The new subfield generators are not correct.") end @info "Inclusion checked with probability $prob_threshold in $(_runtime_logger[:id_inclusion_check]) seconds" - @debug "Out of $(length(rff.mqs.nums_qq)) initial generators there are $(length(new_fracs)) indepdendent" + @debug "Out of $(length(rff.mqs.nums_qq)) initial generators there are $(length(new_fracs)) independent" ranking = generating_set_rank(new_fracs) _runtime_logger[:id_ranking] = ranking @debug "The ranking of the new set of generators is $ranking" diff --git a/src/RationalFunctionFields/normalforms.jl b/src/RationalFunctionFields/normalforms.jl index 9477fe7f0..77b8dce58 100644 --- a/src/RationalFunctionFields/normalforms.jl +++ b/src/RationalFunctionFields/normalforms.jl @@ -1,5 +1,5 @@ -# Maintans a row echelon form of a set of vectors over the integrals. +# Maintains a row echelon form of a set of vectors over the integrals. # Works well when the ambient dimension is small. mutable struct TinyRowEchelonForm{T} rows::Vector{Vector{T}} diff --git a/src/discrete.jl b/src/discrete.jl index 3134fb5e2..3a3f57ad9 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -93,7 +93,7 @@ Input: - `param_values` - parameter values, must be a dictionary mapping parameter to a value - `initial_conditions` - initial conditions of `ode`, must be a dictionary mapping state variable to a value - `input_values` - input sequences in the form input => list of terms; length of the lists must be at least - teh required number of terms in the result + the required number of terms in the result - `num_terms` - number of terms to compute Output: @@ -301,8 +301,8 @@ function _assess_local_identifiability_discrete_aux( # Computing the bound from the Schwartz-Zippel-DeMilo-Lipton lemma deg_x = _degree_with_common_denom(values(x_equations(dds))) deg_y = _degree_with_common_denom(values(y_equations(dds))) - deg_known = reduce(+, map(total_degree, known_ic), init = 0) - deg_to_check = max(map(total_degree, funcs_to_check)...) + deg_known = reduce(+, map(total_degree_frac, known_ic), init = 0) + deg_to_check = max(map(total_degree_frac, funcs_to_check)...) Jac_degree = deg_to_check + deg_known if deg_x > 1 Jac_degree += 2 * deg_y * ((deg_x^prec - 1) ÷ (deg_x - 1)) diff --git a/src/global_identifiability.jl b/src/global_identifiability.jl index da93e6d74..87060fd9d 100644 --- a/src/global_identifiability.jl +++ b/src/global_identifiability.jl @@ -3,7 +3,7 @@ Takes as input input-output equations, the corresponding ode, a list of functions assumed to be known and a flag `with_states`. -Extracts generators of the field of identifiable functions (with or without states) withous +Extracts generators of the field of identifiable functions (with or without states) without any simplifications. Returns a tuple consisting of diff --git a/src/input_macro.jl b/src/input_macro.jl index 3743117c7..5dcb2f797 100644 --- a/src/input_macro.jl +++ b/src/input_macro.jl @@ -263,7 +263,7 @@ Here, - `x1`, `x2` are state variables - `y` is an output variable - `u` is an input variable -- `a`, `b`, `c` are time-indepdendent parameters +- `a`, `b`, `c` are time-independent parameters """ macro ODEmodel(ex::Expr...) diff --git a/src/states.jl b/src/states.jl index 86e0269d8..eee9a018e 100644 --- a/src/states.jl +++ b/src/states.jl @@ -6,7 +6,7 @@ Input: - `vars` - list of variables The function considers `f` as `A / B`, where `A` and `B` are polynomials in `vars` with -coefficients in rational fucntion field in the remaining variables such that at least one of the +coefficients in rational function field in the remaining variables such that at least one of the coefficients is equal to one. Output: diff --git a/src/submodels.jl b/src/submodels.jl index 793e77ba4..b492bc5fd 100644 --- a/src/submodels.jl +++ b/src/submodels.jl @@ -90,7 +90,7 @@ end # ------------------------------------------------------------------------------ -# filters the models containin all states or no states +# filters the models containing all states or no states function filter_max_empty( ode::ODE{P}, submodels::Array{Set{QQMPolyRingElem}, 1}, diff --git a/src/util.jl b/src/util.jl index 919c2bb69..7acc3bdf9 100644 --- a/src/util.jl +++ b/src/util.jl @@ -1,11 +1,11 @@ # ------------------------------------------------------------------------------ -function Nemo.vars(f::Generic.Frac{<:MPolyRingElem}) - return collect(union(Set(vars(numerator(f))), Set(vars(denominator(f))))) +function total_degree_frac(f::Generic.Frac{<:MPolyRingElem}) + return sum(map(total_degree, unpack_fraction(f))) end -function Nemo.total_degree(f::Generic.Frac{<:MPolyRingElem}) - return sum(map(total_degree, unpack_fraction(f))) +function total_degree_frac(f::MPolyRingElem) + return total_degree(f) end # ------------------------------------------------------------------------------ @@ -376,7 +376,7 @@ Input: - `poly` - multivariate polynomial - `variables` - a list of variables from the generators of the ring of p Output: -- dictionary with keys being tuples of length `lenght(variables)` and values being polynomials in the variables other than those which are the coefficients at the corresponding monomials (in a smaller polynomial ring) +- dictionary with keys being tuples of length `length(variables)` and values being polynomials in the variables other than those which are the coefficients at the corresponding monomials (in a smaller polynomial ring) """ function extract_coefficients(poly::P, variables::Array{P, 1}) where {P <: MPolyRingElem} xs = gens(parent(poly)) diff --git a/test/extract_coefficients.jl b/test/extract_coefficients.jl index 7a66fd8a7..0d9302d3d 100644 --- a/test/extract_coefficients.jl +++ b/test/extract_coefficients.jl @@ -1,4 +1,4 @@ -@testset "Coefficient extraction for rational fucntions" begin +@testset "Coefficient extraction for rational functions" begin R, (x, y, z) = polynomial_ring(QQ, ["x", "y", "z"]) C = extract_coefficients_ratfunc( (x^2 + y * z - y^2 * z^3 + 3 * x * z^3) // (x + y + z + z^2 * (x^2 + 1)), diff --git a/test/ode_ps_solution.jl b/test/ode_ps_solution.jl index dcaa60739..35c59ccfa 100644 --- a/test/ode_ps_solution.jl +++ b/test/ode_ps_solution.jl @@ -53,7 +53,7 @@ end end - # Testing ps_ode_solution in conjuntion with the ODE class + # Testing ps_ode_solution in conjunction with the ODE class for i in 1:30 # Setting up the ring NUMX = 3 @@ -68,7 +68,7 @@ PType = fpMPolyRingElem TDict = Dict{PType, Union{PType, Generic.Frac{PType}}} - # Generating the intial conditions etc + # Generating the initial conditions etc ic = Dict(vars[i] => F(rand(-5:5)) for i in 1:NUMX) param_vals = Dict(vars[i + NUMX] => F(rand(-5:5)) for i in 1:NUMP) inputs = diff --git a/test/runtests.jl b/test/runtests.jl index a2e3dc781..1c75f517f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using StructuralIdentifiability using Test using TestSetExtensions +using SpecialFunctions using StructuralIdentifiability.DataStructures using StructuralIdentifiability.Nemo @@ -136,9 +137,6 @@ end @info "Testing started" -@test isempty(Test.detect_ambiguities(StructuralIdentifiability)) -@test isempty(Test.detect_unbound_args(StructuralIdentifiability)) - all_tests = get_test_files(GROUP) if !isempty(ARGS) all_tests = ARGS From 3088effe489e843baac3e9d35c0d52d9250b74e7 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 28 Jan 2024 00:07:08 +0100 Subject: [PATCH 68/82] fixing QA --- .github/workflows/SpellCheck.yml | 13 +++++++++++++ .typos.toml | 2 ++ Project.toml | 2 +- ext/ModelingToolkitExt.jl | 1 + src/StructuralIdentifiability.jl | 2 +- test/qa.jl | 12 ++++++++++++ 6 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/SpellCheck.yml create mode 100644 .typos.toml create mode 100644 test/qa.jl diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 000000000..3ccc0e5a5 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +iname: Spell Check + + on: [pull_request] + + jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v3 + - name: Check spelling + uses: crate-ci/typos@v1.16.23 diff --git a/.typos.toml b/.typos.toml new file mode 100644 index 000000000..aa9a6283e --- /dev/null +++ b/.typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] + numer = "numer" diff --git a/Project.toml b/Project.toml index 9d3d09d91..e41d635c4 100644 --- a/Project.toml +++ b/Project.toml @@ -18,7 +18,6 @@ ParamPunPam = "3e851597-e36f-45a9-af0a-b7781937992f" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" [weakdeps] @@ -61,6 +60,7 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" CPUSummary = "2a0fbf3d-bb9c-48f3-b0a9-814d99fd7ab9" ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/ext/ModelingToolkitExt.jl b/ext/ModelingToolkitExt.jl index 4ca8de85f..334475083 100644 --- a/ext/ModelingToolkitExt.jl +++ b/ext/ModelingToolkitExt.jl @@ -15,6 +15,7 @@ else using ..ModelingToolkit end +export mtk_to_si export assess_local_identifiability, assess_identifiability, find_identifiable_functions # ------------------------------------------------------------------------------ diff --git a/src/StructuralIdentifiability.jl b/src/StructuralIdentifiability.jl index a18c98285..f5a8cb8e9 100644 --- a/src/StructuralIdentifiability.jl +++ b/src/StructuralIdentifiability.jl @@ -21,7 +21,7 @@ using ParamPunPam: reduce_mod_p!, specialize_mod_p, AbstractBlackboxIdeal ParamPunPam.enable_progressbar(false) # defining a model -export ODE, @ODEmodel, @DDSmodel, mtk_to_si +export ODE, @ODEmodel, @DDSmodel # assessing identifiability export assess_local_identifiability, assess_identifiability diff --git a/test/qa.jl b/test/qa.jl new file mode 100644 index 000000000..6cf40ac94 --- /dev/null +++ b/test/qa.jl @@ -0,0 +1,12 @@ +using StructuralIdentifiability, Aqua +@testset "Aqua" begin + Aqua.find_persistent_tasks_deps(StructuralIdentifiability) + Aqua.test_ambiguities(StructuralIdentifiability, recursive = false) + Aqua.test_deps_compat(StructuralIdentifiability) + Aqua.test_piracies(StructuralIdentifiability, + treat_as_own = []) + Aqua.test_project_extras(StructuralIdentifiability) + Aqua.test_stale_deps(StructuralIdentifiability) + Aqua.test_unbound_args(StructuralIdentifiability) + Aqua.test_undefined_exports(StructuralIdentifiability) +end From 1d88ccf5d0f07fed85b3d39bece4962aa0248c6f Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 28 Jan 2024 00:07:47 +0100 Subject: [PATCH 69/82] formatting --- test/qa.jl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/qa.jl b/test/qa.jl index 6cf40ac94..c0d2d8618 100644 --- a/test/qa.jl +++ b/test/qa.jl @@ -1,12 +1,11 @@ using StructuralIdentifiability, Aqua @testset "Aqua" begin - Aqua.find_persistent_tasks_deps(StructuralIdentifiability) - Aqua.test_ambiguities(StructuralIdentifiability, recursive = false) - Aqua.test_deps_compat(StructuralIdentifiability) - Aqua.test_piracies(StructuralIdentifiability, - treat_as_own = []) - Aqua.test_project_extras(StructuralIdentifiability) - Aqua.test_stale_deps(StructuralIdentifiability) - Aqua.test_unbound_args(StructuralIdentifiability) - Aqua.test_undefined_exports(StructuralIdentifiability) + Aqua.find_persistent_tasks_deps(StructuralIdentifiability) + Aqua.test_ambiguities(StructuralIdentifiability, recursive = false) + Aqua.test_deps_compat(StructuralIdentifiability) + Aqua.test_piracies(StructuralIdentifiability, treat_as_own = []) + Aqua.test_project_extras(StructuralIdentifiability) + Aqua.test_stale_deps(StructuralIdentifiability) + Aqua.test_unbound_args(StructuralIdentifiability) + Aqua.test_undefined_exports(StructuralIdentifiability) end From 1526318c143acac801b7a1428f4949baaa3c3177 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 28 Jan 2024 23:13:04 +0100 Subject: [PATCH 70/82] tweaking the downgrade options hoping for the best --- .github/workflows/Downgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 3a84c6477..4f2678db4 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -25,7 +25,7 @@ jobs: # if: ${{ matrix.version == '1.6' }} with: # skip standard libraries.. - skip: Pkg,TOML,Logging,Random,Dates,LinearAlgebra + skip: Pkg,TOML,Logging,Random,Dates,LinearAlgebra,Test,Compat,Aqua strict: 'false' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From 9f3cc5817b1346b79b4db8febaa350c943a65b34 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Sun, 28 Jan 2024 23:41:34 +0100 Subject: [PATCH 71/82] A vi druja kak ni sadites'... --- test/runtests.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index 1c75f517f..a6d23c663 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -119,7 +119,7 @@ function get_test_files(group) result = Vector{String}() for (dir, _, files) in walkdir("./") for fname in files - if fname != "runtests.jl" && endswith(fname, ".jl") + if !(fname in ("runtests.jl", "qa.jl")) && endswith(fname, ".jl") if group == "All" || (group == "Core" && dir != "./extensions") || ( @@ -137,6 +137,8 @@ end @info "Testing started" +@time @testset "Quality Assurance" include("qa.jl") + all_tests = get_test_files(GROUP) if !isempty(ARGS) all_tests = ARGS From 2e9573e8142d944d81480e94567ea607478976ba Mon Sep 17 00:00:00 2001 From: gpogudin Date: Mon, 29 Jan 2024 16:18:47 +0100 Subject: [PATCH 72/82] fixing runtests --- test/runtests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests.jl b/test/runtests.jl index a6d23c663..e4a1b8f6a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -137,7 +137,7 @@ end @info "Testing started" -@time @testset "Quality Assurance" include("qa.jl") +@time include("qa.jl") all_tests = get_test_files(GROUP) if !isempty(ARGS) From d2ac99feb9becfcbc02139e13a1b20374aa780f6 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Mon, 29 Jan 2024 16:33:35 +0100 Subject: [PATCH 73/82] at least fixing SpellCheck --- .github/workflows/SpellCheck.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index 3ccc0e5a5..0fc329195 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -1,8 +1,8 @@ -iname: Spell Check +name: Spell Check - on: [pull_request] +on: [pull_request] - jobs: +jobs: typos-check: name: Spell Check with Typos runs-on: ubuntu-latest From ba33fd9f0bce9aee45b388e6da5cd096d97478f1 Mon Sep 17 00:00:00 2001 From: gpogudin Date: Mon, 29 Jan 2024 16:36:23 +0100 Subject: [PATCH 74/82] fixing typos --- docs/src/tutorials/creating_ode.md | 2 +- src/discrete.jl | 2 +- src/input_macro.jl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/tutorials/creating_ode.md b/docs/src/tutorials/creating_ode.md index 199e38156..915a742a8 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 descibe +`StructuralIdentifiability` has an extension `ModelingToolkitExt` 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/src/discrete.jl b/src/discrete.jl index 3a3f57ad9..45bbe0c58 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -97,7 +97,7 @@ Input: - `num_terms` - number of terms to compute Output: -- computes a sequence solution with teh required number of terms prec presented as a dictionary state_variable => corresponding sequence +- computes a sequence solution with the required number of terms prec presented as a dictionary state_variable => corresponding sequence """ function sequence_solution( dds::DDS{P}, diff --git a/src/input_macro.jl b/src/input_macro.jl index 5dcb2f797..a4c8a6f1c 100644 --- a/src/input_macro.jl +++ b/src/input_macro.jl @@ -297,7 +297,7 @@ Here, - `x1`, `x2` are state variables - `y` is an output variable - `u` is an input variable -- `a`, `b`, `c` are time-indepdendent parameters +- `a`, `b`, `c` are time-independent parameters """ macro DDSmodel(ex::Expr...) From c791f61475ba555daf4a77b3e01bcfbade583d39 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Tue, 30 Jan 2024 23:40:45 +0100 Subject: [PATCH 75/82] bumping MTK --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index e41d635c4..9c0612450 100644 --- a/Project.toml +++ b/Project.toml @@ -40,7 +40,7 @@ IterTools = "1" LinearAlgebra = "1.6, 1.7" Logging = "1.6, 1.7" MacroTools = "0.5" -ModelingToolkit = "8.74" +ModelingToolkit = "8.75" Nemo = "0.38.3, 0.39" ParamPunPam = "0.3.1" Pkg = "1.6, 1.7" From c3c25a9468f52be644fdc1554733c48086a7a6a3 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 31 Jan 2024 17:10:02 +0100 Subject: [PATCH 76/82] skipping IterTools - that's the key --- .github/workflows/Downgrade.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Downgrade.yml b/.github/workflows/Downgrade.yml index 4f2678db4..df4414fc5 100644 --- a/.github/workflows/Downgrade.yml +++ b/.github/workflows/Downgrade.yml @@ -25,7 +25,7 @@ jobs: # if: ${{ matrix.version == '1.6' }} with: # skip standard libraries.. - skip: Pkg,TOML,Logging,Random,Dates,LinearAlgebra,Test,Compat,Aqua + skip: Aqua,Compat,Dates,IterTools,LinearAlgebra,Logging,Pkg,Random,Test,TOML strict: 'false' - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 From f7845e85bdbb483b7c20f57a1b3e4b0c78f41640 Mon Sep 17 00:00:00 2001 From: pogudingleb Date: Wed, 31 Jan 2024 18:00:14 +0100 Subject: [PATCH 77/82] merging qa with other tests --- test/runtests.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index e4a1b8f6a..1c75f517f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -119,7 +119,7 @@ function get_test_files(group) result = Vector{String}() for (dir, _, files) in walkdir("./") for fname in files - if !(fname in ("runtests.jl", "qa.jl")) && endswith(fname, ".jl") + if fname != "runtests.jl" && endswith(fname, ".jl") if group == "All" || (group == "Core" && dir != "./extensions") || ( @@ -137,8 +137,6 @@ end @info "Testing started" -@time include("qa.jl") - all_tests = get_test_files(GROUP) if !isempty(ARGS) all_tests = ARGS From 4de0daa8d1d5acbbda7a64b345dabeeff66d5a5e Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 1 Feb 2024 15:19:10 -0500 Subject: [PATCH 78/82] init --- .github/workflows/CI.yml | 2 +- Project.toml | 2 +- docs/src/tutorials/creating_ode.md | 2 +- docs/src/tutorials/discrete_time.md | 2 +- ...{ModelingToolkitExt.jl => ModelingToolkitSIExt.jl} | 11 ++++------- src/StructuralIdentifiability.jl | 6 ++++++ test/extensions/modelingtoolkit.jl | 6 +++++- test/runtests.jl | 4 ++-- 8 files changed, 21 insertions(+), 14 deletions(-) rename ext/{ModelingToolkitExt.jl => ModelingToolkitSIExt.jl} (99%) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 997b5426e..ed747b4c5 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 9c0612450..005a89cc8 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 915a742a8..a09b069cc 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 c8f0571f8..4138c47b4 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 334475083..65af94c9e 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 f5a8cb8e9..66713ccab 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 7753e5651..c85883a6b 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,8 @@ if GROUP == "All" || GROUP == "ModelingToolkitExt" ) == c[:res] end end + + @testset "Exporting ModelingToolkit Model to SI Model" begin + + end end diff --git a/test/runtests.jl b/test/runtests.jl index 1c75f517f..e5ed9fe7b 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" ) From 302356eb4921e9953f7dc40bd7c927b036d1cb6f Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 1 Feb 2024 15:29:45 -0500 Subject: [PATCH 79/82] prepare tests --- test/extensions/modelingtoolkit.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index c85883a6b..d09ce8108 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -658,6 +658,6 @@ if GROUP == "All" || GROUP == "ModelingToolkitSIExt" end @testset "Exporting ModelingToolkit Model to SI Model" begin - + # Add test of `mtk_to_si` function here, as well as identifiability functions when applied to its output. end end From 8ee49c8907286066c2688bf11b41a9c1f398e19c Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 1 Feb 2024 15:50:20 -0500 Subject: [PATCH 80/82] add tests --- test/extensions/modelingtoolkit.jl | 39 +++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index d09ce8108..d06257e50 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -658,6 +658,43 @@ if GROUP == "All" || GROUP == "ModelingToolkitSIExt" end @testset "Exporting ModelingToolkit Model to SI Model" begin - # Add test of `mtk_to_si` function here, as well as identifiability functions when applied to its output. + + # 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 From 18d36479ee2b407db90514ff253f85788ab50fb9 Mon Sep 17 00:00:00 2001 From: Torkel Date: Thu, 1 Feb 2024 16:56:29 -0500 Subject: [PATCH 81/82] format tests --- test/extensions/modelingtoolkit.jl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test/extensions/modelingtoolkit.jl b/test/extensions/modelingtoolkit.jl index d06257e50..bdeaf619c 100644 --- a/test/extensions/modelingtoolkit.jl +++ b/test/extensions/modelingtoolkit.jl @@ -662,7 +662,7 @@ if GROUP == "All" || GROUP == "ModelingToolkitSIExt" # 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) + 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), @@ -670,9 +670,12 @@ if GROUP == "All" || GROUP == "ModelingToolkitSIExt" 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) + 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) @@ -683,18 +686,18 @@ if GROUP == "All" || GROUP == "ModelingToolkitSIExt" # 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}() + 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 + 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) + @test length(ifs_1) == length(ifs_2) end end From 63158d595078e9cc366ac2489810341ee2acea16 Mon Sep 17 00:00:00 2001 From: Gleb Pogudin Date: Thu, 1 Feb 2024 23:34:07 +0100 Subject: [PATCH 82/82] bump version --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 005a89cc8..1bbff5d02 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StructuralIdentifiability" uuid = "220ca800-aa68-49bb-acd8-6037fa93a544" authors = ["Alexander Demin, Ruiwen Dong, Christian Goodbrake, Heather Harrington, Gleb Pogudin "] -version = "0.5.2" +version = "0.5.3" [deps] AbstractAlgebra = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"