This page presents theoretical models as a mock foraging case using the Marginal Value Theorem.
Wawa is an orangutan living in the dense rain forests of Kalimantan.
She spends her days searching for food to sustain herself and her
offspring. Fruit is her favourite, but its availability varies greatly
across the forest.
Wawa moves between different areas of the forest, which we can consider
as \(patches\). These patches might be
different types of trees (e.g., fig trees, durian trees) or areas with a
high density of a particular food source. Traveling between these
patches requires time and energy. This travel time is Wawa’s \(τ\). Each tree or area within a patch
offers a certain amount of fruit, but Wawa can’t eat all the fruit on a
tree instantaneously.
The Challenge:
Wawa is currently in a patch of fig trees. These trees are full of ripe
figs, and she’s enjoying a feast. Initially, she’s eating fruit quickly.
This is represented by a high instantaneous gain rate, \(g(t)\). Her cumulative food gain, \(G(t)\), is increasing rapidly.
However, several factors complicate her foraging:
- Depletion by Wawa: As Wawa eats figs, the supply
within the patch dwindles. The remaining figs are harder to find or less
ripe, and it takes her more time and energy to get the same amount of
food. Her instantaneous gain rate, \(g(t)\), begins to decrease.
- Competition: Wawa isn’t alone in the rain forest.
Other animals consume fruits, reducing the amount available to Wawa,
while deforestation also happens. This means the overall rate of
depletion in the patch is faster than if Wawa were the only one
foraging.
- Fruit Ripeness: The figs on the trees ripen at
different rates. Over time, some figs become overripe and fall to the
ground (making them harder to get to, and less nutritious), while others
are still unripe and not worth eating. This changes the quality of the
food patch, as the proportion of edible fruit decreases.
Wawa’s Decision:
Wawa must decide when to leave this patch of fig trees and move to a new
one. If she stays too long, she’ll be spending a lot of time searching
for the few remaining edible figs, and her instantaneous gain rate,
\(g(t)\), will be very low. This will
reduce her overall foraging efficiency.
However, moving to a new patch also requires time and energy \((τ)\). If she leaves the fig trees too soon, she’ll spend a large portion of her time traveling, and she won’t have enough food (\(G(t)\) will be small) to compensate for the travel.
The Question:
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.4 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Instantaneous gain rate: current harvest rate g(t) at time t, assuming starts at rate patch_qual and decays exponentially at rate deplete_rate
gain_rate <- function(t, patch_qual, deplete_rate) {
patch_qual * exp(-deplete_rate * t)
}
MVT Simulator : Decision to move based on patch quality
# Simulate MVT for 50 patches
# Use smaller time step (ts) to capture gain rate in detail
MVT <- function(moving_time, patch_qual, deplete_rate, ts=0.1, n_patches=50) {
total_gain <- 0 # food collected so far
total_time <- 0 # time spent so far
rate_est <- patch_qual / deplete_rate # long-term rate initial guess
leave_times <- numeric(n_patches) #leave time from each patch
# loop over n_patches
for(i in seq_len(n_patches)) {
t <- 0 # start time at 0
current_fruits <- 0 # initial fruits gained is 0
# stay while instantaneous rate ≥ current rate_est (gain rate estimation)
while (gain_rate(t, patch_qual, deplete_rate) >= rate_est) {
current_fruits <- current_fruits + gain_rate(t, patch_qual, deplete_rate) * ts
t <- t + ts
}
leave_times[i] <- t
total_gain <- total_gain + current_fruits
total_time <- total_time + t + moving_time
rate_est <- total_gain / total_time
}
list(
rate_long = total_gain / total_time,
t_leave = leave_times
)
}
GUT Simulator : Decision to move based on predetermined giving-up time
GUT <- function(moving_time, patch_qual, deplete_rate, gut_time, ts=0.1, n_patches=50) {
total_gain <- 0 # initial gain total, also to hold total gain in loop
total_time <- 0 # initial time spent, also to hold total time in loop
# loop over total patches (n_patches)
for(i in seq_len(n_patches)) {
t <- seq(0, gut_time - ts, by = ts)
current_fruits <- sum(gain_rate(t, patch_qual, deplete_rate) * ts)
total_gain <- total_gain + current_fruits
total_time <- total_time + gut_time + moving_time
}
total_gain / total_time
}
Measuring the outputs based on parameters: moving time, pre-determined giving up time and patch quality
Moving Time
# travels unit is in minute
travels <- seq(1, 60, length.out = 10)
# create tibble to hold outcomes from all parameters
res_travel <- tibble(
travel = travels
) %>%
mutate(
mvt = map_dbl(travel, ~ MVT(.x, patch_qual= 1, deplete_rate = 1)$rate_long), # patch_qual = deplete_rate = 1 indicates moderate depletion and to give neutral point
gut = map_dbl(travel, ~ GUT(.x, patch_qual = 1, deplete_rate = 1, gut_time=5)) # Wawa leaves every 5 minutes
)
Pre-determined Giving-up Time
# the GUT time unit is in minute (min 1 minute, max 60 minutes)
guts <- seq(1, 60, length.out = 15)
res_gut <- tibble(
gut_time = guts,
R_gut = map_dbl(gut_time, ~ GUT(5, patch_qual = 1, deplete_rate = 1, gut_time = .x)) # moving time is fixed at 5 minutes
)
Patch Quality
qualities <- c(0.5, 1, 2) # using 1 as the benchmark middle point for easy interpretation
res_quality <- tibble(
patch_qual = qualities
) %>%
mutate(
mvt = map_dbl(patch_qual, ~ MVT(5, patch_qual = .x, deplete_rate = 1)$rate_long),
gut = map_dbl(patch_qual, ~ GUT(5, patch_qual = .x, deplete_rate = 1, gut_time=5))
)
# Travel time plot
res_travel %>%
pivot_longer(c(mvt, gut), names_to="rule", values_to="R") %>%
ggplot(aes(travel, R, color=rule)) +
geom_line(linewidth=1.5) +
labs(x="Travel time (Ï„) in minute", y="Long-term gain rate", color="Rule") +
theme_minimal()
# GUT sweep
res_gut %>%
ggplot(aes(gut_time, R_gut)) +
geom_line(linewidth=1.5, color="steelblue") +
labs(x="Giving-up time (in minute)", y="Long-term gain rate") +
theme_minimal()
# Patch quality plot
res_quality %>%
pivot_longer(c(mvt, gut), names_to="rule", values_to="R") %>%
ggplot(aes(patch_qual, R, color=rule)) +
geom_line(linewidth=1.5) +
labs(x="Patch quality", y="Long-term gain rate", color="Rule") +
theme_minimal()