---
title: "Warm-Starting and Sensitivity Analysis"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Warm-Starting and Sensitivity Analysis}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

When solving a sequence of related optimization problems, warm-starting
from a previous solution can dramatically reduce solve time. The
**highs** package supports warm-starting via both basis and solution
information.

## Basis Warm-Start

The simplex method maintains a *basis* — a partition of variables into
basic and non-basic sets. Saving and restoring the basis lets the solver
skip the initial phase of finding a feasible basis.

Basis status values:

| Code | Status | Meaning |
|------|--------|---------|
| 0 | Lower | Variable at its lower bound |
| 1 | Basic | Variable is basic |
| 2 | Upper | Variable at its upper bound |
| 3 | Zero | Free variable at zero |
| 4 | Nonbasic | Non-basic (no bound info) |

### Example: Basis Round-Trip

```{r basis-roundtrip}
library(highs)

model <- highs_model(
  L = c(2, 4, 3),
  lower = 0,
  A = matrix(c(3, 4, 2, 2, 1, 2, 1, 3, 2), nrow = 3, byrow = TRUE),
  rhs = c(60, 40, 80),
  maximum = TRUE
)
solver <- hi_new_solver(model)

# Solve the original problem
hi_solver_run(solver)
info1 <- hi_solver_info(solver)
cat("First solve:", info1$simplex_iteration_count, "iterations\n")

# Save the basis
basis <- hi_solver_get_basis(solver)
cat("Basis valid:", basis$valid, "\n")
cat("Column statuses:", basis$col_status, "\n")
cat("Row statuses:", basis$row_status, "\n")
```

Now clear the solver state, restore the basis, and re-solve. The solver
should converge in zero iterations:

```{r basis-restore}
hi_solver_clear_solver(solver)
hi_solver_set_basis(solver, basis$col_status, basis$row_status)
hi_solver_run(solver)
info2 <- hi_solver_info(solver)
cat("Warm-start solve:", info2$simplex_iteration_count, "iterations\n")
cat("Same objective:", info1$objective_function_value == info2$objective_function_value, "\n")
```

### Iterative Solving with Perturbations

A common use case: solve a problem, modify it slightly, and re-solve
with the previous basis as a warm-start.

```{r perturbation}
solver <- hi_new_solver(model)
hi_solver_run(solver)
obj_original <- hi_solver_info(solver)$objective_function_value
cat("Original objective:", obj_original, "\n")

# Save basis before modification
basis <- hi_solver_get_basis(solver)

# Tighten a constraint: rhs from 60 to 50
hi_solver_change_constraint_bounds(solver, idx = 0L, lhs = -Inf, rhs = 50)

# Warm-start from the saved basis
hi_solver_set_basis(solver, basis$col_status, basis$row_status)
hi_solver_run(solver)
info <- hi_solver_info(solver)
cat("After perturbation:", info$objective_function_value,
    "(", info$simplex_iteration_count, "iterations)\n")
```

## Solution Warm-Start

For cases where you have a good primal/dual solution but not a basis
(e.g., from a different solver), you can supply it as a starting point:

```{r solution-warmstart}
solver <- hi_new_solver(model)
hi_solver_run(solver)
sol <- hi_solver_get_solution(solver)

# Clear and warm-start from solution
hi_solver_clear_solver(solver)
hi_solver_set_solution(
  solver,
  col_value = sol$col_value,
  row_value = sol$row_value,
  col_dual  = sol$col_dual,
  row_dual  = sol$row_dual
)
hi_solver_run(solver)
hi_solver_info(solver)$objective_function_value
```

### Sparse Solution

When only a few variables have non-zero values, use the sparse
interface:

```{r sparse-solution}
solver <- hi_new_solver(model)

# Set only the non-zero entries (0-based column indices)
hi_solver_set_sparse_solution(solver, index = c(0L, 1L), value = c(5.0, 10.0))
hi_solver_run(solver)
hi_solver_get_solution(solver)$col_value
```

## Clearing the Basis

Use `hi_solver_clear_basis()` to invalidate the current basis. This
is useful when you want to force presolve to run on the next solve
(presolve is skipped when a valid basis is present):

```{r clear-basis}
solver <- hi_new_solver(model)
hi_solver_run(solver)
cat("Basis valid after solve:", hi_solver_get_basis(solver)$valid, "\n")

hi_solver_clear_basis(solver)
cat("Basis valid after clear:", hi_solver_get_basis(solver)$valid, "\n")
```

## Using the Closure Interface

The `highs_solver()` wrapper exposes warm-start methods directly:

```{r closure}
hw <- highs_solver(model)
hw$solve()
basis <- hw$get_basis()
cat("Basis valid:", basis$valid, "\n")

# Perturb and warm-start
hw$cbounds(1, -Inf, 50)
hw$set_basis(basis$col_status, basis$row_status)
hw$solve()
hw$info()$simplex_iteration_count
```

## Sensitivity Analysis (Ranging)

After solving an LP, ranging analysis shows how much each cost
coefficient or bound can change before the basis changes:

```{r ranging}
solver <- hi_new_solver(model)
hi_solver_run(solver)
ranging <- hi_solver_get_ranging(solver)
cat("Ranging valid:", ranging$valid, "\n")
```

### Cost Ranging

For each column, `col_cost_up` and `col_cost_dn` show how much the
objective coefficient can increase or decrease:

```{r cost-ranging}
cost_up <- ranging$col_cost_up
data.frame(
  variable = seq_along(cost_up$value),
  max_increase = cost_up$value,
  new_objective = cost_up$objective
)
```

### Bound Ranging

For each row, `row_bound_up` and `row_bound_dn` show how much the
constraint bound can change:

```{r bound-ranging}
bound_up <- ranging$row_bound_up
data.frame(
  constraint = seq_along(bound_up$value),
  max_increase = bound_up$value,
  new_objective = bound_up$objective
)
```

## Presolve

Run presolve independently of solving:

```{r presolve}
solver <- hi_new_solver(model)
hi_solver_presolve(solver)
```
