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