Skip to contents

Solve a planning problem stored in a Problem object.

This is the main execution step of the multiscape workflow. It reads the problem specification stored in a Problem object, builds the corresponding optimization model when needed, applies the configured solver settings, and returns a solutionset-class object.

A SolutionSet is the standard result object returned by solve(). Single-objective workflows are represented as one-run SolutionSet objects, while multi-objective workflows are represented as multi-run SolutionSet objects.

Usage

solve(x, ...)

# S3 method for class 'Problem'
solve(x, ...)

Arguments

x

A Problem object created with create_problem and optionally enriched with actions, effects, targets, constraints, objectives, spatial relations, method settings, and solver settings.

...

Additional arguments reserved for internal or legacy solver handling. These are not part of the main recommended user interface.

Value

A solutionset-class object.

The returned object contains run-level information, solver diagnostics, objective values, and stored optimization outputs. For single-objective problems, the returned SolutionSet contains one run. For multi-objective workflows, it contains one or more runs generated by the selected method.

Users will typically inspect or visualize results using accessor and plotting functions such as get_pu, get_actions, get_features, get_targets, and plot_tradeoff.

Details

Role of solve()

The typical multiscape workflow is:


x <- create_problem(...)
x <- add_...(x, ...)
x <- set_...(x, ...)
res <- solve(x)

Thus, solve() is the stage at which the stored problem specification is turned into one or more optimization runs.

For most users, solve() is the standard execution entry point. Explicit compilation with compile_model is optional and is mainly useful for advanced inspection or debugging workflows.

What solve() uses from the problem object

The function uses the information already stored in the Problem object, including:

  • baseline planning data;

  • actions, effects, profit, and spatial relations;

  • targets and constraints;

  • registered objectives;

  • an optional multi-objective method configuration;

  • solver settings.

If a model has not yet been built, it is built internally during the solve process. If a model snapshot or pointer already exists, the solving layer may reuse or refresh it depending on the internal model state.

Single-objective and multi-objective behaviour

solve() always returns a solutionset-class object.

  • Single-objective case. If exactly one objective is active and no multi-objective method is configured, solve() runs a single optimization problem and returns a one-run SolutionSet.

  • Multi-objective case. If a multi-objective method is configured, solve() dispatches internally according to the stored method name and returns a multi-run SolutionSet.

This unified output structure makes it possible to use the same inspection, plotting, and analysis functions regardless of whether the original problem was single-objective or multi-objective.

Currently supported multi-objective method names are:

  • "weighted";

  • "epsilon_constraint";

  • "augmecon".

Consistency rule

If multiple objectives are registered but no multi-objective method has been selected, solve() stops with an error. In practical terms:

  • one objective and no multi-objective method \(\Rightarrow\) single-objective solve;

  • multiple objectives and a valid multi-objective method \(\Rightarrow\) multi-objective solve;

  • multiple objectives and no multi-objective method \(\Rightarrow\) error.

Implicit conservation-planning model

If no explicit actions and no explicit effects are provided, solve() can build the corresponding classical conservation-planning formulation internally. In this case, selecting a planning unit is interpreted as applying an implicit conservation action, and the baseline feature amounts stored in dist_features count toward representation targets.

This allows standard reserve-selection problems to be solved without requiring users to manually define actions and effects. Explicit action-based workflows remain available by using add_actions and add_effects.

Solver settings

Solver configuration is read from the Problem object, typically after calling set_solver or one of its convenience wrappers such as set_solver_gurobi or set_solver_cbc.

These settings may include:

  • the selected backend;

  • time limits;

  • optimality-gap settings;

  • CPU cores;

  • verbosity options;

  • backend-specific solver parameters.

Method dispatch

solve() is an S3 generic. The public method documented here is solve.Problem(), which operates on Problem objects.

Examples

# ------------------------------------------------------------
# Minimal single-objective example
# ------------------------------------------------------------
pu <- data.frame(
  id = 1:4,
  cost = c(1, 2, 3, 4)
)

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

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

