Skip to content

Commit d7cf00a

Browse files
committed
add phase portrait example
1 parent d4af1a9 commit d7cf00a

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

examples/misc/phase_plot_ode.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""
2+
Phase portrait of ordinary differential equations
3+
=================================================
4+
5+
Very basic example of how to plot the trajectories of a few trajectories that solve an ordinary differential equation
6+
given some constants that help define the starting points
7+
"""
8+
9+
# test_example = false
10+
# sphinx_gallery_pygfx_docs = 'screenshot'
11+
12+
import numpy as np
13+
import scipy
14+
15+
import fastplotlib as fpl
16+
17+
18+
def create_and_solve_ode(
19+
t_start: float,
20+
t_end: float,
21+
n_t: int,
22+
p: float,
23+
q: float,
24+
A: np.ndarray,
25+
coef: float = 1.0,
26+
) -> float:
27+
"""
28+
Given an ODE of the form:
29+
30+
x' = Ax
31+
32+
where:
33+
x' ∈ R^2
34+
x ∈ R^2, x is a function of x, i.e. x(t)
35+
A ∈ R^2x2
36+
37+
We can solve this ODE through an eigendecomposition of A:
38+
39+
A η_i = λ_i η_i
40+
41+
where λ_i, η_i are the eigenvalues and eigenvectors of A.
42+
43+
Thus this gives us the following solution for x(t):
44+
45+
x(t) = p * η_1 * e^(λ_1 * t) + q * η_2 * e^(λ_2 * t)
46+
47+
where p and q are constants that determine the initial conditions
48+
49+
Parameters
50+
----------
51+
t_start: float
52+
t_end: float
53+
p: float
54+
q: float
55+
A: np.ndarray
56+
coef: float
57+
58+
Returns
59+
-------
60+
61+
np.ndarray
62+
63+
"""
64+
65+
matrix = A * np.array([[1, 1], [1 / coef, 1 / coef]])
66+
67+
(lam1, lam2), evecs = scipy.linalg.eig(matrix)
68+
eta1 = evecs[:, 0]
69+
eta2 = evecs[:, 1]
70+
71+
x_t = np.zeros((n_t, 2))
72+
73+
for i, t in enumerate(np.linspace(t_start, t_end, n_t)):
74+
x_t[i] = (p * eta1 * np.exp(lam1 * t)) + (q * eta2 * np.exp(lam2 * t))
75+
76+
return x_t
77+
78+
79+
# constants range
80+
START, STOP = -2, 2
81+
82+
T_START, T_END = -2, 5
83+
84+
A = np.array([
85+
[0, 1],
86+
[-1, -1]
87+
])
88+
89+
x_t_collection = list()
90+
x_t_vel_collection = list()
91+
92+
for p in np.linspace(START, STOP, 8):
93+
for q in np.linspace(START, STOP, 8):
94+
n_t = 100
95+
96+
x_t_vel = np.zeros(n_t)
97+
98+
x_t = create_and_solve_ode(
99+
t_start=T_START,
100+
t_end=T_END,
101+
n_t=n_t,
102+
p=p,
103+
q=q,
104+
A=A,
105+
coef=0.5,
106+
)
107+
108+
x_t_vel[:-1] = np.linalg.norm(x_t[:-1] - x_t[1:], axis=1, ord=2)
109+
x_t_vel[-1] = x_t_vel[-2]
110+
111+
x_t_collection.append(x_t)
112+
x_t_vel_collection.append(x_t_vel)
113+
114+
fig = fpl.Figure()
115+
116+
lines = fig[0, 0].add_line_collection(x_t_collection, cmap="viridis", cmap_transform=x_t_vel_collection)
117+
for (line, vel) in zip(lines, x_t_vel_collection):
118+
line.cmap = "viridis"
119+
120+
# color indicates velocity along trajectory
121+
line.cmap.transform = vel
122+
print(vel)
123+
124+
fig[0, 0].axes.intersection = (0, 0, 0)
125+
126+
fig.show()
127+
128+
# NOTE: `if __name__ == "__main__"` is NOT how to use fastplotlib interactively
129+
# please see our docs for using fastplotlib interactively in ipython and jupyter
130+
if __name__ == "__main__":
131+
print(__doc__)
132+
fpl.loop.run()

0 commit comments

Comments
 (0)