← All Lessons
Week 11|Science

Modeling and Simulation

Build computer simulations of population growth and discover how scientists predict the future.

Materials for this lesson

  • Laptop (charged)
  • Whiteboard and markers

Warm-Up: The Power of One Tree

🔥 Warm-Up

There are roughly 8 billion people on Earth. What would happen if every single person planted one tree today? How much CO2 would those trees absorb? How much land would they need? Take 3 minutes to estimate before revealing the answer.

Think about:

  • How much space does one tree need?
  • How much CO2 does one tree absorb per year?
  • How much CO2 does humanity produce per year?

Core Lesson: What Is a Model?

Models Are Everywhere

A model is a simplified representation of something real. Scientists, engineers, economists, and game designers all use models. The purpose of a model is to help us understand, predict, and explore systems that are too complex, too large, too small, or too dangerous to study directly.

| Type of Model | Example | What it simplifies | |---------------|---------|-------------------| | Physical model | Globe of the Earth | Shrinks the planet to something you can hold | | Conceptual model | Food web diagram | Shows relationships without every detail | | Mathematical model | E = mc^2 | Describes physics with equations | | Computer simulation | Weather forecast | Runs a mathematical model millions of times |

💡 Key Concept

"All models are wrong, but some are useful." — George Box, statistician. Every model leaves things out. The skill is deciding what to include and what to simplify so that the model is still useful for answering your specific question.

The Simulation Loop

A computer simulation works by stepping through time in small increments. At each step, it applies rules to update the state of the system. The basic structure is:

1. Set initial conditions
2. For each time step:
   a. Apply the rules (equations)
   b. Update the state
   c. Record the results
3. Analyze and visualize

This is exactly what we will build today.

Population Growth: Two Models

Model 1: Exponential Growth

If a population grows by a fixed percentage each time step, you get exponential growth:

N(t+1) = N(t) + r * N(t)

Where:

  • N(t) = population at time t
  • r = growth rate (e.g., 0.1 = 10% growth per time step)

This can also be written as: N(t+1) = N(t) * (1 + r)

💡 Key Concept

Exponential growth starts slow, then explodes. A population of 100 growing at 10% per step becomes: 100, 110, 121, 133, 146, 161, ... After 50 steps it reaches 11,739. After 100 steps: 1,378,061. This is why exponential growth is unsustainable — nothing in nature grows exponentially forever.

Model 2: Logistic Growth (with Carrying Capacity)

In reality, every environment has a limit to how many organisms it can support. This limit is called the carrying capacity (K). As the population approaches K, growth slows down and eventually stops.

N(t+1) = N(t) + r * N(t) * (1 - N(t) / K)

The new term (1 - N(t) / K) acts like a brake:

  • When N is small compared to K, the brake is weak (nearly 1), and growth is fast.
  • When N is close to K, the brake is strong (nearly 0), and growth stalls.
  • When N equals K, growth stops completely.

This produces an S-shaped curve (sigmoid), which is how most real populations grow.

Population Growth — Crash Course Ecology

In the logistic growth equation, what happens when the population N equals the carrying capacity K?

Which of the following shows exponential growth?


Hands-On Lab: Building Simulations in Python

Simulation 1: Exponential Growth

# Exponential Growth Simulation
# ----------------------------------
# A bacteria colony doubles every hour.
# Starting population: 100 bacteria
# Growth rate: 100% per hour (r = 1.0)

population = 100
growth_rate = 1.0
time_steps = 24  # simulate 24 hours

# Store results for display
times = []
populations = []

print("Hour | Population")
print("-" * 30)

for t in range(time_steps + 1):
    times.append(t)
    populations.append(population)
    print(f"  {t:2d}  | {population:,.0f}")
    population = population + growth_rate * population

print(f"\nAfter {time_steps} hours: {populations[-1]:,.0f} bacteria")
print(f"That's a {populations[-1] / populations[0]:,.0f}x increase!")
Tip

