This page presents theoretical models as a mock foraging case using the Marginal Value Theorem.

The Orangutan’s Foraging Dillema

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:
Wawa wants to gain maximum amount of fig fruits, what should Wawa do?

Set up

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)
}

Simulator

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
}

Sweeping Parameters

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))
  )

Outcomes Visualisation

# 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()

Result interpretation

To get the maximum amount of ripe figs from the rain forest with all the challenges, Wawa should :