Constrained Growth

Module 2.3

TipThe modeling question

How does population growth change when resources become limited—and how can a simple feedback term keep a model from growing forever?

NoteDownload the example

Download the complete R script. It builds the constrained-growth model, graphs the population and carrying capacity, and answers two questions about the simulation.

Learning goals

After working through this module, you should be able to:

  • explain why unconstrained growth eventually becomes unrealistic;
  • interpret carrying capacity and the constraint term;
  • translate the constrained-growth differential equation into an Euler update;
  • implement and graph the model in R;
  • identify equilibria and the region of fastest growth; and
  • use simulation results to answer threshold questions.
NoteTextbook

Read Module 2.3, “Constrained Growth,” before completing this tutorial.

Begin with a prediction

Suppose a population begins at 20 individuals, grows at 8% per year, and lives in an environment that can support about 500 individuals.

  • Will the population ever exceed 500?
  • Will it gain the same number every year?
  • When will growth be fastest: near 20, near 250, or near 500?
  • What shape should its graph have?

Add a constraint

The unconstrained model is

\[ \frac{dP}{dt}=rP. \]

It assumes that the same proportional growth rate continues no matter how large the population becomes. To represent limited food, space, or other resources, introduce a carrying capacity \(K\):

\[ \frac{dP}{dt}=rP\left(1-\frac{P}{K}\right). \]

Quantity Meaning Example units
\(P\) current population individuals
\(P_0\) initial population individuals
\(r\) unconstrained proportional growth rate per year
\(K\) carrying capacity individuals
\(t\) time years
\(\Delta t\) length of one simulation step years

The new factor

\[ 1-\frac{P}{K} \]

measures the fraction of the environment’s capacity that remains available.

Current population Constraint term What the model does
\(P\) is small compared with \(K\) close to 1 behaves almost like unconstrained growth
\(P=K/2\) \(1/2\) growth remains positive but is reduced
\(P=K\) 0 population remains constant
\(P>K\) negative population decreases toward capacity
ImportantNegative feedback

As the population grows, the constraint term becomes smaller. That reduces the growth flow, which slows further population growth. This balancing feedback is what keeps the model near carrying capacity.

flowchart LR
  R[Growth rate r] --> G[Growth flow]
  P[Population P] --> G
  K[Carrying capacity K] --> C[Constraint 1 - P/K]
  P --> C
  C --> G
  G --> P

flowchart LR
  R[Growth rate r] --> G[Growth flow]
  P[Population P] --> G
  K[Carrying capacity K] --> C[Constraint 1 - P/K]
  P --> C
  C --> G
  G --> P

The Euler update

Over a time step of length \(\Delta t\), approximate the change with

\[ \Delta P \approx rP_{old}\left(1-\frac{P_{old}}{K}\right)\Delta t. \]

Therefore,

\[ P_{new}=P_{old}+rP_{old}\left(1-\frac{P_{old}}{K}\right)\Delta t. \]

This is still the same reusable simulation pattern:

\[ \boxed{\text{new amount}=\text{old amount}+\text{rate of change}\times\text{time passed}} \]

Check one step by hand

Use \(P_{old}=20\), \(r=0.08\) per year, \(K=500\), and \(\Delta t=1\) year.

\[ \text{constraint}=1-\frac{20}{500}=0.96 \]

\[ \text{growth flow}=(0.08)(20)(0.96)=1.536 \]

\[ P_{new}=20+(1.536)(1)=21.536. \]

Build the simulation in R

1. Define time, parameters, and the initial condition

start_time <- 0             # years
end_time <- 100             # years
delta_t <- 1                # years per step

growth_rate <- 0.08         # per year
carrying_capacity <- 500    # individuals
initial_population <- 20    # individuals

2. Create the time points

time <- seq(from = start_time, to = end_time, by = delta_t)

3. Create and initialize the stock

population <- numeric(length(time))
population[1] <- initial_population

4. Advance through time

for (i in 2:length(time)) {
  population_old <- population[i - 1]
  constraint <- 1 - population_old / carrying_capacity
  growth <- growth_rate * population_old * constraint
  change <- growth * delta_t

  population[i] <- population_old + change
}

The only change from Module 2.2 is the constraint term inside the growth flow.

5. Organize and verify the results

results <- data.frame(time, population)
results$constraint <- 1 - results$population / carrying_capacity
results$growth <- growth_rate * results$population * results$constraint

head(results)
  time population constraint   growth
1    0   20.00000  0.9600000 1.536000
2    1   21.53600  0.9569280 1.648672
3    2   23.18467  0.9536307 1.768769
4    3   24.95344  0.9500931 1.896647
5    4   26.85009  0.9462998 2.032659
6    5   28.88275  0.9422345 2.177146

Check that the first update agrees with the hand calculation.

6. Graph the population and capacity

plot(
  results$time,
  results$population,
  type = "l",
  lwd = 3,
  col = "#214f73",
  ylim = c(0, carrying_capacity * 1.1),
  xlab = "Time (years)",
  ylab = "Population (individuals)",
  main = "Constrained population growth"
)

abline(
  h = carrying_capacity,
  lty = 2,
  lwd = 2,
  col = "#b65335"
)

legend(
  "bottomright",
  legend = c("Population", "Carrying capacity"),
  col = c("#214f73", "#b65335"),
  lty = c(1, 2),
  lwd = c(3, 2),
  bty = "n"
)

