---
title: "Response Time and The Number of Options"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Response Time and The Number of Options}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(masc)
```


Constant terms a and b were calculated by regressing the predicted number of fixations
onto log2(N-1). 

Here, we simulate single attribute decisions with threshold increase 𝜃Δ = .01, search sensitivity α = 5 and sampling noise 𝜎
 = 1, although MASC consistently predicts this logarithmic relationship across a range of parameter sets.

```{r}
# masc_hicks_law.R - Creates Figure 11 reproduction from
# Gluth, S., Deakin, J., & Rieskamp, J. (2026). A theory of multiattribute search and choice. Psychological Review.

library(ggplot2)
library(dplyr)
library(tibble)
library(purrr)
library(patchwork)
library(masc)

# Function to simulate relationship between number of options and fixations
simulate_hicks_law <- function(
    n_trials = 200,              # Number of trials per condition
    n_subjects = 100,            # Number of simulated participants
    max_options = 20,            # Maximum number of options to test
    min_options = 2,             # Minimum number of options to test
    n_attributes = 1,            # Use single attribute decisions
    sampling_noise = 1,          # Fixed sampling noise
    alpha = 5,                   # Fixed search sensitivity
    delta = 0.01,                # Fixed threshold increase
    theta = 0.01,                # Fixed initial threshold
    include_approx_rt = FALSE,   # Whether to include approx RT calculation
    plot_while_running = FALSE   # Whether to update plot as simulation runs
) {
  # Pre-allocate results
  options_to_test <- min_options:max_options
  mean_fixations <- numeric(length(options_to_test))
  mean_approx_rt <- numeric(length(options_to_test))

  # ExGaussian parameters (from paper)
  mu <- 139.5530
  sigma <- 63.1433
  tau <- 91.0133

  # Create color
  start_color <- c(194, 218, 184) / 255

  # Create and initialize plot if requested
  if (plot_while_running) {
    plot(1, 1, type = "n",
         xlim = c(min_options-1, max_options+1),
         ylim = c(0, 100),  # Will adjust as data comes in
         xlab = "Number of Options (N)",
         ylab = "Number of Fixations",
         main = "Hick's Law in Multi-Alternative Decisions")
  }

  # Generate weights
  weights <- rep(1, n_attributes)  # Equal weights for single attribute

  # Loop through different numbers of options
  for (i in seq_along(options_to_test)) {
    n_opts <- options_to_test[i]
    cat(sprintf("Processing %d options (%d/%d)\n",
                n_opts, i, length(options_to_test)))

    # Pre-allocate result arrays
    all_fixations <- matrix(0, nrow = n_trials, ncol = n_subjects)
    all_approx_rt <- matrix(0, nrow = n_trials, ncol = n_subjects)

    # Process each subject
    for (s in 1:n_subjects) {
      # Generate attribute values for all trials
      trial_data <- do.call(rbind, lapply(1:n_trials, function(t) {
        # Generate valid attribute values
        # For single attribute, we don't need to check for dominance
        attr_vals <- matrix(rnorm(n_opts * n_attributes), nrow = n_opts)
        as.data.frame(matrix(attr_vals, nrow = 1))
      }))

      # Rename columns appropriately for rMASC
      colnames(trial_data) <- c(
        paste0("opt", rep(1:n_opts, each = n_attributes),
               "_att", rep(1:n_attributes, n_opts))
      )

      # Run rMASC for this subject with current parameters
      result <- rMASC(
        data = trial_data,
        w = weights,
        sigma = sampling_noise,
        alpha = alpha,
        delta = delta,
        theta = theta,
        n_options = n_opts,
        n_attributes = n_attributes,
        max_steps = 100
      )

      # Extract fixation counts
      all_fixations[, s] <- result$results$rt

      # Calculate approximate RT if requested
      if (include_approx_rt) {
        for (t in 1:n_trials) {
          curr_rt <- result$results$rt[t]
          gauss_comp <- rnorm(curr_rt, mu, sigma)
          exp_comp <- rexp(curr_rt, 1/tau)  # R uses rate parameter (1/scale)
          all_approx_rt[t, s] <- sum(gauss_comp + exp_comp)
        }
      }
    }

    # Calculate means
    mean_fixations[i] <- mean(all_fixations)
    if (include_approx_rt) {
      mean_approx_rt[i] <- mean(all_approx_rt)
    }

    # Update plot if requested
    if (plot_while_running) {
      # Fit Hick's Law model
      x_vals <- log2(options_to_test[1:i] - 1)
      y_vals <- mean_fixations[1:i]
      model_fit <- lm(y_vals ~ x_vals)
      a <- model_fit$coefficients[1]
      b <- model_fit$coefficients[2]

      # Generate prediction
      x_pred <- 1:max_options
      y_pred <- a + b * log2(pmax(x_pred - 1, 1))  # Avoid log(0)

      # Clear plot
      plot(options_to_test[1:i], mean_fixations[1:i],
           pch = 21, bg = "gray50", col = "transparent",
           xlim = c(min_options-1, max_options+1),
           ylim = c(0, max(y_pred, mean_fixations[1:i], na.rm = TRUE) * 1.1),
           xlab = "Number of Options (N)",
           ylab = "Number of Fixations",
           main = "Hick's Law in Multi-Alternative Decisions")

      # Add prediction line
      lines(x_pred, y_pred, col = rgb(start_color[1], start_color[2], start_color[3]), lwd = 2)

      # Add legend
      legend("topleft",
             legend = c("Number of Fixations", "Hick's Law: a + b * log₂(N-1)"),
             pch = c(21, NA),
             lty = c(NA, 1),
             pt.bg = c("gray50", NA),
             col = c("transparent", rgb(start_color[1], start_color[2], start_color[3])),
             lwd = c(NA, 2))

      # Force update
      Sys.sleep(0.1)
    }
  }

  # Prepare results
  results <- tibble(
    n_options = options_to_test,
    mean_fixations = mean_fixations
  )

  if (include_approx_rt) {
    results$mean_approx_rt <- mean_approx_rt
  }

  return(results)
}

