## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(multiscape)
can_solve <- requireNamespace("rcbc", quietly = TRUE) &&
  requireNamespace("ggplot2", quietly = TRUE)

## ----build-problem------------------------------------------------------------
sim <- load_sim_multiaction()

# A common catalogue: each action is a separate binary decision in a unit.
actions <- data.frame(
  id = c("restore", "control", "fence", "monitor")
)

# Two illustrative opportunity areas in the 8-by-8 landscape.
southwest <- with(sim$planning_units, x < 4 & y < 4)
northeast <- with(sim$planning_units, x > 4 & y > 4)

# Assign a return to every planning-unit--action pair.
returns <- expand.grid(
  pu = sim$planning_units$id,
  action = actions$id,
  KEEP.OUT.ATTRS = FALSE,
  stringsAsFactors = FALSE
)
idx <- match(returns$pu, sim$planning_units$id)
returns$profit <- -3  # No intervention is preferred outside opportunity areas.
returns$profit[returns$action == "restore" & southwest[idx]] <- 8
returns$profit[returns$action == "control" & southwest[idx]] <- -2
returns$profit[returns$action == "fence"   & northeast[idx]] <- 7
returns$profit[returns$action == "monitor" & northeast[idx]] <- 4

# Build the problem without any special action-selection rules.
raw <- create_problem(
  pu = sim$planning_units,
  features = sim$features,
  dist_features = sim$dist_features,
  cost = "cost"
) |>
  add_actions(actions, cost = 1) |>
  add_profit(returns)

## ----register-sets------------------------------------------------------------
base <- raw |>
  add_action_sets(list(
    restore_control = c("restore", "control"),
    restore_monitor = c("restore", "monitor"),
    fence_monitor = c("fence", "monitor")
  ))

# Membership is a compact table; no new decision variable has been added.
get_action_sets(base)

## ----alternative-set-format---------------------------------------------------
memberships <- data.frame(
  set = c("restore_control", "restore_control",
          "restore_monitor", "restore_monitor",
          "fence_monitor", "fence_monitor"),
  action = c("restore", "control", "restore", "monitor", "fence", "monitor")
)
alternative <- add_action_sets(raw, memberships)

## ----permit-concurrency-------------------------------------------------------
concurrent <- base |>
  add_constraint_action_cardinality(2, "max", name = "two_per_unit")

## ----cardinality-variations---------------------------------------------------
# Up to two actions in the south-west, but the default one elsewhere.
local_capacity <- base |>
  add_constraint_action_cardinality(
    2, "max", pu = sim$planning_units$id[southwest]
  )

# Allow two in total, but at most one of restoration or control.
subset_capacity <- base |>
  add_constraint_action_cardinality(2, "max") |>
  add_constraint_action_cardinality(
    1, "max", actions = c("restore", "control")
  )

# Exactly two actions in each south-west unit.
exact_two <- base |>
  add_constraint_action_cardinality(
    2, "equal", pu = sim$planning_units$id[southwest]
  )

## ----requirements-------------------------------------------------------------
# Whenever restoration is chosen in a south-west unit, control is required.
dependent <- concurrent |>
  add_constraint_action_requires(
    actions = "restore", requires = "control",
    pu = sim$planning_units$id[southwest]
  )

# A different example: either control or monitoring can accompany restoration.
flexible <- concurrent |>
  add_constraint_action_requires(
    actions = "restore", requires = c("control", "monitor"),
    sense = "any", pu = sim$planning_units$id[southwest]
  )

## ----exclusions-and-together--------------------------------------------------
# Fencing and monitoring cannot be combined in north-east units.
exclusive <- concurrent |>
  add_constraint_action_excludes(
    c("fence", "monitor"), pu = sim$planning_units$id[northeast]
  )

# Reuse the MEMBERS of a registered set, not the set identifier itself.
registered <- get_action_sets(base)
members <- registered$action[registered$set == "restore_control"]

joint_selection <- concurrent |>
  add_constraint_action_together(
    members, pu = sim$planning_units$id[southwest]
  )

## ----configure-scenarios------------------------------------------------------
# A: one action at most (the package default).
single <- base

# B: up to two actions in every unit.
#    Positive-return actions may now be combined where advantageous.

# C: two actions allowed, but restoration needs control in the south-west;
#    fencing and monitoring are incompatible in the north-east.
compatible <- concurrent |>
  add_constraint_action_requires(
    "restore", "control", pu = sim$planning_units$id[southwest]
  ) |>
  add_constraint_action_excludes(
    c("fence", "monitor"), pu = sim$planning_units$id[northeast]
  )

models <- list(
  single = single,
  concurrent = concurrent,
  compatible = compatible
)

## ----solve-scenarios, eval=can_solve------------------------------------------
solutions <- lapply(models, function(problem) {
  problem |>
    add_objective_max_profit(alias = "profit") |>
    set_solver_cbc(time_limit = 30, verbose = FALSE) |>
    solve()
})

# A concise comparison: objective values and selected action counts.
comparison <- do.call(rbind, lapply(names(solutions), function(label) {
  result <- solutions[[label]]
  selected <- get_actions(result, solution = 1)
  selected <- selected[selected$selected > 0.5, , drop = FALSE]
  counts <- table(factor(selected$action, levels = actions$id))
  data.frame(
    scenario = label,
    profit = get_objectives(result, format = "wide")$profit[1],
    restore = unname(counts["restore"]),
    control = unname(counts["control"]),
    fence = unname(counts["fence"]),
    monitor = unname(counts["monitor"]),
    row.names = NULL
  )
}))
comparison

## ----map-single, eval=can_solve, fig.width=7.5, fig.height=4.8, fig.cap="A. Default: at most one action in each planning unit."----
plot_spatial_actions(solutions$single, layout = "single")

## ----map-concurrent, eval=can_solve, fig.width=7.5, fig.height=4.8, fig.cap="B. Up to two actions: concurrent selections become possible."----
plot_spatial_actions(solutions$concurrent, layout = "single")

## ----map-compatible, eval=can_solve, fig.width=7.5, fig.height=4.8, fig.cap="C. Action dependencies and exclusions restrict which concurrent choices are possible."----
plot_spatial_actions(solutions$compatible, layout = "single")

## ----joint-outcome------------------------------------------------------------
joint_effects <- concurrent |>
  add_effects(data.frame(
    action = c("restore", "control", "restore_control"),
    feature = "woodland",
    effect = c(0.10, 0.05, 0.20)
  ))

