Exponential vs logistic growth
Both models start from the same per-capita growth rate r. Exponential growth keeps it forever; logistic growth scales it by the fraction of capacity still free:
While N is a small fraction of K the factor 1 − N/K is close to 1 and the two curves are indistinguishable: the population doubles every ln 2/r time units. They part company as crowding sets in. The logistic curve is steepest exactly at N = K/2, its inflection point, where the growth rate reaches its maximum rK/4. For N₀ < K/2 that happens at
After the inflection point growth slows symmetrically: the S-curve has the same shape on both sides of it. A population that starts above K simply declines toward K. The lower chart shows why: the growth rate is a parabola in N, positive between 0 and K and negative above K.
Harvesting and the collapse threshold
Now remove a constant number h of individuals per time unit — a fixed fishing quota, say:
In the lower chart the harvest is the horizontal line. Where it crosses the parabola, growth exactly replaces the catch:
- The upper root N*₊ is stable: a little above it the catch exceeds growth and N falls back; a little below, growth wins.
- The lower root N*₋ is a tipping point: start below it, or get pushed below it, and harvesting outruns growth all the way to zero.
- Above h = rK/4 there is no equilibrium at all. The line clears the top of the parabola, dN/dt is negative for every N, and the population collapses from any starting size. rK/4 is the maximum sustainable yield of this model, taken at N = K/2.
With the defaults (r = 0.5, K = 1,000) the threshold is 125. Set N₀ to 1,000 and h to 100: the population settles at N*₊ ≈ 723.6. Raise h to 130 and set T to 100: the equilibria vanish, yet the decline stalls near K/2 for a long time — the ghost of the lost equilibrium — before the population crashes at t ≈ 54.9. Near the threshold, a harvest that looks stable for years can still be fatal. The two roots merging and vanishing is a saddle-node bifurcation.
How the curves are computed
Where a closed form exists the lab uses it: the exponential curve always, and the logistic curve when h = 0. With harvesting it integrates the equation with the classical fourth-order Runge–Kutta method (RK4), 4,000 fixed steps over the time span. Once N reaches zero it stays there — the model treats extinction as permanent, and the collapse time is interpolated inside the step where N crosses zero.
For h = 0 the lab also runs RK4 and reports its largest deviation from the exact solution, relative to K, as an accuracy check; Euler vs Runge–Kutta explains why the error is so small.
Example result
With r = 0.5, K = 1,000, N₀ = 10 and no harvest, the population doubles every 1.39 time units at first, passes the inflection point N = K/2 at t = 9.19 with the maximum growth rate of 125.0 per time unit, and reaches 1,000.0 by t = 30. Exponential growth from the same start would reach 3.27e+7. RK4 matches the exact logistic curve to within 1.1e-12 of K.
| Outcome | grows toward K |
|---|---|
| Logistic N(T) | 1,000.0 |
| Exponential N₀eʳᵀ | 3.27e+7 |
| Doubling time ln 2 / r | 1.39 |
| Inflection time (N = K/2) | 9.19 |
| Max growth rate rK/4 | 125.0 |
| Stable equilibrium N*₊ | 1,000.0 |
| Unstable equilibrium N*₋ | 0.0 |
| Collapse time (N = 0) | — |
| max |RK4 − exact| / K (h = 0) | 1.1e-12 |
Implementation
The page runs simulation.js from the package below; the Python file is a line-by-line port with the same random-number generator. The excerpts show the core of each.
// dN/dt = rN(1 − N/K) − h. With h = 0 this is Verhulst's logistic equation.
export const rate = (n, r, k, h) => r * n * (1 - n / k) - h;
// Closed forms: exponential growth dN/dt = rN, and the logistic curve for h = 0.
export const exponential = (t, r, n0) => n0 * Math.exp(r * t);
export const logistic = (t, r, k, n0) => k / (1 + ((k - n0) / n0) * Math.exp(-r * t));
// Equilibria of the harvested model: rN(1 − N/K) = h has real roots only if h ≤ rK/4.
export function equilibria(r, k, h) {
const disc = (k * k) / 4 - (h * k) / r;
if (disc < 0) return null; // harvest exceeds the maximum growth rate: no equilibrium
const s = Math.sqrt(disc);
return { stable: k / 2 + s, unstable: k / 2 - s };
}
// One RK4 step of size dt.
export function rk4Step(n, dt, r, k, h) {
const k1 = rate(n, r, k, h);
const k2 = rate(n + (dt / 2) * k1, r, k, h);
const k3 = rate(n + (dt / 2) * k2, r, k, h);
const k4 = rate(n + dt * k3, r, k, h);
return n + (dt / 6) * (k1 + 2 * k2 + 2 * k3 + k4);
}def rate(n, r, k, h):
"""dN/dt = r*N*(1 - N/K) - h; h = 0 is Verhulst's logistic equation."""
return r * n * (1 - n / k) - h
def exponential(t, r, n0):
return n0 * math.exp(r * t)
def logistic(t, r, k, n0):
return k / (1 + (k - n0) / n0 * math.exp(-r * t))
def equilibria(r, k, h):
"""Roots of r*N*(1 - N/K) = h; none when h > r*K/4."""
disc = k * k / 4 - h * k / r
if disc < 0:
return None
s = math.sqrt(disc)
return {"stable": k / 2 + s, "unstable": k / 2 - s}
def rk4_step(n, dt, r, k, h):
k1 = rate(n, r, k, h)
k2 = rate(n + dt / 2 * k1, r, k, h)
k3 = rate(n + dt / 2 * k2, r, k, h)
k4 = rate(n + dt * k3, r, k, h)
return n + dt / 6 * (k1 + 2 * k2 + 2 * k3 + k4)Run the full model from a terminal:
node simulation.js --r 0.5 --k 1000
python simulation.py --r 0.5 --k 1000Download the model
| File | Contents | Size |
|---|---|---|
README.md | How to run it, parameters, example result | 2.3 KB |
model.json | Parameters with ranges and defaults, outputs, the seed-42 example | 2.8 KB |
simulation.js | The model used on this page plus a command-line runner (Node 18+) | 9.3 KB |
simulation.py | Python 3.9+ port, standard library only | 7.8 KB |
example.csv | Result table from the example run | 17.8 KB |
LICENSE.txt | MIT License | 1.0 KB |
Assumptions & limitations
- Linear crowding. The per-capita growth rate falls in a straight line from r at N = 0 to zero at N = K. Real populations often show thresholds or Allee effects (poor growth at very low density).
- Constant environment. r and K do not change with seasons, weather or evolution, and the population responds instantly, with no age structure or maturation delay.
- Continuous and deterministic. N is a real number, not a count of individuals, and there is no chance: a population of 3.2 behaves like any other. Small populations are in reality vulnerable to random extinction.
- Constant harvest. The catch h is fixed regardless of stock size. Proportional (effort-based) harvesting behaves differently and does not collapse this way.
- Extinction at zero. Once N reaches 0 the model stops harvesting and N stays at 0.
References
- Verhulst, P.-F. (1838). Notice sur la loi que la population suit dans son accroissement. Correspondance mathématique et physique, 10, 113–121.
- Murray, J. D. (2002). Mathematical Biology I: An Introduction, 3rd ed. Springer.
- Strogatz, S. H. (2015). Nonlinear Dynamics and Chaos, 2nd ed. Westview Press.