% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/add_constraint_locked_actions.R
\name{add_constraint_locked_actions}
\alias{add_constraint_locked_actions}
\title{Add locked action decisions to a planning problem}
\usage{
add_constraint_locked_actions(x, locked_in = NULL, locked_out = NULL)
}
\arguments{
\item{x}{A \code{Problem} object with action feasibility already defined via
\code{\link{add_actions}}.}

\item{locked_in}{Optional specification of feasible \code{(pu, action)} pairs
that must be selected. It may be \code{NULL}, a \code{data.frame}, or a
named list.}

\item{locked_out}{Optional specification of feasible \code{(pu, action)}
pairs that must not be selected. It may be \code{NULL}, a
\code{data.frame}, or a named list.}
}
\value{
An updated \code{Problem} object in which the status column of the
feasible action table has been modified to reflect locked-in and locked-out
decisions.
}
\description{
Fix feasible planning unit--action decisions to be selected or excluded.

This function modifies the status of existing feasible
\code{(pu, action)} pairs stored in the feasible action table. It does not
create new feasible action pairs and therefore must be used only after
\code{\link{add_actions}} has been called.
}
\details{
Use this function when only specific feasible \code{(pu, action)} decisions
must be forced in or out of the solution, rather than whole planning units.

Let \eqn{\mathcal{I}} denote the set of planning units and
\eqn{\mathcal{A}} the set of actions. Let
\eqn{\mathcal{D} \subseteq \mathcal{I} \times \mathcal{A}} denote the set of
feasible planning unit--action pairs already defined in the problem.

This function allows the user to define two subsets:
\itemize{
\item \eqn{\mathcal{D}^{in} \subseteq \mathcal{D}}, the set of feasible
pairs that must be selected,
\item \eqn{\mathcal{D}^{out} \subseteq \mathcal{D}}, the set of feasible
pairs that must not be selected.
}

These sets are encoded by updating the \code{status} column of the feasible
action table. The function validates that all requested locked-in and
locked-out pairs are already feasible. Therefore, it cannot be used to
introduce new planning unit--action combinations into the problem.

In optimization terms, if \eqn{x_{ia}} denotes the decision variable
associated with planning unit \eqn{i} and action \eqn{a}, then:
\itemize{
\item locked-in pairs conceptually impose \eqn{x_{ia} = 1},
\item locked-out pairs conceptually impose \eqn{x_{ia} = 0}.
}

The exact translation into solver-side constraints occurs later when the
model is built.

In contrast, \code{\link{add_constraint_locked_pu}} fixes whole planning
units through the unit-selection variables, whereas this function fixes only
specific feasible \code{(pu, action)} decisions.

\strong{Accepted formats}

Both \code{locked_in} and \code{locked_out} accept the same formats:
\itemize{
\item \code{NULL},
\item a \code{data.frame} with columns \code{pu} and \code{action},
optionally including a \code{feasible} column used as a filter,
\item a named list whose names are action ids and whose elements are either
vectors of planning unit ids or \code{sf} objects.
}

If a \code{feasible} column is supplied in a \code{data.frame}, only rows
with \code{feasible = TRUE} are used. Missing values in \code{feasible} are
treated as \code{FALSE}.

If an \code{sf} specification is supplied, the problem object must contain
planning-unit geometry, and planning units are matched spatially using
\code{sf::st_intersects()}.

\strong{Conflict checking}

A given \code{(pu, action)} pair cannot be simultaneously requested in both
\code{locked_in} and \code{locked_out}. Such overlaps are rejected.

In addition, if a planning unit is already marked as locked out at the
planning-unit level, then all feasible actions in that planning unit are
forced to \code{status = 3}. Any attempt to lock in an action within such a
planning unit raises an error.

\strong{Order of precedence}

User-supplied locked-in and locked-out action requests are first applied to
the feasible action table. Afterwards, any planning-unit-level
\code{locked_out} flag is enforced, overriding action-level status and
ensuring consistency with planning-unit exclusions.
}
\examples{
pu <- data.frame(
  id = 1:4,
  cost = c(2, 3, 1, 4)
)

features <- data.frame(
  id = 1:2,
  name = c("sp1", "sp2")
)

dist_features <- data.frame(
  pu = c(1, 1, 2, 3, 4, 4),
  feature = c(1, 2, 1, 2, 1, 2),
  amount = c(1, 2, 1, 3, 2, 1)
)

p <- create_problem(
  pu = pu,
  features = features,
  dist_features = dist_features
)

p <- add_actions(
  x = p,
  actions = data.frame(id = c("conservation", "restoration")),
  cost = c(conservation = 3, restoration = 8)
)

# Lock a few feasible decisions
p <- add_constraint_locked_actions(
  x = p,
  locked_in = data.frame(
    pu = c(1, 2),
    action = c("conservation", "restoration")
  ),
  locked_out = data.frame(
    pu = c(4),
    action = c("conservation")
  )
)

p$data$dist_actions

# Named-list interface
p2 <- add_constraint_locked_actions(
  x = p,
  locked_in = list(
    conservation = c(1, 3)
  ),
  locked_out = list(
    restoration = c(2)
  )
)

p2$data$dist_actions

}
\seealso{
\code{\link{add_actions}},
\code{\link{add_constraint_locked_pu}}
}
