Skip to content

Improver

Improvers are algorithm components that take a solution and try to improve its objective function value. The key constraint is that improvers cannot return a worse solution than the input.

Overview

Improvers usually implement local optimization strategies. They explore the neighborhood of a solution, looking for improvements, and if they do not find any they usually return the solution as is.

graph TD
    A[Input Solution] --> B[Improver]
    B --> C{Found Better?}
    C -->|Yes| D[Return Improved Solution]
    C -->|No| E[Return Original Solution]
    D --> F[score_out ≤ score_in]
    E --> F

Common Improver Types

Improver Type Description Documentation
Local Search Base page for neighborhood-based improvers Local Search
LocalSearchBestImprovement Local search that always selects the best improving move Best-improvement strategy
LocalSearchFirstImprovement Local search that accepts the first improving move found First-improvement strategy
VND Systematic multi-neighborhood search VND
Simulated Annealing Probabilistic acceptance (used as improver) SA

How to Use

Standalone

var improver = new MyLocalSearch();
var solution = constructor.construct(instance);
solution = improver.improve(solution);  
// solution is now at local optimum for that improver

Multi-Start Algorithm example

var multiStart = new MultiStartAlgorithm<>(
    "GRASP",
    constructor,
    improver,  // Improver is applied to each constructed solution
    100
);

Implementation Guidelines

Basic Pattern

public class MyImprover<S extends Solution<S, I>, I extends Instance> 
        extends Improver<S, I> {

    public MyImprover() {
        super("MyImprover");
    }

    @Override
    public S improve(S solution) {
        boolean improved = true;

        while (improved && !TimeControl.isTimeUp()) {
            improved = false;
            // Do something to try to improve the solution. If improved, flip improved variable.
        }

        return solution;
    }
}

Note that it is important to check the output of the TimeControl.isTimeUp() in any time consuming loop, so the algorithm can cleanly finish under time constrains.

Common Patterns

Chaining improvers

Improvers can be easily chained by using the Improver::serial method. Example:

var chainedImprover = Improver.serial(
    new FastLocalSearch<>(),
    new SlowButThoroughSearch<>()
);

If you want, you can use a different objective than the main one, by passing it as the first argument.

Manually chaining improvement methods is not recommended as it complicates algorithm implementations unnecesarily:

// Alternative: manually chain, but requires the algorithm to accept multiple improvers, or to handle arrays. Not recommended.
solution = improver1.improve(solution);
solution = improver2.improve(solution);
solution = improver3.improve(solution);

Null Improver

Most algorithms require an improver as an argument. It is always valid to generate an improver that does nothing, example:

var algorithm = new MultiStartAlgorithm<>(
    "OnlyConstruct",
    constructor,
    Improver.nul(),  // Null Improver does nothing --> Skips improvement phase
    100
);