Skip to content

Commit 5460da1

Browse files
authored
Merge pull request #1194 from SciML/add_nullcline_example
Add example of how to compute nullclines using bifurcation kit
2 parents 732c935 + e0e211d commit 5460da1

File tree

3 files changed

+92
-2
lines changed

3 files changed

+92
-2
lines changed

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ DataFrames = "1.6"
5555
DataInterpolations = "7.2, 8"
5656
DiffEqBase = "6.159.0"
5757
Distributions = "0.25"
58-
Documenter = "1.4.1"
58+
Documenter = "< 1.11.0"
5959
DynamicalSystems = "3.3"
6060
GlobalSensitivity = "2.6"
6161
GraphMakie = "0.5"

docs/pages.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ pages = Any[
5151
"steady_state_functionality/bifurcation_diagrams.md",
5252
"steady_state_functionality/dynamical_systems.md",
5353
"Examples" => Any[
54-
"steady_state_functionality/examples/bifurcationkit_periodic_orbits.md"
54+
"steady_state_functionality/examples/nullcline_plotting.md",
55+
"steady_state_functionality/examples/bifurcationkit_periodic_orbits.md",
5556
"steady_state_functionality/examples/bifurcationkit_codim2.md"
5657
]
5758
],
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# [Plotting Nullclines and Steady States in Phase Space](@id nullcline_plotting)
2+
In this tutorial we will show how to extract a system's steady states and [nullclines](https://en.wikipedia.org/wiki/Nullcline), and how to plot these in [phase space](https://en.wikipedia.org/wiki/Phase_space). Generally, while nullclines are not directly needed for most types analysis, plotting these can give some understanding of a system's steady state and stability properties.
3+
4+
For an ordinary differential equation
5+
```math
6+
\begin{aligned}
7+
\frac{dx_1}{dt} &= f_1(x_1, x_2, ..., x_n) \\
8+
\frac{dx_2}{dt} &= f_2(x_1, x_2, ..., x_n) \\
9+
&\vdots\\
10+
\frac{dx_n}{dt} &= f_n(x_1, x_2, ..., x_n) \\
11+
\end{aligned}
12+
```
13+
the $i$'th nullcline is the surface along which $\frac{dx_i}{dt} = 0$, i.e. the implicit surface given by $f_i(x_1,\dots,x_n) = 0$. Nullclines are frequently used when visualizing the phase-planes of two-dimensional models (as these can be easily plotted).
14+
15+
## [Computing nullclines and steady states for a bistable switch](@id nullcline_plotting_computation)
16+
For our example we will use a simple bistable switch model, consisting of two species ($X$ and $Y$) which mutually inhibit each other through repressive Hill functions.
17+
```@example nullcline_plotting
18+
using Catalyst
19+
bs_switch = @reaction_network begin
20+
hillr(Y, v, K, n), 0 --> X
21+
hillr(X, v, K, n), 0 --> Y
22+
1.0, (X,Y) --> 0
23+
end
24+
```
25+
26+
Next, we compute the steady states [using homotopy continuation](@ref homotopy_continuation).
27+
```@example nullcline_plotting
28+
import HomotopyContinuation
29+
ps = [:v => 1.0, :K => 0.6, :n => 4.0]
30+
sss = hc_steady_states(bs_switch, ps; show_progress = false)
31+
```
32+
33+
Finally, we will compute the nullclines. First we create a function which, for species values $(X,Y)$, returns the evaluation of the model's ODE's right-hand side.
34+
```@example nullcline_plotting
35+
nlprob = NonlinearProblem(bs_switch, [:X => 0.0, :Y => 0.0], ps)
36+
function get_XY(Xval, Yval)
37+
prob = Catalyst.remake(nlprob; u0 = [:X => Xval, :Y => Yval])
38+
return nlprob.f(prob.u0, prob.p)
39+
end
40+
nothing # hide
41+
```
42+
Next, we plot our nullclines by using Plot.jl's [`contour` function](https://docs.juliaplots.org/latest/series_types/contour/). Here, we plot the $0$ contour lines (which corresponds to the nullclines) for both the $X$ and $Y$ nullclines. We will plot the steady states using `scatter`. We use the `steady_state_stability` function to [compute steady state stabilities](@ref steady_state_stability) (and use this to determine how to plot the steady state markers).
43+
```@example nullcline_plotting
44+
using Plots
45+
# Plot the nullclines. Line labels added in separate `plot` commands (due to how the `contour` works).
46+
plt_mesh = 0:0.02:1.25
47+
contour(plt_mesh, plt_mesh, (x,y) -> get_XY(x,y)[1]; levels = [0.0], lw = 7, la = 0.7, color = 1, cbar = false)
48+
contour!(plt_mesh, plt_mesh, (x,y) -> get_XY(x,y)[2]; levels = [0.0], lw = 7, la = 0.7, color = 2, cbar = false)
49+
plot!([]; label = "dX/dt = 0", lw = 7, la = 0.7, color = 1)
50+
plot!([]; label = "dY/dt = 0", lw = 7, la = 0.7, color = 2)
51+
52+
# Plot the steady states.
53+
for ss in sss
54+
color, markershape = if steady_state_stability(ss, bs_switch, ps)
55+
:blue, :circle
56+
else
57+
:red, :star4
58+
end
59+
scatter!((ss[2], ss[1]); color, markershape, label = "", markersize = 10)
60+
end
61+
scatter!([], []; color = :blue, markershape = :circle, label = "Stable stead state")
62+
scatter!([], []; color = :red, markershape = :star4, label = "Unstable stead state")
63+
64+
# Finishing touches.
65+
plot!(xguide = "X", yguide = "Y", xlimit = (0.0, 1.25), ylimit = (0.0, 1.25), legendfontsize = 10, size = (600,600), legend = :topright)
66+
```
67+
Here we can see how the steady states occur at the nullclines intersections.
68+
69+
!!! note
70+
Here we use an inherent Plots function to plot the nullclines. However, there are also specialised packages for these kinds of plots, such as [ImplicitPlots.jl](https://github.com/saschatimme/ImplicitPlots.jl).
71+
72+
## [Plotting system directions in phase space](@id nullcline_plotting_directions)
73+
One useful property of nullclines is that the sign of $dX/dt$ will only switch whenever the solution crosses the $dX/dt=0$ nullcline. This means that, within each region defined by the nullclines, the direction of the solution remains constant. Below we use this to, for each such region, plot arrows showing the solution's direction.
74+
```@example nullcline_plotting
75+
# Creates a function for plotting the ODE's direction at a point in phase space.
76+
function plot_xy_arrow!(Xval, Yval)
77+
dX, dY = get_XY(Xval, Yval)
78+
dX = dX > 0 ? 0.05 : -0.05
79+
dY = dY > 0 ? 0.05 : -0.05
80+
plot!([Xval, Xval + dX], [Yval, Yval]; color = :black, lw = 2, arrow = :arrow, label = "")
81+
plot!([Xval, Xval], [Yval, Yval + dY]; color = :black, lw = 2, arrow = :arrow, label = "")
82+
end
83+
84+
# Plots the ODE's direction in phase space at selected positions.
85+
arrow_positions = [(0.25, 0.25), (0.75, 0.75), (0.35, 0.8), (0.8, 0.35), (0.02, 1.1), (1.1, 0.02)]
86+
foreach(pos -> plot_xy_arrow!(pos...), arrow_positions)
87+
plot!()
88+
```
89+
This also works as a form of simple stability analysis, where we can see how the solution moves *away* from the unstable steady state, and *to* the stable ones.

0 commit comments

Comments
 (0)