Nothing
#' @title Add absolute targets
#'
#' @description
#' Add feature-level absolute targets to a planning problem.
#'
#' These targets are stored in the problem object and later translated into
#' linear constraints when the optimization model is built. Absolute targets
#' are interpreted directly in the same units as the feature
#' contributions used by the model. Each call appends one or more target definitions to the problem. This makes
#' it possible to combine multiple target rules, including targets associated
#' with different action subsets.
#'
#' @details
#' Calls accumulate targets for distinct feature/action scopes. A second target
#' for the same feature and action scope raises an error immediately, even if
#' it uses relative units or a different label. To change a target, rebuild
#' from the problem before it was added.
#'
#' Use this function when target requirements are naturally expressed in the
#' original units of the modelled feature contributions, rather than as
#' proportions of reference-scenario totals.
#'
#' Let \eqn{\mathcal{F}} denote the set of features. For each targeted feature
#' \eqn{f \in \mathcal{F}}, this function stores an absolute target threshold
#' \eqn{T_f \ge 0}.
#'
#' With at most one action per unit, each target is interpreted as a
#' lower-bound constraint on post-action amounts of the form:
#' \deqn{
#' \sum_{(i,a) \in \mathcal{D}_f^{\star}} c_{iaf} x_{ia} \ge T_f,
#' }
#' where:
#' \itemize{
#' \item \eqn{i \in \mathcal{I}} indexes planning units,
#' \item \eqn{a \in \mathcal{A}} indexes actions,
#' \item \eqn{x_{ia}} indicates whether action \eqn{a} is selected in planning
#' unit \eqn{i},
#' \item \eqn{c_{iaf}} is the contribution of that action to feature
#' \eqn{f},
#' \item \eqn{\mathcal{D}_f^{\star}} is the subset of planning unit--action
#' pairs allowed to count toward the target for feature \eqn{f}.
#' }
#'
#' In the absolute case, the stored target threshold is simply:
#' \deqn{
#' T_f = t_f,
#' }
#' where \eqn{t_f} is the user-supplied target value for feature \eqn{f}.
#'
#' With concurrent actions, the model instead combines signed changes within
#' each unit and feature and counts the reference once:
#' \deqn{\sum_i \left(r_{if} s_i^{\star} + \Delta_{if}^{\star}(x)\right) \ge T_f.}
#' Here \eqn{s_i^{\star}} indicates that at least one action in the target scope
#' is selected. Joint corrections contribute only when every member belongs
#' to that scope. Individual effects are additive when no interaction is supplied.
#' The same aggregation is used to report target achievement.
#'
#' The \code{actions} argument restricts which actions may contribute toward
#' achievement of the target, but it does not modify the value of \eqn{T_f}
#' itself.
#'
#' The \code{targets} argument is parsed by \code{.pa_parse_targets()} and may be
#' supplied in several equivalent forms, including:
#' \itemize{
#' \item a single numeric value recycled to all selected features,
#' \item a numeric vector aligned to \code{features},
#' \item a named numeric vector where names identify features,
#' \item a \code{data.frame} with \code{feature} and \code{target} columns.
#' }
#'
#' If \code{targets} does not explicitly identify features:
#' \itemize{
#' \item if \code{features = NULL}, the target is applied to all features,
#' \item if \code{features} is supplied, the target values are interpreted with
#' respect to that feature set.
#' }
#'
#' Repeated calls append new target rules rather than replacing previous ones.
#' This allows cumulative target modelling, including multiple rules on the same
#' feature with different contributing action subsets.
#'
#' @param x A \code{Problem} object.
#' @param targets Target specification. This is interpreted as an absolute target
#' value in the same units as the modelled feature contributions. It may be a
#' scalar, vector, named vector, or \code{data.frame}. See Details.
#' @param features Optional feature specification indicating which features the
#' supplied target values refer to when \code{targets} does not identify
#' features explicitly. If \code{NULL}, all features are targeted.
#' @param actions Optional character vector indicating which actions count toward
#' target achievement. Entries may match action ids, \code{action_set} labels,
#' or both. If \code{NULL}, all actions count.
#' @param label Optional character string stored with the targets for reporting
#' and bookkeeping.
#'
#' @return An updated \code{Problem} object with absolute targets appended to
#' the stored target table.
#'
#' @examples
#' # Load a complete simulated planning problem.
#' example_data <- load_sim_multiaction()
#'
#' p <- create_problem(
#' pu = example_data$planning_units,
#' features = example_data$features,
#' dist_features = example_data$dist_features,
#' cost = "cost"
#' ) |>
#' add_actions(
#' example_data$actions,
#' cost = example_data$action_costs
#' )
#'
#' # Same absolute target for all features
#' p1 <- add_constraint_targets_absolute(p, 3)
#' p1$data$targets
#'
#' # Different targets by feature
#' p2 <- add_constraint_targets_absolute(
#' p,
#' c("1" = 4, "2" = 2)
#' )
#' p2$data$targets
#'
#' # Restrict which actions count toward target achievement
#' p3 <- add_constraint_targets_absolute(
#' p,
#' 2,
#' actions = "protect"
#' )
#' p3$data$targets
#'
#' @seealso
#' \code{\link{add_constraint_targets_relative}}
#'
#' @export
add_constraint_targets_absolute <- function(x, targets,
features = NULL,
actions = NULL,
label = NULL) {
stopifnot(inherits(x, "Problem"))
x <- .pa_clone_data(x)
dt <- .pa_parse_targets(x, targets, features = features)
actions_txt <- .pa_target_actions(x, actions)
out <- data.frame(
feature = as.numeric(dt$feature),
type = "actions",
sense = "ge",
target_unit = "absolute",
target_raw = as.numeric(dt$target_raw),
basis_total = NA_real_,
target_value = as.numeric(dt$target_raw),
actions = actions_txt,
label = if (is.null(label)) NA_character_ else as.character(label),
created_at = as.character(Sys.time()),
stringsAsFactors = FALSE
)
out <- .pa_add_feature_labels(
df = out,
features_df = x$data$features,
feature_col = "feature",
internal_feature_col = "internal_feature",
out_col = "feature_name"
)
x$data$meta$model_dirty <- TRUE
.pa_store_targets(x, out)
}
#' @title Add relative targets
#'
#' @description
#' Add feature-level relative targets to a planning problem.
#'
#' These targets are stored in the problem object and later translated into
#' linear constraints when the optimization model is built. Relative targets are
#' supplied as proportions in \eqn{[0,1]} and are converted internally into
#' absolute thresholds using the current total amount of each
#' feature in the landscape.
#'
#' Each call appends one or more target definitions to the problem. This makes
#' it possible to combine multiple target rules, including targets associated
#' with different action subsets.
#'
#' @details
#' Use this function when target requirements are naturally expressed as
#' proportions of reference-scenario feature totals rather than in original
#' feature units.
#'
#' Let \eqn{\mathcal{F}} denote the set of features. For each targeted feature
#' \eqn{f \in \mathcal{F}}, let \eqn{B_f} denote the reference-scenario total
#' amount of that feature in the landscape, as computed by
#' \code{.pa_feature_totals()}.
#'
#' If the user supplies a relative target \eqn{r_f \in [0,1]}, then this
#' function converts it to an absolute threshold:
#' \deqn{
#' T_f = r_f \times B_f.
#' }
#'
#' The absolute threshold \eqn{T_f} is stored in \code{target_value}, while:
#' \itemize{
#' \item the original user-supplied proportion \eqn{r_f} is stored in
#' \code{target_raw},
#' \item the baseline total \eqn{B_f} is stored in \code{basis_total}.
#' }
#'
#' When the optimization model is built, the resulting target is interpreted as:
#' \deqn{
#' \sum_{(i,a) \in \mathcal{D}_f^{\star}} c_{iaf} x_{ia} \ge T_f,
#' }
#' where:
#' \itemize{
#' \item \eqn{i \in \mathcal{I}} indexes planning units,
#' \item \eqn{a \in \mathcal{A}} indexes actions,
#' \item \eqn{x_{ia}} indicates whether action \eqn{a} is selected in planning
#' unit \eqn{i},
#' \item \eqn{c_{iaf}} is the contribution of that action to feature
#' \eqn{f},
#' \item \eqn{\mathcal{D}_f^{\star}} is the subset of planning unit--action
#' pairs allowed to count toward the target for feature \eqn{f}.
#' }
#'
#' With concurrent actions, the model instead combines signed changes within
#' each unit and feature and counts the reference once:
#' \deqn{\sum_i \left(r_{if} s_i^{\star} + \Delta_{if}^{\star}(x)\right) \ge T_f.}
#' Here \eqn{s_i^{\star}} indicates that at least one action in the target scope
#' is selected. Joint corrections contribute only when every member belongs
#' to that scope. Individual effects are additive when no interaction is supplied.
#' The same aggregation is used to report target achievement.
#'
#' The \code{actions} argument restricts which actions may contribute toward
#' target achievement, but it does not affect the baseline amount \eqn{B_f} used
#' to compute the threshold. In other words, relative targets are always scaled
#' against the full landscape reference scenario.
#'
#' Therefore, \code{actions} changes who may satisfy the target, but not how the
#' threshold itself is scaled.
#'
#' The \code{targets} argument is parsed by \code{.pa_parse_targets()} and may be
#' supplied in several equivalent forms, including:
#' \itemize{
#' \item a single numeric value recycled to all selected features,
#' \item a numeric vector aligned to \code{features},
#' \item a named numeric vector where names identify features,
#' \item a \code{data.frame} with \code{feature} and \code{target} columns.
#' }
#'
#' If \code{targets} does not explicitly identify features:
#' \itemize{
#' \item if \code{features = NULL}, the target is applied to all features,
#' \item if \code{features} is supplied, the target values are interpreted with
#' respect to that feature set.
#' }
#'
#' Relative targets must lie in \eqn{[0,1]}.
#' A second target for the same feature and action scope raises an error,
#' including when an absolute target already exists. Labels and threshold
#' values do not change target identity.
#'
#' Repeated calls append new target rules rather than replacing previous ones.
#' This allows cumulative target modelling, including multiple rules on the same
#' feature with different contributing action subsets.
#'
#' @param x A \code{Problem} object.
#' @param targets Target specification as proportions in \eqn{[0,1]}. It may be
#' a scalar, vector, named vector, or \code{data.frame}. See Details.
#' @param features Optional feature specification indicating which features the
#' supplied target values refer to when \code{targets} does not identify
#' features explicitly. If \code{NULL}, all features are targeted.
#' @param actions Optional character vector indicating which actions count toward
#' target achievement. Entries may match action ids, \code{action_set} labels,
#' or both. If \code{NULL}, all actions count.
#' @param label Optional character string stored with the targets for reporting
#' and bookkeeping.
#'
#' @return An updated \code{Problem} object with relative targets appended to
#' the stored target table.
#'
#' @examples
#' # Load a complete simulated planning problem.
#' example_data <- load_sim_multiaction()
#'
#' p <- create_problem(
#' pu = example_data$planning_units,
#' features = example_data$features,
#' dist_features = example_data$dist_features,
#' cost = "cost"
#' ) |>
#' add_actions(
#' example_data$actions,
#' cost = example_data$action_costs
#' )
#'
#' # Require 30% of the baseline total for all features
#' p1 <- add_constraint_targets_relative(p, 0.3)
#' p1$data$targets
#'
#' # Require 20% for one selected feature
#' p2 <- add_constraint_targets_relative(
#' p,
#' 0.2,
#' features = 1
#' )
#' p2$data$targets
#'
#' # Restrict which actions count toward target achievement
#' p3 <- add_constraint_targets_relative(
#' p,
#' 0.2,
#' actions = "protect"
#' )
#' p3$data$targets
#'
#' @seealso
#' \code{\link{add_constraint_targets_absolute}}
#'
#' @export
add_constraint_targets_relative <- function(x, targets,
features = NULL,
actions = NULL,
label = NULL) {
stopifnot(inherits(x, "Problem"))
x <- .pa_clone_data(x)
dt <- .pa_parse_targets(x, targets, features = features)
rel <- as.numeric(dt$target_raw)
if (any(rel < 0 | rel > 1, na.rm = TRUE)) {
stop("Relative targets must be between 0 and 1.", call. = FALSE)
}
basis <- .pa_feature_totals(x)
basis_v <- basis[as.character(dt$feature)]
basis_v[is.na(basis_v)] <- 0
abs_target <- rel * as.numeric(basis_v)
actions_txt <- .pa_target_actions(x, actions)
out <- data.frame(
feature = as.numeric(dt$feature),
type = "actions",
sense = "ge",
target_unit = "relative_baseline",
target_raw = rel,
basis_total = as.numeric(basis_v),
target_value = as.numeric(abs_target),
actions = actions_txt,
label = if (is.null(label)) NA_character_ else as.character(label),
created_at = as.character(Sys.time()),
stringsAsFactors = FALSE
)
out <- .pa_add_feature_labels(
df = out,
features_df = x$data$features,
feature_col = "feature",
internal_feature_col = "internal_feature",
out_col = "feature_name"
)
x$data$meta$model_dirty <- TRUE
.pa_store_targets(x, out)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.