|
| 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