The resulting S-shaped curve is called logistic growth. Growth is initially slow because the population is small, becomes faster as the population increases, and then slows as the population approaches carrying capacity.

Use the model to answer questions

When does the population reach 90% of capacity?

target_population <- 0.90 * carrying_capacity
target_positions <- which(results$population >= target_population)
first_target <- target_positions[1]

results[c(first_target - 1, first_target), ]
   time population constraint   growth
68   67   447.6636 0.10467276 3.748655
69   68   451.4123 0.09717545 3.509295

Inspecting the crossing row and the preceding row confirms that we found the first simulated time at or above the target.

When is growth fastest?

The growth column records the modeled flow at each time. Find its largest value:

fastest_position <- which(results$growth == max(results$growth))[1]
results[fastest_position, ]
   time population constraint   growth
42   41   253.4035  0.4931931 9.998147

For this model, growth is fastest when the population is near \(K/2\). Before that point, adding more individuals increases the total growth flow. After that point, resource limitation has the stronger effect.

Compare with unconstrained growth

Use the same initial population and growth rate, but remove the constraint:

unconstrained_population <- numeric(length(time))
unconstrained_population[1] <- initial_population

for (i in 2:length(time)) {
  population_old <- unconstrained_population[i - 1]
  growth <- growth_rate * population_old
  change <- growth * delta_t

  unconstrained_population[i] <- population_old + change
}
plot(
  time,
  population,
  type = "l",
  lwd = 3,
  col = "#214f73",
  ylim = c(0, 1000),
  xlab = "Time (years)",
  ylab = "Population (individuals)",
  main = "Constrained and unconstrained growth"
)
lines(time, unconstrained_population, lwd = 3, lty = 2, col = "#b65335")
abline(h = carrying_capacity, lty = 3)
legend(
  "topleft",
  legend = c("Constrained", "Unconstrained", "Capacity"),
  col = c("#214f73", "#b65335", "black"),
  lty = c(1, 2, 3),
  lwd = c(3, 3, 1),
  bty = "n"
)

The unconstrained model eventually exceeds any fixed bound. The constrained model instead approaches an equilibrium near \(K\).

Start above carrying capacity

Carrying capacity is not a wall that the population can never cross. It is an equilibrium toward which the model moves. Test an initial population above capacity:

above_population <- numeric(length(time))
above_population[1] <- 650

for (i in 2:length(time)) {
  population_old <- above_population[i - 1]
  constraint <- 1 - population_old / carrying_capacity
  growth <- growth_rate * population_old * constraint
  change <- growth * delta_t

  above_population[i] <- population_old + change
}

plot(
  time,
  above_population,
  type = "l",
  lwd = 3,
  col = "#6b4c8a",
  ylim = c(0, 700),
  xlab = "Time (years)",
  ylab = "Population (individuals)",
  main = "Starting above carrying capacity"
)
abline(h = carrying_capacity, lty = 2, col = "#b65335")

When \(P>K\), the constraint is negative, so the modeled change is negative. The population moves downward toward carrying capacity.

Compare with the analytical solution

The logistic differential equation has the exact solution

\[ P(t)=\frac{K}{1+\left(\frac{K-P_0}{P_0}\right)e^{-rt}}. \]

results$exact <- carrying_capacity /
  (1 + ((carrying_capacity - initial_population) / initial_population) *
    exp(-growth_rate * results$time))

results$error <- results$population - results$exact
tail(results)
    time population  constraint    growth    exact     error
96    95   494.3547 0.011290587 0.4465244 494.0659 0.2888496
97    96   494.8012 0.010397538 0.4115772 494.5171 0.2841384
98    97   495.2128 0.009574384 0.3793086 494.9344 0.2784404
99    98   495.5921 0.008815767 0.3495220 495.3202 0.2719300
100   99   495.9416 0.008116723 0.3220337 495.6769 0.2647617
101  100   496.2637 0.007472656 0.2966726 496.0066 0.2570725

As in Module 2.2, a smaller time step generally brings the Euler simulation closer to the analytical solution. That verifies the numerical implementation; it does not prove that the model’s assumptions fit a real population.

Assumptions and limits

The constrained-growth model assumes:

  • carrying capacity remains constant;
  • the population responds immediately to crowding;
  • all individuals are interchangeable;
  • the environment is well mixed;
  • births and deaths can be represented by continuous rates; and
  • random events and outside influences are negligible.

Real populations may overshoot a changing capacity, experience delays, migrate, or fluctuate because of seasons and random events. The logistic model is useful because it captures one important feedback—not because it captures everything.

Further experiments

For each experiment, make a prediction before changing the code.

  1. Double the carrying capacity. What changes, and what stays similar?
  2. Double the growth rate. Does the final equilibrium change?
  3. Begin exactly at carrying capacity. What happens?
  4. Begin at population 0. Can the model create individuals from nothing?
  5. Use a very large time step. Can the numerical method overshoot or behave strangely?
  6. Find when the population first reaches 50%, 75%, and 95% of capacity.

What to remember

  1. Carrying capacity represents a sustainable equilibrium, not an impenetrable ceiling.
  2. The constraint \(1-P/K\) weakens growth as the population increases.
  3. Constrained growth uses the same Euler pattern as unconstrained growth.
  4. Growth is fastest near half of carrying capacity.
  5. Populations below or above capacity move toward \(K\) in this model.
  6. Numerical accuracy and model realism are different questions.
Back to top