{"id":2407,"date":"2025-11-06T07:00:26","date_gmt":"2025-11-06T13:00:26","guid":{"rendered":"https:\/\/wollen.org\/blog\/?p=2407"},"modified":"2025-11-15T11:52:01","modified_gmt":"2025-11-15T17:52:01","slug":"the-lotka-volterra-equations","status":"publish","type":"post","link":"https:\/\/wollen.org\/blog\/2025\/11\/the-lotka-volterra-equations\/","title":{"rendered":"The Lotka-Volterra equations"},"content":{"rendered":"<p>The <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lotka%E2%80%93Volterra_equations\" target=\"_blank\" rel=\"noopener\">Lotka-Volterra equations<\/a> are a pair of differential equations used to model population dynamics. They assume an idealized environment with two species\u2014one predator and one prey\u2014and describe how the two populations change over time.<\/p>\n<p>The equations were developed simultaneously by two mathematicians in the 1920s, Alfred Lotka and Vito Volterra. Lotka theorized about oscillating chemical reaction while Volterra tried to make sense of changing fish populations off the coast of Italy during World War I. The equations&#8217; versatility is something I&#8217;ll circle back to later.<\/p>\n<p>Predator-prey models can never <em>perfectly<\/em> describe reality but they still provide a solid foundation for understanding real-world systems. Let&#8217;s take a closer look.<\/p>\n<hr \/>\n<h4>1. The equations.<\/h4>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/equation_dx-dt.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2411\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/equation_dx-dt.png\" alt=\"\" width=\"245\" height=\"78\" \/><\/a><\/p>\n<div style=\"height: 8px;\"><\/div>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/equation_dy-dt.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-2412\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/equation_dy-dt.png\" alt=\"\" width=\"234\" height=\"78\" \/><\/a><\/p>\n<p>The equations describe the rate of change of predator and prey populations with respect to time.<\/p>\n<ul>\n<li>x \u2014 prey population<\/li>\n<li>y \u2014 predator population<\/li>\n<\/ul>\n<p><strong>The key idea<\/strong> is that the rate of change (dx\/dt) depends on the current value (x). In the real world, it means that when a population spikes, it sets itself up for a big contraction. And vice versa. The environment settles into a self-correcting cycle of booms and busts.<\/p>\n<p>Let&#8217;s look at the coefficients and think about what they mean.<\/p>\n<ul>\n<li>\u03b1 (alpha) \u2014 Birth rate of prey. This obviously puts upward pressure on prey population (x) so the term is positive.<\/li>\n<li>\u03b2 (beta) \u2014 Death rate of prey. The term puts downward pressure on prey population. \u03b2xy describes interaction between predator and prey. Either population growing will lead to more encounters. A larger \u03b2 implies that encounters are more likely to end in a kill.<\/li>\n<li><span data-huuid=\"15693143876999511987\">\u03b4<\/span> (delta) \u2014 <span data-huuid=\"15693143876999511987\">\u03b4<\/span>xy is similar in that it describes predator-prey interaction. This time it puts upward pressure on predator population. A higher <span data-huuid=\"15693143876999511987\">\u03b4<\/span> means prey are more efficiently converted into births.<\/li>\n<li>\u03b3 (gamma) \u2014 Natural death rate of predators. This term is independent of prey.<\/li>\n<\/ul>\n<hr \/>\n<h4>2. The solution.<\/h4>\n<p>The equations are considered a system of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ordinary_differential_equation\" target=\"_blank\" rel=\"noopener\">Ordinary Differential Equations<\/a> (ODEs). Sometimes ODEs can be solved analytically. You can manipulate the terms and find that <code>y(t)=3t\u00b2+4<\/code> or something like that.<\/p>\n<p>In this case, we&#8217;ll need to use numerical methods to approximate a solution. <a href=\"https:\/\/tutorial.math.lamar.edu\/classes\/de\/eulersmethod.aspx\" target=\"_blank\" rel=\"noopener\">Euler&#8217;s Method<\/a> is the most intuitive to me. To briefly summarize:<\/p>\n<ul>\n<li>We start with a given point.<\/li>\n<li>We have an equation for the derivative, i.e. slope, at any given point.<\/li>\n<li>So calculate the slope and draw a tiny line segment.<\/li>\n<li>Now we have a new point. Repeat the process from there.<\/li>\n<li>Eventually we&#8217;ll have a series of line segments that approximate the solution.<\/li>\n<\/ul>\n<p>It&#8217;s important to use a small step size (the difference between successive values of t) because error builds up over time. Since we&#8217;re using a modern computer, that&#8217;s not a problem. This technique is much less labor-intensive than it was in <a href=\"https:\/\/euler.euclid.int\/about-leonhard-euler\/\" target=\"_blank\" rel=\"noopener\">Euler&#8217;s<\/a> day.<\/p>\n<p>And since we&#8217;re approximating curves, we will of course plot them.<\/p>\n<hr \/>\n<h4>3. The code.<\/h4>\n<p>First let&#8217;s define coefficients. For our purposes, the values are mostly arbitrary. I think the ones below provide a nice visualization so that&#8217;s what we&#8217;ll do.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">alpha = 1.0\r\nbeta = 0.1\r\ndelta = 0.02\r\ngamma = 0.5<\/pre>\n<p>Next, prepare the time variable, t.<\/p>\n<p><code>dt<\/code> is the time step discussed above. It&#8217;s how much we increment t before calculating a new estimated value of the dependent variable. Visually, it represents the horizontal width of the tiny line segments.<\/p>\n<p>You won&#8217;t be able to see these tiny line segments on the plot. The output will look like a normal smooth curve. That&#8217;s the idea!<\/p>\n<p><code>t_max<\/code> is simply the largest value of t that we&#8217;ll consider. With a step size of 0.001, there will be 40,000 (or really 40,001) elements in <code>t_list<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">from numpy import arange\r\n\r\ndt = 0.001\r\nt_max = 40.0\r\nnum_steps = int(t_max \/ dt)\r\nt_list = arange(0, t_max + dt, dt)<\/pre>\n<p>To use Euler&#8217;s Method, we need an initial point where we can begin drawing tiny line segments. Create <code>x_list<\/code> and <code>y_list<\/code> and give them initial values.<\/p>\n<p>Remember, x (prey population) and y (predator population) are both functions of time. They could be written as x(t) and y(t). Usually y is a function of x but differential equations throw that convention out the window.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">x_list = [10]\r\ny_list = [5]<\/pre>\n<p>Now we&#8217;ll step through a <code>for<\/code> loop to generate line segments.<\/p>\n<p><code>i<\/code> will correspond to the index of <code>x_list<\/code> and <code>y_list<\/code>. We can use it to access the most recent estimated value.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">for i in range(1, num_steps + 1):\r\n    prev_x = x_list[i - 1]\r\n    prev_y = y_list[i - 1]\r\n\r\n    [...]<\/pre>\n<p>From that point, we calculate instantaneous slope using the equations above. The Lotka-Volterra equations isolate dx\/dt and dy\/dt on the left-hand side, so the right-hand side represents slope.<\/p>\n<p>The next value will be slope (rise\/run) multiplied by step size (run). That leaves us with rise, i.e. the vertical change. Add it to the previous value, append to the list, and return to the top of the loop.<\/p>\n<p>Now the <em>previous<\/em> value is the one we just calculated. Draw a line segment beginning there and repeat the process 40,000 times.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">for i in range(1, num_steps + 1):\r\n    prev_x = x_list[i - 1]\r\n    prev_y = y_list[i - 1]\r\n\r\n    dx_dt = alpha * prev_x - beta * prev_x * prev_y\r\n    next_x = prev_x + dx_dt * dt\r\n    x_list.append(next_x)\r\n\r\n    dy_dt = delta * prev_x * prev_y - gamma * prev_y\r\n    next_y = prev_y + dy_dt * dt\r\n    y_list.append(next_y)<\/pre>\n<hr \/>\n<h4>4. Plot the curves.<\/h4>\n<p>I&#8217;ll use a custom Matplotlib style that will be linked at the bottom of this post.<\/p>\n<p>Create an Axes instance (<code>ax<\/code>) for plotting.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import matplotlib.pyplot as plt\r\n\r\nplt.style.use(\"wollen_blue.mplstyle\")\r\n\r\nfig, ax = plt.subplots()<\/pre>\n<p>Now <code>plot<\/code> the two curves, x(t) and y(t).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.plot(t_list, x_list, label=\"Prey (x)\")\r\n\r\nax.plot(t_list, y_list, label=\"Predator (y)\")<\/pre>\n<p>Since we&#8217;re already hard-coding several values in this script, I won&#8217;t feel guilty about hard-coding ticks and window limits as well. We can combine them into a single <code>set<\/code> method along with labels.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.set(xticks=range(0, 45, 5),\r\n       yticks=range(0, 80, 10),\r\n       xlim=(-0.5, 40.5),\r\n       ylim=(0, 70.5),\r\n       xlabel=\"Time (t)\",\r\n       ylabel=\"Population\",\r\n       title=\"Lotka-Volterra Predator-Prey Model\")<\/pre>\n<p>I want to write the Lotka-Volterra equations on the plot using Matplotlib&#8217;s built-in <a href=\"https:\/\/www.cmor-faculty.rice.edu\/~heinken\/latex\/symbols.pdf\" target=\"_blank\" rel=\"noopener\">LaTeX<\/a> rendering. Activate it by putting $ symbols at the beginning and end of a string. Let&#8217;s locate text halfway between the two rightmost grid lines.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.text(37.5, 57.5, r'$\\frac{dx}{dt} = \\alpha x - \\beta x y$', ha=\"center\", va=\"center\")\r\nax.text(37.5, 53.0, r'$\\frac{dy}{dt} = \\delta x y - \\gamma y$', ha=\"center\", va=\"center\")\r\nax.text(37.5, 48.7, r'$\\alpha = ' + f'{alpha:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 46.2, r'$\\beta = ' + f'{beta:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 43.7, r'$\\delta = ' + f'{delta:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 41.2, r'$\\gamma = ' + f'{gamma:.02}' + '$', size=9, ha=\"center\", va=\"center\")<\/pre>\n<p>Place a legend directly above the equations. <code>bbox_to_anchor<\/code> allows for more precise control of the legend&#8217;s location. (0, 0) corresponds to the plot&#8217;s bottom-left and (1, 1) is top-right.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">ax.legend(bbox_to_anchor=(0.93, 0.9), loc=\"center\")<\/pre>\n<p>Finally, save the figure with an increased <code>dpi<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">plt.savefig(\"predator_prey.png\", dpi=150)<\/pre>\n<hr \/>\n<h4>5. The output.<\/h4>\n<p><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-2444 size-full\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey.png\" alt=\"\" width=\"1950\" height=\"1050\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey.png 1950w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey-300x162.png 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey-1024x551.png 1024w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey-768x414.png 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/predator_prey-1536x827.png 1536w\" sizes=\"auto, (max-width: 1950px) 100vw, 1950px\" \/><\/a><\/p>\n<p>I think that&#8217;s a cool result! The model shows that when there are few predators around, prey population booms. But that means plenty of food for predators. They reproduce faster and eventually catch up, leading to a bust in prey population. That means less food for predators, and so on.<\/p>\n<p>Our approximation is pretty good with a time step of <code>dt=0.001<\/code>. We see a change of about 0.2% from peak to peak. If you scaled back step size to 0.01, you would easily see values drift with your naked eye.<\/p>\n<p>Maybe theoretical rabbits and foxes aren&#8217;t that exciting to you, but the Lotka-Volterra equations can be applied in other fields as well. For example, they&#8217;re often used in economics. You can imagine large firms as predators and smaller firms as prey.<\/p>\n<p>Or sellers and buyers in financial markets. Day trading stocks is hard because some of the smartest people in the world are in those markets. Many of them probably build models based on these concepts (and a lot more).<\/p>\n<p>If nothing else, it&#8217;s nice to know that oftentimes you can brute force your way to a differential equation solution with a little code.<!-- HFCM by 99 Robots - Snippet # 15: endmark-python -->\n<span class=\"endmark-python\"><\/span>\n<!-- \/end HFCM by 99 Robots -->\n<\/p>\n<hr style=\"width: 50%;\" \/>\n<figure id=\"attachment_2435\" aria-describedby=\"caption-attachment-2435\" style=\"width: 300px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny.jpeg\"><img loading=\"lazy\" decoding=\"async\" class=\"wp-image-2435 size-medium\" src=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny-300x296.jpeg\" alt=\"\" width=\"300\" height=\"296\" srcset=\"https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny-300x296.jpeg 300w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny-1024x1012.jpeg 1024w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny-768x759.jpeg 768w, https:\/\/wollen.org\/blog\/wp-content\/uploads\/2025\/11\/cute_bunny.jpeg 1080w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2435\" class=\"wp-caption-text\">A cute bunny picture. Photo credit: instagram.com\/mister.carrots.<\/figcaption><\/figure>\n<hr \/>\n<p><a href=\"https:\/\/wollen.org\/misc\/lotka-volterra_2025.zip\"><strong>Download the Matplotlib style.<\/strong><\/a><\/p>\n<p><strong>Full code:<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">from numpy import arange\r\nimport matplotlib.pyplot as plt\r\n\r\n\r\nalpha = 1.0\r\nbeta = 0.1\r\ndelta = 0.02\r\ngamma = 0.5\r\n\r\ndt = 0.001\r\nt_max = 40.0\r\nnum_steps = int(t_max \/ dt)\r\nt_list = arange(0, t_max + dt, dt)\r\n\r\nx_list = [10]\r\ny_list = [5]\r\n\r\nfor i in range(1, num_steps + 1):\r\n    prev_x = x_list[i - 1]\r\n    prev_y = y_list[i - 1]\r\n\r\n    dx_dt = alpha * prev_x - beta * prev_x * prev_y\r\n    next_x = prev_x + dx_dt * dt\r\n    x_list.append(next_x)\r\n\r\n    dy_dt = delta * prev_x * prev_y - gamma * prev_y\r\n    next_y = prev_y + dy_dt * dt\r\n    y_list.append(next_y)\r\n\r\nplt.style.use(\"wollen_blue.mplstyle\")\r\n\r\nfig, ax = plt.subplots()\r\n\r\nax.plot(t_list, x_list, label=\"Prey (x)\")\r\n\r\nax.plot(t_list, y_list, label=\"Predator (y)\")\r\n\r\nax.set(xticks=range(0, 45, 5),\r\n       yticks=range(0, 80, 10),\r\n       xlim=(-0.5, 40.5),\r\n       ylim=(0, 70.5),\r\n       xlabel=\"Time (t)\",\r\n       ylabel=\"Population\",\r\n       title=\"Lotka-Volterra Predator-Prey Model\")\r\n\r\nax.text(37.5, 57.5, r'$\\frac{dx}{dt} = \\alpha x - \\beta x y$', ha=\"center\", va=\"center\")\r\nax.text(37.5, 53.0, r'$\\frac{dy}{dt} = \\delta x y - \\gamma y$', ha=\"center\", va=\"center\")\r\nax.text(37.5, 48.7, r'$\\alpha = ' + f'{alpha:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 46.2, r'$\\beta = ' + f'{beta:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 43.7, r'$\\delta = ' + f'{delta:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\nax.text(37.5, 41.2, r'$\\gamma = ' + f'{gamma:.02}' + '$', size=9, ha=\"center\", va=\"center\")\r\n\r\nax.legend(bbox_to_anchor=(0.93, 0.9), loc=\"center\")\r\n\r\nplt.savefig(\"predator_prey.png\", dpi=150)<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The Lotka-Volterra equations are a pair of differential equations used to model population dynamics. They assume an idealized environment with two species\u2014one predator and one prey\u2014and describe how the two populations change over time. The equations were developed simultaneously by<\/p>\n","protected":false},"author":1,"featured_media":2443,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[40,279],"tags":[501,502,39,22,504,487,496,497,495,503,498,460,489,488,47,499,24,407,75,46,31,491,492,25,494,500,493,490],"class_list":["post-2407","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-math","category-nature","tag-alpha","tag-beta","tag-code","tag-data","tag-delta","tag-differential-equations","tag-euler","tag-eulers-method","tag-fox","tag-gamma","tag-integration","tag-latex","tag-lotka","tag-lotka-volterra","tag-math","tag-mathematics","tag-matplotlib","tag-model","tag-numpy","tag-plot","tag-population","tag-predator","tag-prey","tag-python","tag-rabbit","tag-solution","tag-system","tag-volterra"],"_links":{"self":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2407","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/comments?post=2407"}],"version-history":[{"count":30,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2407\/revisions"}],"predecessor-version":[{"id":3286,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/posts\/2407\/revisions\/3286"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media\/2443"}],"wp:attachment":[{"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/media?parent=2407"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/categories?post=2407"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wollen.org\/blog\/wp-json\/wp\/v2\/tags?post=2407"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}