Skip to contents

Fix feasible planning unit–action decisions to be selected or excluded.

This function modifies the status of existing feasible (pu, action) pairs stored in the feasible action table. It does not create new feasible action pairs and therefore must be used only after add_actions has been called.

Usage

add_constraint_locked_actions(x, locked_in = NULL, locked_out = NULL)

Arguments

x

A Problem object with action feasibility already defined via add_actions.

locked_in

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

locked_out

Optional specification of feasible (pu, action) pairs that must not be selected. It may be NULL, a data.frame, or a named list.

Value

An updated Problem object in which the status column of the feasible action table has been modified to reflect locked-in and locked-out decisions.

Details

Use this function when only specific feasible (pu, action) decisions must be forced in or out of the solution, rather than whole planning units.

Let \(\mathcal{I}\) denote the set of planning units and \(\mathcal{A}\) the set of actions. Let \(\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:

  • \(\mathcal{D}^{in} \subseteq \mathcal{D}\), the set of feasible pairs that must be selected,

  • \(\mathcal{D}^{out} \subseteq \mathcal{D}\), the set of feasible pairs that must not be selected.

These sets are encoded by updating the 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 \(x_{ia}\) denotes the decision variable associated with planning unit \(i\) and action \(a\), then:

  • locked-in pairs conceptually impose \(x_{ia} = 1\),

  • locked-out pairs conceptually impose \(x_{ia} = 0\).

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

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

Accepted formats

Both locked_in and locked_out accept the same formats:

  • NULL,

  • a data.frame with columns pu and action, optionally including a feasible column used as a filter,

  • a named list whose names are action ids and whose elements are either vectors of planning unit ids or sf objects.

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

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

Conflict checking

A given (pu, action) pair cannot be simultaneously requested in both locked_in and 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 status = 3. Any attempt to lock in an action within such a planning unit raises an error.

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 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
#>   pu       action cost status internal_pu internal_action
#> 1  1 conservation    3      2           1               1
#> 5  1  restoration    8      0           1               2
#> 2  2 conservation    3      0           2               1
#> 6  2  restoration    8      2           2               2
#> 3  3 conservation    3      0           3               1
#> 7  3  restoration    8      0           3               2
#> 4  4 conservation    3      3           4               1
#> 8  4  restoration    8      0           4               2

# 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
#>   pu       action cost status internal_pu internal_action
#> 1  1 conservation    3      2           1               1
#> 5  1  restoration    8      0           1               2
#> 2  2 conservation    3      0           2               1
#> 6  2  restoration    8      3           2               2
#> 3  3 conservation    3      2           3               1
#> 7  3  restoration    8      0           3               2
#> 4  4 conservation    3      3           4               1
#> 8  4  restoration    8      0           4               2