---
title: "A Simple Race Model"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{A Simple Race Model}
  %\VignetteEncoding{UTF-8}
  %\VignetteEngine{knitr::rmarkdown}
editor_options: 
  markdown: 
    wrap: 72
---

This vignette shows the basic simulate-and-fit workflow for a race model. The
example has two accumulators feeding response `R1` and one accumulator feeding
response `R2`.

```{r}
library(AccumulatR)
```

## Define the model

We use three accumulators. `R1_A` and `R1_B` both feed response `R1`, while
`R2` feeds response `R2`. Parameters are grouped by default; the
`set_parameters()` call gives the two drift parameters separate values and
shares the threshold gaps, starting-point ranges, and numerical values of
the LBA drift spread and RDM diffusion scale.

```{r}
model <- race_spec() |>
  add_accumulator("R1_A", "LBA") |>
  add_accumulator("R1_B", "RDM") |>
  add_accumulator("R2", "lognormal") |>
  add_pool("R1", c("R1_A", "R1_B")) |>
  add_outcome("R1", "R1") |>
  add_outcome("R2", "R2") |>
  set_parameters(
    separate = list(v = c("R1_A", "R1_B"), m = TRUE, lognormal.s = TRUE),
    share = list(
      B_shared = c("R1_A.B", "R1_B.B"),
      A_shared = c("R1_A.A", "R1_B.A"),
      noise_shared = c("R1_A.sv", "R1_B.s")
    ),
    rename = c(t0 = "t0_shared")
  ) |>
  finalize_model()

true_params <- c(
  R1_A.v = 2,
  R1_B.v = 3,
  B_shared = 1,
  A_shared = 0.3,
  noise_shared = 1,
  R2.m = log(0.4),
  R2.s = 0.18,
  t0_shared = 0.05
)
```

## Simulate data

We generate response outcomes and response times for each trial.

```{r}
set.seed(123456)

n_trials <- 2000
params_df <- build_param_matrix(model, true_params, n_trials = n_trials)

sim <- simulate(model, params_df)

data_df <- data.frame(
  trials = sim$trials,
  R = factor(sim$R),
  rt = sim$rt,
  stringsAsFactors = FALSE
)

table(data_df$R)
```
## Evaluate the likelihood

We prepare the data, build a model context, and evaluate the log-likelihood at
the true parameter values.

```{r}
prepared <- prepare_data(model, data_df)
ctx <- make_context(model)

params_df_true <- build_param_matrix(
  model,
  true_params,
  n_trials = n_trials
)

ll_true <- as.numeric(log_likelihood(ctx, prepared, params_df_true))
ll_true
```

For comparison, we can evaluate a clearly misspecified parameter set.

```{r}
wrong_params <- true_params
wrong_params["t0_shared"] <- 0.08

params_df_wrong <- build_param_matrix(
  model,
  wrong_params,
  n_trials = n_trials
)

ll_wrong <- as.numeric(log_likelihood(ctx, prepared, params_df_wrong))
ll_wrong
```

## Estimate parameters with `optim()`

We estimate six parameters: `R1_A.v`, `R1_B.v`, shared `B`, `R2.m`, `R2.s`,
and shared `t0`. The RDM drift, `B`, `R2.s`, and `t0` are estimated on the
log scale to keep them positive. The shared `A` and variability parameters
are held fixed in this example.

```{r eval=FALSE}
neg_loglik <- function(theta) {
  est <- true_params
  est["R1_A.v"] <- theta[["R1_A.v"]]
  est["R1_B.v"] <- exp(theta[["log_R1_B.v"]])
  est["B_shared"] <- exp(theta[["log_B_shared"]])
  est["R2.m"] <- theta[["R2.m"]]
  est["R2.s"] <- exp(theta[["log_R2.s"]])
  est["t0_shared"] <- exp(theta[["log_t0_shared"]])
  params_df <- build_param_matrix(
    model,
    est,
    n_trials = n_trials
  )
  ll <- log_likelihood(ctx, prepared, params_df)
  -as.numeric(ll)
}

start <- c(
  R1_A.v = 1.5,
  log_R1_B.v = log(1.5),
  log_B_shared = log(1.0),
  R2.m = log(0.32),
  log_R2.s = log(0.15),
  log_t0_shared = log(0.03)
)

fit <- optim(start, neg_loglik, method = "Nelder-Mead", control = list(maxit = 4000, reltol = 1e-9))

fit_params <- c(
  R1_A.v = fit$par[["R1_A.v"]],
  R1_B.v = exp(fit$par[["log_R1_B.v"]]),
  B_shared = exp(fit$par[["log_B_shared"]]),
  R2.m = fit$par[["R2.m"]],
  R2.s = exp(fit$par[["log_R2.s"]]),
  t0_shared = exp(fit$par[["log_t0_shared"]])
)
target <- c(
  R1_A.v = true_params[["R1_A.v"]],
  R1_B.v = true_params[["R1_B.v"]],
  B_shared = true_params[["B_shared"]],
  R2.m = true_params[["R2.m"]],
  R2.s = true_params[["R2.s"]],
  t0_shared = true_params[["t0_shared"]]
)
data.frame(true = target, recovered = fit_params, miss = abs(target - fit_params))
```