x <- create_problem(
  pu = pu,
  features = features,
  dist_features = dist_features,
  cost = "cost"
) |>
  add_constraint_targets_relative(0.05) |>
  add_objective_min_cost(alias = "cost")

if (requireNamespace("rcbc", quietly = TRUE)) {
  x <- set_solver_cbc(x, verbose = FALSE)
  solset <- solve(x)
  print(solset)
}
#> A multiscape solution set (<SolutionSet>)
#> ├─method
#> │├─name: `single`
#> │├─objectives: 1 (cost)
#> │└─run design: unspecified
#> └─content
#> │├─design rows: 1
#> │├─attempted runs: 1
#> │├─stored solutions: 1
#> │└─without solution: 0
#> └─run summary
#> │├─statuses: optimal: 1
#> │├─runtime: 0.02
#> │├─gap: 0
#> │├─design columns: none
#> │└─objective columns: value_cost
#> └─objective ranges
#> │└─cost: 1
#> # ℹ Use get_runs(), get_objectives(), get_pu(), and get_actions() to inspect
#> results.

# ------------------------------------------------------------
# Minimal action-based example
# ------------------------------------------------------------
actions <- data.frame(
  id = c("conservation", "restoration")
)

effects <- data.frame(
  action = rep(c("conservation", "restoration"), each = 2),
  feature = rep(features$id, times = 2),
  multiplier = c(1.00, 1.00, 1.50, 1.50)
)

x_actions <- create_problem(
  pu = pu,
  features = features,
  dist_features = dist_features,
  cost = "cost"
) |>
  add_actions(
    actions = actions,
    cost = c(conservation = 1, restoration = 2)
  ) |>
  add_effects(
    effects = effects,
    effect_type = "after"
  ) |>
  add_constraint_targets_relative(0.05) |>
  add_objective_min_cost(alias = "cost")

if (requireNamespace("rcbc", quietly = TRUE)) {
  x_actions <- set_solver_cbc(x_actions, verbose = FALSE)
  solset_actions <- solve(x_actions)
  print(solset_actions)
}
#> A multiscape solution set (<SolutionSet>)
#> ├─method
#> │├─name: `single`
#> │├─objectives: 1 (cost)
#> │└─run design: unspecified
#> └─content
#> │├─design rows: 1
#> │├─attempted runs: 1
#> │├─stored solutions: 1
#> │└─without solution: 0
#> └─run summary
#> │├─statuses: optimal: 1
#> │├─runtime: 0.02
#> │├─gap: 0
#> │├─design columns: none
#> │└─objective columns: value_cost
#> └─objective ranges
#> │└─cost: 2
#> # ℹ Use get_runs(), get_objectives(), get_pu(), and get_actions() to inspect
#> results.

# ------------------------------------------------------------
# Minimal multi-objective example
# ------------------------------------------------------------
x_mo <- create_problem(
  pu = pu,
  features = features,
  dist_features = dist_features,
  cost = "cost"
) |>
  add_constraint_targets_relative(0.05) |>
  add_objective_min_cost(alias = "cost") |>
  add_objective_max_benefit(alias = "benefit") |>
  set_method_weighted_sum(
    aliases = c("cost", "benefit"),
    weights = c(0.5, 0.5),
    normalize_weights = TRUE
  )
#> Warning: `weights` is deprecated. Use `runs = run_manual(data.frame(weight_<alias> = ...))` instead.

if (requireNamespace("rcbc", quietly = TRUE)) {
  x_mo <- set_solver_cbc(x_mo, verbose = FALSE)
  solset_mo <- solve(x_mo)
  print(solset_mo)
}
#> Error: Objective 'benefit' has no positive non-zero benefit coefficients.
#> This objective cannot be used as a benefit objective because all selected effects are zero, missing, or non-positive.
#> For add_objective_max_benefit(), the selected feature(s) must have positive action effects in add_effects().