# Function to plot Hick's Law results
plot_hicks_law <- function(results, include_approx_rt = FALSE) {
  # Fit Hick's Law model to fixation data
  x_vals <- log2(results$n_options - 1)
  y_vals <- results$mean_fixations
  model_fit <- lm(y_vals ~ x_vals)
  a <- model_fit$coefficients[1]
  b <- model_fit$coefficients[2]

  # Generate prediction
  x_pred <- 1:max(results$n_options)
  y_pred <- a + b * log2(pmax(x_pred - 1, 1))  # Avoid log(0)

  # Start color from paper
  start_color <- c(194, 218, 184) / 255

  # Create plot data
  plot_data <- tibble(
    n_options = results$n_options,
    mean_fixations = results$mean_fixations
  )

  pred_data <- tibble(
    n_options = x_pred,
    predicted = y_pred
  )

  # Create fixation plot
  p1 <- ggplot() +
    geom_point(data = plot_data,
               aes(x = n_options, y = mean_fixations),
               size = 3, shape = 21, fill = "gray50", color = "black") +
    geom_line(data = pred_data,
              aes(x = n_options, y = predicted),
              color = rgb(start_color[1], start_color[2], start_color[3]),
              size = 1.2) +
    labs(
      title = "Relationship between Number of Fixations and Number of Options",
      x = "Number of Options (N)",
      y = "Number of Fixations"
    ) +
    theme_classic() +
    theme(
      plot.title = element_text(face = "bold"),
      panel.grid.minor = element_blank()
    )

  # Create RT plot if requested
  if (include_approx_rt && "mean_approx_rt" %in% names(results)) {
    # Fit Hick's Law model to RT data
    rt_model <- lm(results$mean_approx_rt ~ x_vals)
    rt_a <- rt_model$coefficients[1]
    rt_b <- rt_model$coefficients[2]

    # Generate RT prediction
    rt_pred <- rt_a + rt_b * log2(pmax(x_pred - 1, 1))

    # Create RT plot data
    rt_plot_data <- tibble(
      n_options = results$n_options,
      mean_rt = results$mean_approx_rt
    )

    rt_pred_data <- tibble(
      n_options = x_pred,
      predicted = rt_pred
    )

    p2 <- ggplot() +
      geom_point(data = rt_plot_data,
                 aes(x = n_options, y = mean_rt),
                 size = 3, shape = 21, fill = "gray50", color = "black") +
      geom_line(data = rt_pred_data,
                aes(x = n_options, y = predicted),
                color = rgb(start_color[1], start_color[2], start_color[3]),
                size = 1.2) +
      labs(
        title = "Relationship between Reaction Time and Number of Options",
        x = "Number of Options (N)",
        y = "Reaction Time (ms)"
      ) +
      theme_classic() +
      theme(
        plot.title = element_text(face = "bold"),
        panel.grid.minor = element_blank()
      )

    # Combine plots
    return(p1 + p2 + plot_layout(ncol = 2))
  }

  # Otherwise return just the fixation plot
  return(p1)
}
```


```{r}
# Run simulation (note: this can take a while)
set.seed(2025)
results <- simulate_hicks_law(
  n_trials = 15,
  n_subjects = 6,
  max_options = 8,
  min_options = 2,
  include_approx_rt = FALSE,
  plot_while_running = FALSE
)
```


```{r, fig.width=12, fig.height=8, out.width="100%"}
# Plot results
plot <- plot_hicks_law(results)
print(plot)

# Calculate and report model fit
x_vals <- log2(results$n_options - 1)
model_fit <- lm(results$mean_fixations ~ x_vals)
summary(model_fit)

cat(sprintf("\nHick's Law equation: Number of fixations = %.2f + %.2f * log₂(N-1)\n",
            model_fit$coefficients[1], model_fit$coefficients[2]))
cat(sprintf("R² = %.4f\n", summary(model_fit)$r.squared))
```

