flowchart LR
R[Growth rate r] --> G[Growth flow rP]
P[Population P] --> G
G --> Pflowchart LR R[Growth rate r] --> G[Growth flow rP] P[Population P] --> G G --> P
Module 2.2
If a population changes at a rate proportional to its current size, how can we predict its future—and how much should we trust a step-by-step simulation?
Download the complete R script. It builds the growth example from this tutorial, checks the first step, and compares the Euler simulation with the analytical solution.
After working through this module, you should be able to:
Read Module 2.2, “Unconstrained Growth and Decay,” before completing this tutorial.
A population begins with 100 individuals and grows at a continuous rate of 10% per hour.
Keep your predictions. We will test them after building the model.
Let \(P(t)\) be the population at time \(t\) and let \(r\) be its per-capita growth rate. Our central assumption is:
The population’s rate of change is proportional to the population currently present.
Mathematically,
\[ \frac{dP}{dt}=rP. \]
| Quantity | Meaning | Example units |
|---|---|---|
| \(P\) | current population | individuals |
| \(P_0\) | initial population | individuals |
| \(r\) | proportional growth rate | per hour |
| \(t\) | time | hours |
| \(\Delta t\) | length of one simulation step | hours |
The population is a stock: an amount accumulated over time. Growth is a flow into that stock. Because the flow \(rP\) depends on the stock itself, this is a positive feedback loop.
flowchart LR R[Growth rate r] --> G[Growth flow rP] P[Population P] --> G G --> P
\(rP\) is a rate with units of individuals per hour. Over a step lasting \(\Delta t\) hours, the approximate change is
\[ \Delta P \approx rP\Delta t. \]
Multiplying by the time step converts a rate into an amount.
Over a short interval, approximate the derivative with
\[ \frac{P_{new}-P_{old}}{\Delta t}\approx rP_{old}. \]
Solving for the new population gives Euler’s update rule:
\[ P_{new}=P_{old}+rP_{old}\Delta t. \]
This has a form we will reuse throughout the course:
\[ \boxed{\text{new amount}=\text{old amount}+\text{rate of change}\times\text{time passed}} \]
With \(P_{old}=100\), \(r=0.10\) per hour, and \(\Delta t=1\) hour,
\[ P_{new}=100+(0.10)(100)(1)=110. \]
The next increase will be larger because the growth flow is calculated from a larger population.
Keeping units in the comments makes it easier to catch mistakes.
Time begins at 0, but R begins counting vector positions at 1. Therefore time[1] contains 0.
The vector will store the population’s entire trajectory. Initially, we know only its first value.
Each iteration reads the previous stock, calculates its flow, converts the flow into a change, and stores the next stock value.
time population
1 0 100.0000
2 1 110.0000
3 2 121.0000
4 3 133.1000
5 4 146.4100
6 5 161.0510
7 6 177.1561
8 7 194.8717
9 8 214.3589
Before trusting a graph, verify that the first two rows agree with the hand calculation: 100 at time 0 and 110 at time 1.

Return to your opening predictions. The curve bends upward because every increase creates a larger population—and therefore an even larger future increase.
Calculus gives the exact solution to this differential equation:
\[ P(t)=P_0e^{rt}. \]
Calculate exact values at the same times and measure the numerical error:
time population exact error
1 0 100.0000 100.0000 0.0000000
2 1 110.0000 110.5171 -0.5170918
3 2 121.0000 122.1403 -1.1402758
4 3 133.1000 134.9859 -1.8858808
5 4 146.4100 149.1825 -2.7724698
6 5 161.0510 164.8721 -3.8211271
7 6 177.1561 182.2119 -5.0557800
8 7 194.8717 201.3753 -6.5035607
9 8 214.3589 222.5541 -8.1952118
plot(
results$time,
results$exact,
type = "l",
lwd = 3,
col = "#214f73",
xlab = "Time (hours)",
ylab = "Population (individuals)",
main = "Euler simulation and analytical solution"
)
lines(results$time, results$population, lwd = 3, lty = 2, col = "#b65335")
legend(
"topleft",
legend = c("Analytical solution", "Euler simulation"),
col = c("#214f73", "#b65335"),
lty = c(1, 2),
lwd = 3,
bty = "n"
)
The code can correctly implement Euler’s method without producing the exact continuous solution. The difference is numerical error caused by replacing continuous change with discrete steps.
To repeat the model easily, place the simulation inside a function:
simulate_growth <- function(initial_population, growth_rate,
end_time, delta_t) {
time <- seq(from = 0, to = end_time, by = delta_t)
population <- numeric(length(time))
population[1] <- initial_population
for (i in 2:length(time)) {
growth <- growth_rate * population[i - 1]
population[i] <- population[i - 1] + growth * delta_t
}
data.frame(time, population)
}Compare several time steps:
time_steps <- c(2, 1, 0.5, 0.25)
exact_at_end <- initial_population * exp(growth_rate * end_time)
final_population <- numeric(length(time_steps))
absolute_error <- numeric(length(time_steps))
for (i in seq_along(time_steps)) {
run <- simulate_growth(
initial_population,
growth_rate,
end_time,
time_steps[i]
)
final_population[i] <- tail(run$population, 1)
absolute_error[i] <- abs(final_population[i] - exact_at_end)
}
data.frame(
delta_t = time_steps,
number_of_steps = end_time / time_steps,
final_population,
absolute_error
) delta_t number_of_steps final_population absolute_error
1 2.00 4 207.3600 15.194093
2 1.00 8 214.3589 8.195212
3 0.50 16 218.2875 4.266634
4 0.25 32 220.3757 2.178399
A smaller time step generally reduces Euler error because the rate is recalculated more often. It also requires more computation. Accuracy is therefore connected to both the numerical method and the time-step choice.
Agreement with the analytical solution helps verify that we implemented the equation correctly. It does not validate the assumption that a real population can grow without limits.
The same equation describes decay when \(r<0\). No new algorithm is needed; changing the sign changes the system’s behavior.
plot(
decay$time,
decay$exact,
type = "l",
lwd = 3,
col = "#214f73",
xlab = "Time (hours)",
ylab = "Amount",
main = "Unconstrained decay"
)
lines(decay$time, decay$population, lwd = 3, lty = 2, col = "#b65335")
legend(
"topright",
legend = c("Analytical solution", "Euler simulation"),
col = c("#214f73", "#b65335"),
lty = c(1, 2),
lwd = 3,
bty = "n"
)
The exact decay curve approaches zero without reaching it. Euler’s method can produce an impossible negative amount if the time step is too large relative to the decay rate.
This model assumes:
Those assumptions may be useful over a limited interval. A numerically accurate answer can still be a poor prediction if the model’s assumptions do not fit reality.
For each experiment, make a prediction, run the model, and explain what happened.
new = old + rate * time passed.In Saving for a Car, the same update pattern controls three quantities: savings, new-car price, and trade-in value.