Attachment 'tutorial_lokta-voltera_v2.py'
Download 1 # This example describe how to integrate ODEs with scipy.integrate module, and how
2 # to use the matplotlib module to plot trajectories, directions field and others
3 # useful informations.
4 #
5 # == Presentation of the Lokta-Volterra Model ==
6 #
7 # We will have a look at the Lokta-Volterra model, also known as the
8 # predator-prey equations, which are a pair of first order, non-linear, differential
9 # equations frequently used to describe the dynamics of biological systems in
10 # which two species interact, one a predator and one its prey. They were proposed
11 # independently by Alfred J. Lotka in 1925 and Vito Volterra in 1926 :
12 # du/dt = a*u - b*u*v
13 # dv/dt = -c*v + d*b*u*v
14 #
15 # with the following notations :
16 #
17 # * u : number of prey (for example rabbits)
18 #
19 # * v : number of predators (for example foxes)
20 #
21 # * a, b, c, d are constant parameters defining the behavior of the population :
22 #
23 # + a is the natural growing rate of rabbits, when there's no foxes
24 #
25 # + b is the natural dying rate of rabbits, due to predation
26 #
27 # + c is the natural dying rate of foxes, when there's no rabbits
28 #
29 # + d is the factor descibing how many catched rabbits let create new rabbits
30 #
31 #
32 # We will use X=[u, v] to describe the state of both populations.
33 #
34 # Definition of the equations:
35 #
36 from numpy import *
37 import pylab as p
38
39 # Definition of parameters
40 a = 1.
41 b = 0.1
42 c = 1.5
43 d = 0.75
44
45 def dX_dt(X, t=0):
46 """ Return the growth rate of foxes and rabbits populations. """
47 return array([ a*X[0] - b*X[0]*X[1] ,
48 -c*X[1] + d*b*X[0]*X[1] ])
49 #
50 # === Population equilibrium ===
51 #
52 # Before using scipy to integrate this system, we will have a closer look on
53 # position equilibrium. Equilibrium occurs when the growth rate is equal to 0.
54 # This gives two fixed points:
55 #
56 X_f0 = array([ 0. , 0.])
57 X_f1 = array([ c/(d*b), a/b])
58 all(dX_dt(X_f0) == zeros(2) ) and all(dX_dt(X_f1) == zeros(2)) # => True
59 #
60 # === Stability of the fixed points ===
61 # Near theses two points, the system can be linearized :
62 # dX_dt = A_f*X where A is the Jacobian matrix evaluated at the corresponding point.
63 # We have to define the Jacobian matrix:
64 #
65 def d2X_dt2(X, t=0):
66 """ Return the jacobian matrix evaluated in X. """
67 return array([[a -b*X[1], -b*X[0] ],
68 [b*d*X[1] , -c +b*d*X[0]] ])
69 #
70 # So, near X_f0, which represents the extinction of both species, we have:
71 # A_f0 = d2X_dt2(X_f0) # >>> array([[ 1. , -0. ],
72 # # [ 0. , -1.5]])
73 #
74 # Near X_f0, the number of rabbits increase and the population of foxes decrease.
75 # The origin is a [http://en.wikipedia.org/wiki/Saddle_point saddle point].
76 #
77 # Near X_f1, we have :
78 A_f1 = d2X_dt2(X_f1) # >>> array([[ 0. , -2. ],
79 # [ 0.75, 0. ]])
80
81 # whose eigenvalues are +/- sqrt(c*a).j :
82 lambda1, lambda2 = linalg.eigvals(A_f1) # >>> (1.22474j, -1.22474j)
83
84 # They are imaginary number, so the fox and rabbit populations are periodic and
85 # their period is given by :
86 T_f1 = 2*pi/abs(lambda1) # >>> 5.130199
87 #
88 # == Integrating the ODE using scipy.integate ==
89 #
90 # Now we will use the scipy.integrate module to integrate the ODE.
91 # This module offers a method named odeint, very easy to use to integrade ODE:
92 #
93 from scipy import integrate
94
95 t = linspace(0, 15, 1000) # time
96 X0 = array([10, 5]) # initials conditions: 10 rabbits and 5 foxes
97
98 X, infodict = integrate.odeint(dX_dt, X0, t, full_output=True)
99 infodict['message'] # >>> 'Integration successful.'
100 #
101 # `infodict` is optionnal, and you can omit the `full_output` argument if you don't want it.
102 # type "info(odeint)" if you want more information about odeint inputs and outputs.
103 #
104 # We can now use Matplotlib to plot the evolution of both populations:
105 #
106 rabbits, foxes = X.T
107
108 f1 = p.figure()
109 p.plot(t, rabbits, 'r-', label='Rabbits')
110 p.plot(t, foxes , 'b-', label='Foxes')
111 p.grid()
112 p.legend(loc='best')
113 p.xlabel('time')
114 p.ylabel('population')
115 p.title('Evolution of fox and rabbit populations')
116 f1.savefig('rabbits_and_foxes_1.png')
117 #
118 #
119 # The populations are indeed periodic, and their period is near to the T_f1 we calculated.
120 #
121 # == Plotting directions field and trajectories in the phase plane ==
122 #
123 # We will plot some trajectories in a phase plane for different starting
124 # points between X__f0 and X_f1.
125 #
126 # We will ue matplotlib's colormap to define colors for the trajectories.
127 # These colormaps are very useful to make nice plots.
128 # Have a look at [http://www.scipy.org/Cookbook/Matplotlib/Show_colormaps ShowColormaps] if you want more information.
129 #
130 values = linspace(0.3, 0.9, 5) # position of X0 between X_f0 and X_f1
131 vcolors = p.cm.autumn_r(linspace(0.3, 1., len(values))) # colors for each trajectory
132
133 f2 = p.figure()
134
135 #-------------------------------------------------------
136 # plot trajectories
137 for v, col in zip(values, vcolors):
138 X0 = v * X_f1 # starting point
139 X = integrate.odeint( dX_dt, X0, t) # we don't need infodict here
140 p.plot( X[:,0], X[:,1], lw=3.5*v, color=col, label='X0=(%.f, %.f)' % ( X0[0], X0[1]) )
141
142 #-------------------------------------------------------
143 # define a grid and compute direction at each point
144 ymax = p.ylim(ymin=0)[1] # get axis limits
145 xmax = p.xlim(xmin=0)[1]
146 nb_points = 20
147
148 x = linspace(0, xmax, nb_points)
149 y = linspace(0, ymax, nb_points)
150
151 X1 , Y1 = meshgrid(x, y) # create a grid
152 DX1, DY1 = dX_dt([X1, Y1]) # compute growth rate on the gridt
153 M = (hypot(DX1, DY1)) # Norm of the growth rate
154 M[ M == 0] = 1. # Avoid zero division errors
155 DX1 /= M # Normalize each arrows
156 DY1 /= M
157
158 #-------------------------------------------------------
159 # Drow direction fields, using matplotlib 's quiver function
160 # I choose to plot normalized arrows and to use colors to give information on
161 # the growth speed
162 p.title('Trajectories and direction field')
163 Q = p.quiver(X1, Y1, DX1, DY1, M, pivot='mid', cmap=p.cm.jet)
164 p.xlabel('Number of Rabbits')
165 p.ylabel('Number of Foxes')
166 p.legend()
167 p.grid()
168 p.xlim(0, xmax)
169 p.ylim(0, ymax)
170 f2.savefig('rabbits_and_foxes_2.png')
171 #
172 #
173 # We can see on this graph that an intervention on fox or rabbit populations can
174 # have non intuitive effects. If, in order to decrease the number of rabbits,
175 # we introduce foxes, this can lead to an increase of rabbits in the long run,
176 # if that intervention happens at a bad moment.
177 #
178 #
179 # == Plotting contours ==
180 #
181 # We can verify that the fonction IF defined below remains constant along a trajectory:
182 #
183 def IF(X):
184 u, v = X
185 return u**(c/a) * v * exp( -(b/a)*(d*u+v) )
186
187 def IF2(X):
188 u, v = X
189 return u**(c/a) * v * exp( -(b/a)*(d*u+v) )
190
191 # We will verify that IF remains constant for differents trajectories
192 for v in values:
193 X0 = v * X_f1 # starting point
194 X = integrate.odeint( dX_dt, X0, t)
195 I = IF(X.T) # compute IF along the trajectory
196 I_mean = I.mean()
197 delta = 100 * (I.max()-I.min())/I_mean
198 print 'X0=(%2.f,%2.f) => I ~ %.1f |delta = %.3G %%' % (X0[0], X0[1], I_mean, delta)
199
200 # >>> X0=( 6, 3) => I ~ 20.8 |delta = 6.19E-05 %
201 # X0=( 9, 4) => I ~ 39.4 |delta = 2.67E-05 %
202 # X0=(12, 6) => I ~ 55.7 |delta = 1.82E-05 %
203 # X0=(15, 8) => I ~ 66.8 |delta = 1.12E-05 %
204 # X0=(18, 9) => I ~ 72.4 |delta = 4.68E-06 %
205 #
206 # Potting iso-contours of IF can be a good representation of trajectories,
207 # without having to integrate the ODE
208 #
209 #-------------------------------------------------------
210 # plot iso contours
211 nb_points = 80 # grid size
212
213 x = linspace(0, xmax, nb_points)
214 y = linspace(0, ymax, nb_points)
215
216 X2 , Y2 = meshgrid(x, y) # create the grid
217 Z2 = IF([X2, Y2]) # compute IF on each point
218
219 f3 = p.figure()
220 CS = p.contourf(X2, Y2, Z2, cmap=p.cm.Purples_r, alpha=0.5)
221 CS2 = p.contour(X2, Y2, Z2, colors='black', linewidths=2. )
222 p.clabel(CS2, inline=1, fontsize=16, fmt='%.f')
223 p.grid()
224 p.xlabel('Number of Rabbits')
225 p.ylabel('Number of Foxes')
226 p.ylim(1, ymax)
227 p.xlim(1, xmax)
228 p.title('IF contours')
229 f3.savefig('rabbits_and_foxes_3.png')
230 p.show()
231 #
232 #
233 # # vim: set et sts=4 sw=4:
New Attachment
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.- [get | view] (2007-11-11 16:53:16, 54.4 KB) [[attachment:rabbits_and_foxes_1.png]]
- [get | view] (2007-11-11 20:37:46, 54.4 KB) [[attachment:rabbits_and_foxes_1v2.png]]
- [get | view] (2007-11-11 16:53:39, 131.6 KB) [[attachment:rabbits_and_foxes_2.png]]
- [get | view] (2007-11-11 17:43:42, 131.9 KB) [[attachment:rabbits_and_foxes_2v2.png]]
- [get | view] (2007-11-11 20:38:36, 131.6 KB) [[attachment:rabbits_and_foxes_2v3.png]]
- [get | view] (2007-11-11 16:54:58, 110.6 KB) [[attachment:rabbits_and_foxes_3.png]]
- [get | view] (2007-11-11 20:39:02, 110.5 KB) [[attachment:rabbits_and_foxes_3v2.png]]
- [get | view] (2007-11-11 17:04:20, 8.3 KB) [[attachment:tutorial_lokta-voltera.py]]
- [get | view] (2007-11-11 17:43:02, 8.4 KB) [[attachment:tutorial_lokta-voltera_v2.py]]
- [get | view] (2007-11-11 17:52:29, 8.3 KB) [[attachment:tutorial_lokta-voltera_v3.py]]
- [get | view] (2007-11-11 20:37:14, 8.3 KB) [[attachment:tutorial_lokta-voltera_v4.py]]
- [get | view] (2011-04-24 19:52:43, 38.7 KB) [[attachment:zombie_nodead_nobirths.png]]
- [get | view] (2011-04-24 19:53:34, 42.4 KB) [[attachment:zombie_somedead_10births.png]]
- [get | view] (2011-04-24 19:53:17, 42.3 KB) [[attachment:zombie_somedead_nobirths.png]]
- [get | view] (2011-04-24 19:53:01, 42.3 KB) [[attachment:zombie_somedeaddead_nobirths.png]]