The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Repeated calls and alternative problem configurations

Each constructor has a fixed repeated-call contract. No argument is needed to choose between accumulation and replacement: existing definitions are never silently replaced.

Family Repeated-call behavior
add_actions(), add_effects(), add_profit() One complete definition per problem; a second call is an error.
set_solver() and its backend wrappers One explicit configuration; a second call is an error. Defaults do not count as configuration.
set_method_*() One MO method, including its run design; switching or repeating it is an error.
add_objective_*() without alias One explicit single-objective definition; a second one is an error.
add_objective_*() with alias Distinct aliases accumulate; a repeated alias is an error.
add_action_sets() Distinct set IDs accumulate; a repeated ID is an error.
Targets, area, budget, action cardinality, and logical action relations Distinct constraint definitions accumulate; repeated identities are errors.
Spatial constructors Distinct relation names accumulate; a repeated name is an error.
Planning-unit and action locks Compatible locks accumulate; identical states are idempotent and contradictory states are errors.

The legacy add_benefits() and add_losses() wrappers share the one effects definition with add_effects(). Even a zero-profit specification or an empty filtered effects table counts as an explicit definition.

Build a common base

Keep an object before the definition you want to vary. Constructors return an independent problem, so alternatives can share that common base.

base <- create_problem(
  pu = data.frame(id = 1:3, cost = 1, area = 10),
  features = data.frame(id = 1L, name = "woodland"),
  dist_features = data.frame(pu = 1:3, feature = 1L, amount = 100)
)

actions_base <- base |>
  add_actions(data.frame(id = c("restore", "control", "fence")), cost = 1)

low_effect <- actions_base |>
  add_effects(data.frame(action = "restore", feature = "woodland", effect = 20))

high_effect <- actions_base |>
  add_effects(data.frame(action = "restore", feature = "woodland", effect = 40))

is.null(actions_base$data$dist_effects)
#> [1] TRUE

Calling add_effects(low_effect, ...) again would raise an error. To change actions, start from base; to change effects or profit, start from actions_base.

Compare solver settings and MO methods

Define all solver parameters in one call. Setting a solver does not run it.

objectives_base <- high_effect |>
  add_constraint_targets_absolute(50) |>
  add_objective_min_cost(alias = "cost") |>
  add_objective_max_effect(alias = "benefit")

cbc_problem <- objectives_base |>
  set_solver_cbc(gap_limit = 0, time_limit = 60)

gurobi_problem <- objectives_base |>
  set_solver_gurobi(gap_limit = 0, time_limit = 60)

weighted_problem <- cbc_problem |>
  set_method_weighted_sum(
    aliases = c("cost", "benefit"),
    runs = set_runs_grid(n = 3)
  )

epsilon_problem <- cbc_problem |>
  set_method_epsilon_constraint(
    primary = "cost",
    aliases = c("cost", "benefit"),
    runs = set_runs_manual(data.frame(eps_benefit = c(40, 80)))
  )

augmecon_problem <- cbc_problem |>
  set_method_augmecon(
    primary = "cost",
    aliases = c("cost", "benefit"),
    runs = set_runs_grid(n = 3)
  )

is.null(cbc_problem$data$method)
#> [1] TRUE
is.null(objectives_base$data$solve_args)
#> [1] TRUE

Each method is derived from cbc_problem, which has no configured MO method. To compare run designs within a method, use this same branching pattern.

Accumulate independent constraints

Constraint identity describes what is constrained, rather than the threshold or its diagnostic name. Identifier order does not affect identity. Explicit constraint names must be unique within their family; omitted names are generated automatically. A different name does not permit a duplicate constraint identity.

Constraint Identity
Action cardinality Set of PUs + total/subset action scope + sense
Logical action relation Type + action groups (including required companions) + all/any sense + set of PUs
Budget Action scope + included cost components + sense
Area Action scope + area measure (area_col) + sense
Target Feature + action scope, shared by absolute and relative targets

Requires, excludes, and together share one name namespace. A single required companion has the same identity under sense = "all" and sense = "any".

constrained <- actions_base |>
  add_action_sets(list(restore_control = c("restore", "control"))) |>
  add_action_sets(list(restore_fence = c("restore", "fence"))) |>
  add_constraint_action_cardinality(3, "max", pu = 1) |>
  add_constraint_action_cardinality(2, "max", pu = 2) |>
  add_constraint_budget(20, "max") |>
  add_constraint_budget(10, "max", include_action_cost = FALSE)

get_action_sets(constrained)
#>               set  action
#> 1 restore_control control
#> 2 restore_control restore
#> 3   restore_fence   fence
#> 4   restore_fence restore
constrained$data$constraints$action_cardinality[, c("count", "sense", "name")]
#>   count sense                 name
#> 1     3   max action_cardinality_1
#> 2     2   max action_cardinality_2

Adding another total maximum for PU 1 would be an error, even with a different count or name. Different scopes may overlap: all accumulated constraints apply simultaneously. Constraints that are individually valid can still make the model infeasible; the solver determines joint feasibility.

Absolute and relative targets cannot be used to redefine the same feature and action scope. Targets for the same feature with different contributing action subsets can accumulate.

Accumulate compatible locks

locked <- actions_base |>
  add_constraint_locked_planning_units(locked_in = 1) |>
  add_constraint_locked_planning_units(locked_out = 3) |>
  add_constraint_locked_actions(locked_out = list(fence = 2)) |>
  add_constraint_locked_actions(locked_out = list(fence = 2))

locked$data$pu[, c("id", "locked_in", "locked_out")]
#>   id locked_in locked_out
#> 1  1      TRUE      FALSE
#> 2  2     FALSE      FALSE
#> 3  3     FALSE       TRUE

Omitted lock arguments preserve existing states. Repeating the same lock does not change its meaning. Attempting to lock PU 1 out now would raise an error, as would locking an action in inside excluded PU 3. This is checked in either call order. To alter a lock, rebuild from the object before the original lock.

Spatial relations similarly accumulate under distinct names. Reusing a name raises an error, whether using add_spatial_relations() directly or a spatial constructor such as add_spatial_boundary().

For migration from earlier behavior, move all solver settings into one call, combine effects into one complete table, and create alternatives from a common base rather than applying a second configuration to an existing problem.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.