Skip to content

Commit 285cbac

Browse files
Merge pull request #2321 from TorkelE/BifurcationKitExt2
BifurcationKit Extension (take 2)
2 parents 0ef0bb4 + eeb160b commit 285cbac

File tree

6 files changed

+166
-1
lines changed

6 files changed

+166
-1
lines changed

Project.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ UnPack = "3a884ed6-31ef-47d7-9d2a-63182c4928ed"
5151
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"
5252

5353
[weakdeps]
54+
BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665"
5455
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
5556

5657
[extensions]
58+
MTKBifurcationKitExt = "BifurcationKit"
5759
MTKDeepDiffsExt = "DeepDiffs"
5860

5961
[compat]
@@ -101,6 +103,7 @@ julia = "1.6"
101103
[extras]
102104
AmplNLWriter = "7c4d4715-977e-5154-bfe0-e096adeac482"
103105
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
106+
BifurcationKit = "0f109fa4-8a5d-4b75-95aa-f515264e7665"
104107
ControlSystemsMTK = "687d7614-c7e5-45fc-bfc3-9ee385575c88"
105108
DeepDiffs = "ab62b9b5-e342-54a8-a765-a90f495de1a6"
106109
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
@@ -123,4 +126,4 @@ Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
123126
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
124127

125128
[targets]
126-
test = ["AmplNLWriter", "BenchmarkTools", "ControlSystemsMTK", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq"]
129+
test = ["AmplNLWriter", "BenchmarkTools", "BifurcationKit", "ControlSystemsMTK", "NonlinearSolve", "ForwardDiff", "Ipopt", "Ipopt_jll", "ModelingToolkitStandardLibrary", "Optimization", "OptimizationOptimJL", "OptimizationMOI", "Random", "ReferenceTests", "SafeTestsets", "StableRNGs", "Statistics", "SteadyStateDiffEq", "Test", "StochasticDiffEq", "Sundials", "StochasticDelayDiffEq"]

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pages = [
88
"tutorials/programmatically_generating.md",
99
"tutorials/stochastic_diffeq.md",
1010
"tutorials/parameter_identifiability.md",
11+
"tutorials/bifurcation_diagram_computation.md",
1112
"tutorials/domain_connections.md"],
1213
"Examples" => Any["Basic Examples" => Any["examples/higher_order.md",
1314
"examples/spring_mass.md",
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# [Bifurcation Diagrams](@id bifurcation_diagrams)
2+
Bifurcation diagrams describes how, for a dynamic system, the quantity and quality of its steady states changes with a parameter's value. These can be computed through the [BifurcationKit.jl](https://github.com/bifurcationkit/BifurcationKit.jl) package. ModelingToolkit provides a simple interface for creating BifurcationKit compatible `BifurcationProblem`s from `NonlinearSystem`s and `ODESystem`s. All teh features provided by BifurcationKit can then be applied to these systems. This tutorial provides a brief introduction for these features, with BifurcationKit.jl providing [a more extensive documentation](https://bifurcationkit.github.io/BifurcationKitDocs.jl/stable/).
3+
4+
### Creating a `BifurcationProblem`
5+
Let us first consider a simple `NonlinearSystem`:
6+
```@example Bif1
7+
using ModelingToolkit
8+
@variables t x(t) y(t)
9+
@parameters μ α
10+
eqs = [0 ~ μ*x - x^3 + α*y,
11+
0 ~ -y]
12+
@named nsys = NonlinearSystem(eqs, [x, y], [μ, α])
13+
```
14+
we wish to compute a bifurcation diagram for this system as we vary the parameter `μ`. For this, we need to provide the following information:
15+
1. The system for which we wish to compute the bifurcation diagram (`nsys`).
16+
2. The parameter which we wish to vary (`μ`).
17+
3. The parameter set for which we want to compute the bifurcation diagram.
18+
4. An initial guess of the state of the system for which there is a steady state at our provided parameter value.
19+
5. The variable which value we wish to plot in the bifurcation diagram (this argument is optional, if not provided, BifurcationKit default plot functions are used).
20+
21+
We declare this additional information:
22+
```@example Bif1
23+
bif_par = μ
24+
p_start = [μ => -1.0, α => 1.0]
25+
u0_guess = [x => 1.0, y => 1.0]
26+
plot_var = x;
27+
```
28+
For the initial state guess (`u0_guess`), typically any value can be provided, however, read BifurcatioKit's documentation for more details.
29+
30+
We can now create our `BifurcationProblem`, which can be provided as input to BifurcationKit's various functions.
31+
```@example Bif1
32+
using BifurcationKit
33+
bprob = BifurcationProblem(nsys, u0_guess, p_start, bif_par; plot_var=plot_var, jac=false)
34+
```
35+
Here, the `jac` argument (by default set to `true`) sets whenever to provide BifurcationKit with a Jacobian or not.
36+
37+
38+
### Computing a bifurcation diagram
39+
40+
Let us consider the `BifurcationProblem` from the last section. If we wish to compute the corresponding bifurcation diagram we must first declare various settings used by BifurcationKit to compute the diagram. These are stored in a `ContinuationPar` structure (which also contain a `NewtonPar` structure).
41+
```@example Bif1
42+
p_span = (-4.0, 6.0)
43+
opt_newton = NewtonPar(tol = 1e-9, max_iterations = 20)
44+
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01,
45+
max_steps = 100, nev = 2, newton_options = opt_newton,
46+
p_min = p_span[1], p_max = p_span[2],
47+
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8, dsmin_bisection = 1e-9);
48+
```
49+
Here, `p_span` sets the interval over which we wish to compute the diagram.
50+
51+
Next, we can use this as input to our bifurcation diagram, and then plot it.
52+
```@example Bif1
53+
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true)
54+
```
55+
Here, the value `2` sets how sub-branches of the diagram that BifurcationKit should compute. Generally, for bifurcation diagrams, it is recommended to use the `bothside=true` argument.
56+
```@example Bif1
57+
using Plots
58+
plot(bf; putspecialptlegend=false, markersize=2, plotfold=false, xguide="μ", yguide = "x")
59+
```
60+
Here, the system exhibits a pitchfork bifurcation at *μ=0.0*.
61+
62+
### Using `ODESystem` inputs
63+
It is also possible to use `ODESystem`s (rather than `NonlinearSystem`s) as input to `BifurcationProblem`. Here follows a brief such example.
64+
65+
```@example Bif2
66+
using BifurcationKit, ModelingToolkit, Plots
67+
68+
@variables t x(t) y(t)
69+
@parameters μ
70+
D = Differential(t)
71+
eqs = [D(x) ~ μ*x - y - x*(x^2+y^2),
72+
D(y) ~ x + μ*y - y*(x^2+y^2)]
73+
@named osys = ODESystem(eqs, t)
74+
75+
bif_par = μ
76+
plot_var = x
77+
p_start = [μ => 1.0]
78+
u0_guess = [x => 0.0, y=> 0.0]
79+
80+
bprob = BifurcationProblem(osys, u0_guess, p_start, bif_par; plot_var=plot_var, jac=false)
81+
82+
p_span = (-3.0, 3.0)
83+
opt_newton = NewtonPar(tol = 1e-9, max_iterations = 20)
84+
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01,
85+
max_steps = 100, nev = 2, newton_options = opt_newton,
86+
p_max = p_span[2], p_min = p_span[1],
87+
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8, dsmin_bisection = 1e-9)
88+
89+
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true)
90+
using Plots
91+
plot(bf; putspecialptlegend=false, markersize=2, plotfold=false, xguide="μ", yguide = "x")
92+
```
93+
Here, the value of `x` in the steady state does not change, however, at `μ=0` a Hopf bifurcation occur and the steady state turn unstable.

ext/MTKBifurcationKitExt.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module MTKBifurcationKitExt
2+
3+
### Preparations ###
4+
5+
# Imports
6+
using ModelingToolkit, Setfield
7+
import BifurcationKit
8+
9+
### Creates BifurcationProblem Overloads ###
10+
11+
# When input is a NonlinearSystem.
12+
function BifurcationKit.BifurcationProblem(nsys::NonlinearSystem, u0_bif, ps, bif_par, args...; plot_var=nothing, record_from_solution=BifurcationKit.record_sol_default, jac=true, kwargs...)
13+
# Creates F and J functions.
14+
ofun = NonlinearFunction(nsys; jac=jac)
15+
F = ofun.f
16+
J = jac ? ofun.jac : nothing
17+
18+
# Computes bifurcation parameter and plot var indexes.
19+
bif_idx = findfirst(isequal(bif_par), parameters(nsys))
20+
if !isnothing(plot_var)
21+
plot_idx = findfirst(isequal(plot_var), states(nsys))
22+
record_from_solution = (x, p) -> x[plot_idx]
23+
end
24+
25+
# Converts the input state guess.
26+
u0_bif = ModelingToolkit.varmap_to_vars(u0_bif, states(nsys))
27+
ps = ModelingToolkit.varmap_to_vars(ps, parameters(nsys))
28+
29+
return BifurcationKit.BifurcationProblem(F, u0_bif, ps, (@lens _[bif_idx]), args...; record_from_solution = record_from_solution, J = J, kwargs...)
30+
end
31+
32+
# When input is a ODESystem.
33+
function BifurcationKit.BifurcationProblem(osys::ODESystem, args...; kwargs...)
34+
nsys = NonlinearSystem([0 ~ eq.rhs for eq in equations(osys)], states(osys), parameters(osys); name=osys.name)
35+
return BifurcationKit.BifurcationProblem(nsys, args...; kwargs...)
36+
end
37+
38+
end # module

test/extensions/bifurcationkit.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using BifurcationKit, ModelingToolkit, Test
2+
3+
# Checks pitchfork diagram and that there are the correct number of branches (a main one and two children)
4+
let
5+
@variables t x(t) y(t)
6+
@parameters μ α
7+
eqs = [0 ~ μ*x - x^3 + α*y,
8+
0 ~ -y]
9+
@named nsys = NonlinearSystem(eqs, [x, y], [μ, α])
10+
11+
bif_par = μ
12+
p_start ==> -1.0, α => 1.0]
13+
u0_guess = [x => 1.0, y => 1.0]
14+
plot_var = x;
15+
16+
using BifurcationKit
17+
bprob = BifurcationProblem(nsys, u0_guess, p_start, bif_par; plot_var=plot_var, jac=false)
18+
19+
p_span = (-4.0, 6.0)
20+
opt_newton = NewtonPar(tol = 1e-9, max_iterations = 20)
21+
opts_br = ContinuationPar(dsmin = 0.001, dsmax = 0.05, ds = 0.01,
22+
max_steps = 100, nev = 2, newton_options = opt_newton,
23+
p_min = p_span[1], p_max = p_span[2],
24+
detect_bifurcation = 3, n_inversion = 4, tol_bisection_eigenvalue = 1e-8, dsmin_bisection = 1e-9);
25+
26+
bf = bifurcationdiagram(bprob, PALC(), 2, (args...) -> opts_br; bothside=true)
27+
28+
@test length(bf.child) == 2
29+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ end
5959
if VERSION >= v"1.9"
6060
@safetestset "Latexify recipes Test" include("latexify.jl")
6161
end
62+
@safetestset "BifurcationKit Extension Test" include("extensions/bifurcationkit.jl.jl")

0 commit comments

Comments
 (0)