Try changing the growth rate. What happens with growth_rate = 0.1 (10% per hour)? What about growth_rate = 0.5? A higher growth rate means faster explosion, but ALL exponential growth eventually becomes enormous — it is only a question of when.

Simulation 2: Logistic Growth

Now let's add a carrying capacity to make the model more realistic.

# Logistic Growth Simulation
# ----------------------------------
# Same bacteria, but now the petri dish can only sustain 10,000 bacteria.

population = 100.0
growth_rate = 0.5
carrying_capacity = 10000
time_steps = 50

times = []
populations = []

print("Step | Population | Growth Rate (effective)")
print("-" * 55)

for t in range(time_steps + 1):
    times.append(t)
    populations.append(population)

    # Effective growth rate slows as population approaches K
    effective_rate = growth_rate * (1 - population / carrying_capacity)
    print(f"  {t:2d}  | {population:8.1f}   | {effective_rate:.4f}")

    # Logistic growth equation
    population = population + growth_rate * population * (1 - population / carrying_capacity)

print(f"\nCarrying capacity: {carrying_capacity}")
print(f"Final population:  {populations[-1]:.1f}")

Simulation 3: Plotting Both Models Together

# Compare Exponential vs Logistic Growth
# Requires matplotlib — run: pip install matplotlib

import matplotlib.pyplot as plt

# Parameters
initial_pop = 100
growth_rate = 0.3
carrying_capacity = 10000
time_steps = 60

# Simulate exponential growth
exp_pop = initial_pop
exp_populations = []
for t in range(time_steps):
    exp_populations.append(exp_pop)
    exp_pop = exp_pop * (1 + growth_rate)

# Simulate logistic growth
log_pop = float(initial_pop)
log_populations = []
for t in range(time_steps):
    log_populations.append(log_pop)
    log_pop = log_pop + growth_rate * log_pop * (1 - log_pop / carrying_capacity)

# Plot
plt.figure(figsize=(10, 6))
plt.plot(range(time_steps), exp_populations, label="Exponential Growth", color="red", linewidth=2)
plt.plot(range(time_steps), log_populations, label="Logistic Growth", color="blue", linewidth=2)
plt.axhline(y=carrying_capacity, color="green", linestyle="--", label=f"Carrying Capacity (K={carrying_capacity})")

plt.xlabel("Time Steps", fontsize=12)
plt.ylabel("Population", fontsize=12)
plt.title("Exponential vs. Logistic Growth", fontsize=14)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.ylim(0, carrying_capacity * 1.5)
plt.tight_layout()
plt.savefig("growth_comparison.png", dpi=150)
plt.show()
print("Graph saved as growth_comparison.png")
Tip

Experiment with parameters! Try these and observe how the logistic curve changes shape:

  • Higher growth_rate: The S-curve reaches the ceiling faster, and may overshoot and oscillate.
  • Lower carrying_capacity: The curve flattens earlier.
  • Higher initial_pop: The curve starts closer to K and flattens almost immediately.

Write down your predictions BEFORE changing the parameter, then run it. Were you right?


Challenge: Predator-Prey Dynamics

🏆 Challenge

The Lotka-Volterra Model: In real ecosystems, species interact. Predators eat prey, which limits the prey population — but if prey declines, predators starve too, and their population drops, which lets prey recover... creating a fascinating cycle.

Your challenge: Modify the simulation to model two species — rabbits (prey) and foxes (predators) — and see what patterns emerge.

The equations are:

  • Rabbits: R(t+1) = R(t) + a*R(t) - b*R(t)*F(t)
    • Rabbits grow naturally at rate a, but are eaten at a rate proportional to how many foxes there are.
  • Foxes: F(t+1) = F(t) + c*R(t)*F(t) - d*F(t)
    • Foxes grow based on how many rabbits they can eat, but die at rate d without food.

In a predator-prey model, what happens to the predator population shortly AFTER the prey population crashes?


Resources