Skip to content

Improver

Improvers are algorithm components that take a solution and try to improve its objective function value. By convention, an improver should return a solution that is at least as good as its input according to the configured Objective. Custom implementations are responsible for preserving this contract, and the framework may trigger an exception if this contract is broken.

Overview

Improvers commonly implement local optimization strategies. Neighborhood-based improvers look for improving moves and return the current solution when no improvement is available. Other strategies, such as simulated annealing, may temporarily accept worsening moves while still returning the best solution found.

graph TD
    A[Input Solution] --> B[Improver]
    B --> C[Run improvement strategy]
    C --> D[Return result]
    D --> E[Expected to be at least as good under the configured Objective]

Improvers are allowed to modify the supplied solution in place. Clone the solution before calling improve(...) if the original state must be preserved.

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
LocalSearchCachedBestImprovement Heuristic best improvement that reuses and refreshes cached candidates Cached 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);  
// If the search stopped because no improving move remained, solution is a
// local optimum for this neighborhood. Note that the solution may not be 
// locally optimal if the local search stopped early due to the time limit.

Multi-Start Algorithm example

// MultiStartAlgorithm wraps another Algorithm. The improver is applied to each
// constructed solution by the wrapped SimpleAlgorithm.
var base = new SimpleAlgorithm<>("GRASP", constructor, improver);
var multiStart = new MultiStartAlgorithmBuilder<MySolution, MyInstance>()
    .withMaxIterations(100)
    .build(base);

Implementation Guidelines

Basic Pattern

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

    public MyImprover() {
        // Improver stores an Objective for subclasses to use. Forward the main
        // objective from the execution context, or accept a custom Objective
        // as a constructor parameter.
        super(Context.getMainObjective());
    }

    @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;
    }
}

Check TimeControl.isTimeUp() in any time-consuming loop so the algorithm can finish cleanly under time constraints.

Common Patterns

Chaining improvers

Improvers can be chained with Improver.serial(...):

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

The overload that accepts an Objective sets the objective exposed by the sequential wrapper. Each contained improver still uses its own configured objective.

Manually chaining improvement methods is not recommended because it complicates algorithm implementations unnecessarily:

// 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:

// Improver.nul() does nothing --> Skips improvement phase
var base = new SimpleAlgorithm<>("OnlyConstruct", constructor, Improver.nul());
var algorithm = new MultiStartAlgorithmBuilder<MySolution, MyInstance>()
    .withMaxIterations(100)
    .build(base);