diff --git a/.gitignore b/.gitignore index 171f088a6..882c5336d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ log* .benchmark **/docs /example-graphs/instances/ +site/ diff --git a/docs/concepts/algorithm-components/.pages b/docs/concepts/algorithm-components/.pages new file mode 100644 index 000000000..2b7cbb169 --- /dev/null +++ b/docs/concepts/algorithm-components/.pages @@ -0,0 +1,7 @@ +nav: + - intro.md + - components.md + - metaheuristics + - constructors + - improvers + - shakes diff --git a/docs/concepts/algorithm-components/components.md b/docs/concepts/algorithm-components/components.md index 2821ed4f5..344f0099e 100644 --- a/docs/concepts/algorithm-components/components.md +++ b/docs/concepts/algorithm-components/components.md @@ -1,2 +1,118 @@ -# List of components -Under construction \ No newline at end of file +# List of Algorithm Components + +This page provides a comprehensive overview of all algorithm components available in the Mork framework. Components are organized by their role in the optimization process. + +## Metaheuristics + +High-level algorithmic strategies that guide the search process. These are the main algorithms you'll use to solve optimization problems. + +| Algorithm | Description | Documentation | +|-----------|-------------|---------------| +| **Variable Neighborhood Search (VNS)** | Systematically changes neighborhood structures to escape local optima | [VNS Documentation](metaheuristics/vns.md) | +| **Simulated Annealing (SA)** | Temperature-based probabilistic technique inspired by metallurgy | [SA Documentation](metaheuristics/simulated-annealing.md) | +| **Iterated Greedy (IG)** | Destruction-reconstruction metaheuristic that iteratively destroys and rebuilds solutions | [IG Documentation](metaheuristics/iterated-greedy.md) | +| **Scatter Search** | Population-based metaheuristic using reference sets and solution combination | [Scatter Search Documentation](metaheuristics/scatter-search.md) | +| **Multi-Start Algorithm** | Simple but effective strategy that runs constructive+improvement methods multiple times | [Multi-Start Documentation](metaheuristics/multi-start.md) | +| **Variable Neighborhood Descent (VND)** | Systematic exploration of multiple neighborhood structures in a descent manner | [VND Documentation](metaheuristics/vnd.md) | + +## Constructive Methods + +Components that build initial solutions from scratch. These methods create feasible solutions that can then be improved. + +| Constructor | Description | Documentation | +|-------------|-------------|---------------| +| **Constructive (Base)** | Abstract base class for all constructive methods | [Constructive Documentation](constructors/constructive.md) | +| **GRASP Constructive** | Greedy Randomized Adaptive Search Procedure for construction phase | [GRASP Documentation](constructors/grasp.md) | +| **Greedy Random GRASP** | GRASP variant that builds solutions element by element with randomized greedy selection | [Greedy Random Documentation](constructors/greedy-random-grasp.md) | +| **Random Greedy GRASP** | GRASP variant that randomizes the order before applying greedy selection | [Random Greedy Documentation](constructors/random-greedy-grasp.md) | +| **Reconstructive** | Specialized constructor for rebuilding partially destroyed solutions | [Reconstructive Documentation](constructors/reconstructive.md) | + +## Improvement Methods + +Components that take a solution and try to improve it. These methods cannot return worse solutions than their input. + +| Improver | Description | Documentation | +|----------|-------------|---------------| +| **Improver (Base)** | Abstract base class for all improvement methods | [Improver Documentation](improvers/improver.md) | +| **Local Search** | Base class for local search algorithms that explore solution neighborhoods | [Local Search Documentation](improvers/local-search.md) | +| **Best Improvement LS** | Local search that always picks the best move in the neighborhood | [Best Improvement Documentation](improvers/best-improvement.md) | +| **First Improvement LS** | Local search that applies the first improving move found | [First Improvement Documentation](improvers/first-improvement.md) | +| **Simulated Annealing (as Improver)** | SA can be used as an improvement method with temperature-based acceptance | [SA Documentation](metaheuristics/simulated-annealing.md) | + +## Shake/Perturbation Methods + +Components that perturb solutions to escape local optima. Unlike improvers, these can worsen the solution. + +| Shake Method | Description | Documentation | +|--------------|-------------|---------------| +| **Shake (Base)** | Abstract base class for all perturbation methods | [Shake Documentation](shakes/shake.md) | +| **Destructive** | Interface for destruction operators that remove parts of a solution | [Destructive Documentation](shakes/destructive.md) | +| **Destroy-Rebuild** | Combined operator that destroys part of a solution and rebuilds it | [Destroy-Rebuild Documentation](shakes/destroy-rebuild.md) | +| **Random Move Shake** | Simple perturbation that applies random moves to the solution | [Random Move Documentation](shakes/random-move.md) | + +## Component Integration + +All components follow a consistent design pattern and can be combined flexibly: + +```mermaid +graph TD + A[Instance] --> B[Constructive] + B --> C[Initial Solution] + C --> D[Improver] + D --> E[Improved Solution] + E --> F{Continue?} + F -->|Yes| G[Shake] + G --> H[Perturbed Solution] + H --> D + F -->|No| I[Final Solution] +``` + +## Using Components + +Components are designed to be: + +- **Modular**: Each component has a single, well-defined responsibility +- **Composable**: Components can be combined in different ways to create complex algorithms +- **Reusable**: The same component can be used in multiple algorithms +- **Extensible**: Easy to create new components by extending existing ones + +### Example: Building a GRASP Algorithm + +```java +// Create components +var constructor = new MyGRASPConstructive(0.3); // alpha = 0.3 +var improver = new MyLocalSearch(); + +// Combine into an algorithm +var grasp = new MultiStartAlgorithm<>( + "GRASP", + constructor, + improver, + 100 // iterations +); +``` + +### Example: Building VNS + +```java +var vns = new VNSBuilder() + .withConstructive(new MyConstructive()) + .withImprover(new MyLocalSearch()) + .withShake(new MyShake()) + .withNeighChange(5) // kmax = 5 + .build("VNS"); +``` + +## Advanced Topics + +### Component Autodetection + +Components are automatically detected by the framework when annotated with `@AlgorithmComponent` or when extending framework-provided base classes. + +### Automatic Algorithm Configuration + +Components can be automatically configured using irace integration. See [irace documentation](../../features/irace.md) for details. + +### Custom Component Types + +You can create your own component hierarchies by extending existing types or creating new ones. See [intro documentation](intro.md#creating-custom-types) for more information. \ No newline at end of file diff --git a/docs/concepts/algorithm-components/constructors/.pages b/docs/concepts/algorithm-components/constructors/.pages new file mode 100644 index 000000000..28fa8dc55 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/.pages @@ -0,0 +1,7 @@ +title: Constructors +nav: + - constructive.md + - grasp.md + - greedy-random-grasp.md + - random-greedy-grasp.md + - reconstructive.md diff --git a/docs/concepts/algorithm-components/constructors/constructive.md b/docs/concepts/algorithm-components/constructors/constructive.md new file mode 100644 index 000000000..ed793d640 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/constructive.md @@ -0,0 +1,294 @@ +# Constructive Methods + +Constructive methods (also called construction heuristics) build solutions from scratch. They start with an empty or partial solution and incrementally add elements until a complete, feasible solution is obtained. + +## Overview + +All constructive methods in Mork extend the `Constructive` base class and must implement the `construct` method that takes a solution (usually empty) and returns a complete, feasible solution. + +```mermaid +graph LR + A[Empty Solution] --> B[Constructive Method] + B --> C[Add Element 1] + C --> D[Add Element 2] + D --> E[...] + E --> F[Add Element n] + F --> G[Complete Feasible Solution] +``` + +## Base Constructive Interface + +```java +public abstract class Constructive, I extends Instance> + extends AlgorithmComponent { + + /** + * Construct a complete solution from the given (usually empty) solution + * @param solution Starting solution, typically empty + * @return Complete, feasible solution + */ + public abstract S construct(S solution); +} +``` + +## Common Construction Strategies + +### 1. Random Construction + +Build solutions by randomly selecting elements: + +```java +public class RandomConstructive, I extends Instance> + extends Constructive { + + @Override + public S construct(S solution) { + while (!solution.isComplete()) { + var candidates = solution.getAvailableElements(); + var random = candidates.get(ThreadLocalRandom.current().nextInt(candidates.size())); + solution.add(random); + } + return solution; + } +} +``` + +### 2. Greedy Construction + +Build solutions by always selecting the best available element: + +```java +public class GreedyConstructive, I extends Instance> + extends Constructive { + + @Override + public S construct(S solution) { + while (!solution.isComplete()) { + var candidates = solution.getAvailableElements(); + var best = candidates.stream() + .min(Comparator.comparingDouble(c -> evaluateCost(solution, c))) + .orElseThrow(); + solution.add(best); + } + return solution; + } + + protected double evaluateCost(S solution, Element element) { + // Problem-specific evaluation + return element.getCost(); + } +} +``` + +### 3. GRASP Construction + +See [GRASP documentation](grasp.md) for details on Greedy Randomized Adaptive Search Procedure. + +## How to Use + +### As Part of an Algorithm + +```java +// Use in a multi-start algorithm +var constructor = new MyGreedyConstructive(); +var improver = new MyLocalSearch(); + +var multiStart = new MultiStartAlgorithm<>( + "GRASP", + constructor, + improver, + 100 // iterations +); +``` + +### Standalone + +```java +// Build a single solution +var constructor = new MyRandomConstructive(); +var instance = loadInstance(); +var solution = constructor.construct(newSolution(instance)); +``` + +### In VNS + +```java +var vns = new VNSBuilder() + .withConstructive(new MyGreedyConstructive()) // Initial solution + .withImprover(new MyLocalSearch()) + .withShake(new MyShake()) + .build("VNS"); +``` + +## Implementation Guidelines + +### Solution Feasibility + +**Critical**: Constructive methods MUST return feasible solutions. The framework validates this in development mode. + +```java +@Override +public S construct(S solution) { + while (!solution.isComplete()) { + var element = selectNext(solution); + solution.add(element); + } + + // Framework automatically validates feasibility + assert solution.isFeasible() : "Constructed solution must be feasible"; + + return solution; +} +``` + +### Candidate List Management + +Efficiently manage which elements can still be added: + +```java +public abstract class ListBasedConstructive, I extends Instance> + extends Constructive { + + @Override + public S construct(S solution) { + var candidateList = initializeCandidateList(solution); + + while (!candidateList.isEmpty()) { + var selected = selectFromCandidateList(candidateList, solution); + solution.add(selected); + updateCandidateList(candidateList, selected, solution); + } + + return solution; + } + + protected abstract List initializeCandidateList(S solution); + protected abstract Element selectFromCandidateList(List candidates, S solution); + protected abstract void updateCandidateList(List candidates, Element selected, S solution); +} +``` + +### Performance Considerations + +```java +// Efficient: pre-calculate evaluation data +public class EfficientGreedy, I extends Instance> + extends Constructive { + + @Override + public S construct(S solution) { + // Pre-calculate costs once + Map costs = calculateAllCosts(solution); + + while (!solution.isComplete()) { + var best = costs.entrySet().stream() + .filter(e -> isAvailable(e.getKey(), solution)) + .min(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElseThrow(); + + solution.add(best); + + // Incrementally update affected costs only + updateAffectedCosts(costs, best, solution); + } + + return solution; + } +} +``` + +## Types of Constructive Methods + +Mork provides several specialized constructive method types: + +| Type | Description | Link | +|------|-------------|------| +| **GRASP** | Randomized greedy with restricted candidate lists | [GRASP](grasp.md) | +| **Reconstructive** | Rebuilds partially destroyed solutions | [Reconstructive](reconstructive.md) | +| **Greedy Random GRASP** | Element-by-element GRASP | [Greedy Random](greedy-random-grasp.md) | +| **Random Greedy GRASP** | Randomize-then-greedy GRASP | [Random Greedy](random-greedy-grasp.md) | + +## Related Java Classes + +- **[`Constructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Constructive.html)**: Base class for all constructive methods +- **[`GRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPConstructive.html)**: Base for GRASP constructive methods +- **[`Reconstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Reconstructive.html)**: Base for reconstruction methods +- **[`Algorithm`](../../../../apidocs/es/urjc/etsii/grafo/algorithm/Algorithm.html)**: Base algorithm class + +## Example Use Cases + +### TSP Nearest Neighbor + +```java +public class NearestNeighborTSP extends Constructive { + + @Override + public TSPSolution construct(TSPSolution solution) { + int current = 0; // Start from depot + solution.addCity(current); + + while (!solution.isComplete()) { + int nearest = findNearestUnvisited(solution, current); + solution.addCity(nearest); + current = nearest; + } + + return solution; + } + + private int findNearestUnvisited(TSPSolution solution, int from) { + var instance = solution.getInstance(); + return IntStream.range(0, instance.numberOfCities()) + .filter(city -> !solution.contains(city)) + .boxed() + .min(Comparator.comparingDouble(city -> instance.distance(from, city))) + .orElseThrow(); + } +} +``` + +### Knapsack Value/Weight Greedy + +```java +public class ValueDensityKnapsack extends Constructive { + + @Override + public KnapsackSolution construct(KnapsackSolution solution) { + var instance = solution.getInstance(); + + // Sort items by value/weight ratio + var items = instance.getItems().stream() + .sorted(Comparator.comparingDouble( + item -> -item.getValue() / (double) item.getWeight() + )) + .toList(); + + // Add items until capacity reached + for (var item : items) { + if (solution.canAdd(item)) { + solution.add(item); + } + } + + return solution; + } +} +``` + +## Best Practices + +1. **Always return feasible solutions**: This is mandatory +2. **Efficient data structures**: Use appropriate data structures for candidate management +3. **Incremental evaluation**: Update costs incrementally when possible +4. **Problem-specific knowledge**: Leverage domain knowledge for better construction +5. **Deterministic vs random**: Provide both deterministic (for testing) and random variants +6. **Parameter tuning**: Allow parameterization (e.g., GRASP alpha) + +## References + +[1] Resende, M. G., & Ribeiro, C. C. (2003). Greedy randomized adaptive search procedures. In *Handbook of Metaheuristics* (pp. 219-249). Springer. + +[2] Blum, C., & Roli, A. (2003). Metaheuristics in combinatorial optimization: Overview and conceptual comparison. *ACM Computing Surveys*, 35(3), 268-308. + +[3] Papadimitriou, C. H., & Steiglitz, K. (1998). *Combinatorial optimization: algorithms and complexity*. Courier Corporation. diff --git a/docs/concepts/algorithm-components/constructors/grasp.md b/docs/concepts/algorithm-components/constructors/grasp.md new file mode 100644 index 000000000..5ddcb2737 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/grasp.md @@ -0,0 +1,383 @@ +# GRASP Constructive + +GRASP (Greedy Randomized Adaptive Search Procedure) is a construction method that combines greedy selection with randomization. It builds solutions iteratively by selecting elements from a Restricted Candidate List (RCL) that contains the best available options. + +## Overview + +GRASP construction balances between pure greedy (deterministic) and pure random (too exploratory) by restricting choices to high-quality candidates and then selecting randomly among them. + +```mermaid +graph TD + A[Start with Empty Solution] --> B[Evaluate All Candidates] + B --> C[Create RCL with Best Candidates] + C --> D[Randomly Select from RCL] + D --> E[Add Element to Solution] + E --> F{Solution Complete?} + F -->|No| B + F -->|Yes| G[Return Solution] +``` + +## Key Concept: Restricted Candidate List (RCL) + +The RCL contains elements that are "good enough" according to a threshold parameter alpha (α): + +- **α = 0**: Pure greedy (only best element in RCL) +- **α = 1**: Pure random (all elements in RCL) +- **α ∈ (0, 1)**: Restricted randomization (typical: 0.1-0.5) + +### RCL Construction Methods + +**Cardinality-based**: Include top k elements +``` +RCL = {k best elements} +``` + +**Value-based** (most common): Include elements within threshold +``` +RCL = {e : cost(e) ≤ min_cost + α × (max_cost - min_cost)} +``` + +## Algorithm Outline + +``` +GRASP-Construct(alpha): + solution = empty + + while (solution not complete) { + CL = getCandidateList(solution) + costs = evaluate(CL, solution) + + min_cost = min(costs) + max_cost = max(costs) + threshold = min_cost + alpha * (max_cost - min_cost) + + RCL = {e in CL : costs[e] <= threshold} + selected = randomSelect(RCL) + + solution.add(selected) + } + + return solution +``` + +## How to Use + +### Basic Example + +```java +// Define how to evaluate candidates +public class MyGRASPConstructive + extends GRASPConstructive { + + public MyGRASPConstructive(double alpha) { + super("MyGRASP", alpha); + } + + @Override + protected List getCandidateList(MySolution solution) { + // Return all elements that can be added + return solution.getAvailableElements(); + } + + @Override + protected double evaluateElement(Element element, MySolution solution) { + // Return cost of adding this element + // Lower is better + return solution.evaluateAddition(element); + } + + @Override + protected void addElement(MySolution solution, Element element) { + // Add the selected element to solution + solution.add(element); + } +} + +// Use it +var grasp = new MyGRASPConstructive(0.3); // alpha = 0.3 +var solution = grasp.construct(newSolution(instance)); +``` + +### Cardinality-Based RCL + +```java +public class CardinalityGRASP, I extends Instance> + extends GRASPConstructive { + + private final int rclSize; + + public CardinalityGRASP(int rclSize) { + super("CardinalityGRASP", 0.0); // alpha not used + this.rclSize = rclSize; + } + + @Override + protected List buildRCL(List candidates, + Map costs) { + // Return top k candidates + return candidates.stream() + .sorted(Comparator.comparingDouble(costs::get)) + .limit(rclSize) + .collect(Collectors.toList()); + } + + @Override + protected List getCandidateList(S solution) { + return solution.getAvailableElements(); + } + + @Override + protected double evaluateElement(Element element, S solution) { + return solution.evaluateAddition(element); + } + + @Override + protected void addElement(S solution, Element element) { + solution.add(element); + } +} +``` + +### With Adaptive Alpha + +```java +public class AdaptiveGRASP, I extends Instance> + extends GRASPConstructive { + + private double[] alphaValues = {0.0, 0.1, 0.3, 0.5, 0.7, 1.0}; + private int[] alphaScores; + private int iteration = 0; + + public AdaptiveGRASP() { + super("AdaptiveGRASP", 0.3); // Initial alpha + this.alphaScores = new int[alphaValues.length]; + } + + @Override + public S construct(S solution) { + // Select alpha based on past performance + int alphaIndex = selectAlphaIndex(); + this.alpha = alphaValues[alphaIndex]; + + S result = super.construct(solution); + + // Update alpha scores based on solution quality + updateAlphaScore(alphaIndex, result.getScore()); + iteration++; + + return result; + } + + private int selectAlphaIndex() { + if (iteration < alphaValues.length * 10) { + // Exploration phase: round-robin + return iteration % alphaValues.length; + } else { + // Exploitation: select best alpha with some randomization + return selectWeightedRandom(alphaScores); + } + } +} +``` + +## GRASP Variants + +### 1. Greedy Random GRASP + +See [Greedy Random GRASP](greedy-random-grasp.md) - builds solution element by element with GRASP selection at each step. + +### 2. Random Greedy GRASP + +See [Random Greedy GRASP](random-greedy-grasp.md) - randomizes element order first, then applies greedy selection. + +### 3. Reactive GRASP + +Adapts alpha based on solution quality: + +```java +public class ReactiveGRASP, I extends Instance> + extends GRASPConstructive { + + @Override + public S construct(S solution) { + // Adjust alpha based on recent solution quality + adjustAlpha(); + return super.construct(solution); + } + + private void adjustAlpha() { + // If recent solutions are poor, increase diversification + if (averageRecentQuality < targetQuality) { + alpha = Math.min(1.0, alpha + 0.1); + } else { + alpha = Math.max(0.0, alpha - 0.05); + } + } +} +``` + +## Implementation Notes + +### Efficient RCL Management + +```java +// Efficient: pre-sort and threshold +public class EfficientGRASP, I extends Instance> + extends GRASPConstructive { + + @Override + protected List buildRCL(List candidates, + Map costs) { + if (candidates.isEmpty()) return candidates; + + // Find min and max costs + double minCost = Double.MAX_VALUE; + double maxCost = Double.MIN_VALUE; + for (double cost : costs.values()) { + minCost = Math.min(minCost, cost); + maxCost = Math.max(maxCost, cost); + } + + // Build RCL with threshold + double threshold = minCost + alpha * (maxCost - minCost); + return candidates.stream() + .filter(c -> costs.get(c) <= threshold) + .collect(Collectors.toList()); + } +} +``` + +### Incremental Evaluation + +```java +public abstract class IncrementalGRASP, I extends Instance> + extends GRASPConstructive { + + private Map cachedCosts; + + @Override + public S construct(S solution) { + cachedCosts = new HashMap<>(); + // Initialize all costs + for (Element e : solution.getAllElements()) { + cachedCosts.put(e, evaluateElement(e, solution)); + } + + return super.construct(solution); + } + + @Override + protected void addElement(S solution, Element element) { + super.addElement(solution, element); + // Only update affected elements' costs + updateAffectedCosts(element, solution); + } + + protected abstract void updateAffectedCosts(Element added, S solution); +} +``` + +## Parameter Tuning + +### Recommended Alpha Values + +| α Value | Behavior | Use Case | +|---------|----------|----------| +| 0.0 | Pure greedy | Quick, deterministic solutions | +| 0.1-0.2 | Slightly random | Good balance, small search space | +| 0.3-0.5 | Balanced | General purpose, most common | +| 0.6-0.8 | More random | Large search space, more diversity | +| 1.0 | Pure random | Maximum diversity, weak solutions | + +### Auto-tuning Alpha + +```java +// Use irace for automatic configuration +@AutoconfigConstructor +public class TunableGRASP, I extends Instance> + extends GRASPConstructive { + + public TunableGRASP( + @Param(min = 0.0, max = 1.0) double alpha) { + super("TunableGRASP", alpha); + } + + // Implementation... +} +``` + +## Related Java Classes + +- **[`GRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPConstructive.html)**: Base class for GRASP constructors +- **[`GreedyRandomGRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GreedyRandomGRASPConstructive.html)**: Element-by-element GRASP +- **[`RandomGreedyGRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/RandomGreedyGRASPConstructive.html)**: Randomize-then-greedy +- **[`GRASPListManager`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPListManager.html)**: Manages candidate lists + +## Example Use Cases + +### TSP with GRASP + +```java +public class TSPGRASPConstructive extends GRASPConstructive { + + public TSPGRASPConstructive(double alpha) { + super("TSP-GRASP", alpha); + } + + @Override + protected List getCandidateList(TSPSolution solution) { + return solution.getUnvisitedCities(); + } + + @Override + protected double evaluateElement(City city, TSPSolution solution) { + // Cost = distance from current city + return solution.getInstance().distance(solution.getCurrentCity(), city); + } + + @Override + protected void addElement(TSPSolution solution, City city) { + solution.visit(city); + } +} +``` + +### Job Scheduling with GRASP + +```java +public class SchedulingGRASP extends GRASPConstructive { + + @Override + protected List getCandidateList(ScheduleSolution solution) { + return solution.getUnscheduledJobs(); + } + + @Override + protected double evaluateElement(Job job, ScheduleSolution solution) { + // Minimize completion time + return solution.evaluateJobInsertion(job); + } + + @Override + protected void addElement(ScheduleSolution solution, Job job) { + solution.scheduleJob(job); + } +} +``` + +## Best Practices + +1. **Alpha tuning**: Start with α ∈ [0.2, 0.4] and tune based on results +2. **Efficient evaluation**: Cache and incrementally update element costs +3. **Appropriate RCL**: Use value-based for continuous costs, cardinality for discrete +4. **Combine with LS**: GRASP typically followed by local search (GRASP = Construction + LS loop) +5. **Monitor RCL size**: Too small → greedy, too large → random +6. **Problem-specific costs**: Use domain knowledge for better evaluation functions + +## References + +[1] Feo, T. A., & Resende, M. G. (1995). Greedy randomized adaptive search procedures. *Journal of Global Optimization*, 6(2), 109-133. + +[2] Resende, M. G., & Ribeiro, C. C. (2003). Greedy randomized adaptive search procedures. In *Handbook of Metaheuristics* (pp. 219-249). Springer. + +[3] Prais, M., & Ribeiro, C. C. (2000). Reactive GRASP: An application to a matrix decomposition problem in TDMA traffic assignment. *INFORMS Journal on Computing*, 12(3), 164-176. diff --git a/docs/concepts/algorithm-components/constructors/greedy-random-grasp.md b/docs/concepts/algorithm-components/constructors/greedy-random-grasp.md new file mode 100644 index 000000000..9f94136c8 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/greedy-random-grasp.md @@ -0,0 +1,240 @@ +# Greedy Random GRASP + +Greedy Random GRASP is a variant of GRASP that builds solutions element by element, applying GRASP's randomized greedy selection at each construction step. + +## Overview + +This variant focuses on the **element-by-element** construction process, where each element added to the solution is selected using GRASP's RCL mechanism. + +```mermaid +graph LR + A[Empty Solution] --> B[Evaluate All Candidates] + B --> C[Build RCL with Alpha] + C --> D[Random Select from RCL] + D --> E[Add Element] + E --> F{Complete?} + F -->|No| B + F -->|Yes| G[Final Solution] +``` + +## Key Characteristics + +- **Sequential construction**: Elements added one at a time +- **Adaptive selection**: RCL updated after each addition +- **Incremental evaluation**: Costs recalculated as solution grows + +## Algorithm Outline + +``` +GreedyRandomGRASP(alpha): + solution = empty + + while (solution not complete) { + candidates = getAvailableCandidates(solution) + costs = evaluateAll(candidates, solution) + + rcl = buildRCL(candidates, costs, alpha) + selected = randomSelect(rcl) + + add(selected, solution) + } + + return solution +``` + +## How to Use + +### Basic Implementation + +```java +public class MyGreedyRandomGRASP + extends GreedyRandomGRASPConstructive { + + public MyGreedyRandomGRASP(double alpha) { + super(alpha); + } + + @Override + protected List getCandidates(MySolution solution) { + // Return elements that can still be added + return solution.getAvailableElements(); + } + + @Override + protected double evaluate(Element element, MySolution solution) { + // Cost of adding this element given current solution state + return solution.evaluateIncrementalCost(element); + } + + @Override + protected void add(Element element, MySolution solution) { + solution.addElement(element); + } +} +``` + +### Usage Example + +```java +// Create constructor +var constructor = new MyGreedyRandomGRASP(0.3); + +// Use in algorithm +var grasp = new MultiStartAlgorithm<>( + "GRASP", + constructor, + new MyLocalSearch(), + 100 // iterations +); +``` + +## Comparison with Standard GRASP + +| Aspect | Greedy Random GRASP | Standard GRASP | +|--------|---------------------|----------------| +| **Construction** | Element-by-element | Can be batch or element-wise | +| **RCL Updates** | After each element | Typically after each element | +| **Implementation** | Provided by framework | Abstract base to implement | +| **Use Case** | Most construction problems | General purpose | + +## Implementation Notes + +### Performance Optimization + +```java +public class EfficientGreedyRandomGRASP, I extends Instance> + extends GreedyRandomGRASPConstructive { + + private Map costCache; + + @Override + public S construct(S solution) { + costCache = new HashMap<>(); + return super.construct(solution); + } + + @Override + protected double evaluate(Element element, S solution) { + // Use cached cost if available and not invalidated + return costCache.computeIfAbsent(element, + e -> solution.evaluateIncrementalCost(e)); + } + + @Override + protected void add(Element element, S solution) { + super.add(element, solution); + // Invalidate affected cached costs + invalidateAffectedCosts(element); + } +} +``` + +### With List Manager + +```java +// Use GRASPListManager for efficient candidate management +var listManager = new GRASPListManager() { + @Override + public List initialize(MySolution solution) { + return solution.getAllElements(); + } + + @Override + public double evaluate(Element element, MySolution solution) { + return solution.evaluateIncrementalCost(element); + } + + @Override + public void update(Element selected, MySolution solution) { + // Remove selected and any now-invalid elements + removeElement(selected); + removeInvalidElements(solution); + } +}; + +var constructor = new GreedyRandomGRASPConstructive<>(0.3, listManager); +``` + +## Related Java Classes + +- **[`GreedyRandomGRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GreedyRandomGRASPConstructive.html)**: Main implementation +- **[`GRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPConstructive.html)**: Base GRASP class +- **[`GRASPListManager`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPListManager.html)**: Candidate list management +- **[`Constructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Constructive.html)**: Base constructive class + +## Example Use Cases + +### TSP Nearest Neighbor with Randomization + +```java +public class TSPGreedyRandomGRASP + extends GreedyRandomGRASPConstructive { + + @Override + protected List getCandidates(TSPSolution solution) { + return solution.getUnvisitedCities(); + } + + @Override + protected double evaluate(City city, TSPSolution solution) { + return solution.getInstance() + .distance(solution.getLastCity(), city); + } + + @Override + protected void add(City city, TSPSolution solution) { + solution.addCity(city); + } +} +``` + +### Knapsack Item Selection + +```java +public class KnapsackGreedyRandomGRASP + extends GreedyRandomGRASPConstructive { + + @Override + protected List getCandidates(KnapsackSolution solution) { + return solution.getUnselectedItems().stream() + .filter(item -> solution.hasCapacityFor(item)) + .collect(Collectors.toList()); + } + + @Override + protected double evaluate(Item item, KnapsackSolution solution) { + // Minimize negative value/weight ratio (maximize ratio) + return -item.getValue() / (double) item.getWeight(); + } + + @Override + protected void add(Item item, KnapsackSolution solution) { + solution.addItem(item); + } +} +``` + +## Best Practices + +1. **Efficient candidate management**: Update available candidates incrementally +2. **Incremental cost evaluation**: Avoid full re-evaluation at each step +3. **Alpha tuning**: Typical range 0.2-0.4 works well for most problems +4. **Cache when possible**: Store and update costs instead of recalculating +5. **Early termination**: Check feasibility constraints before full evaluation + +## When to Use + +**Good for:** +- Problems where each element addition significantly changes costs +- Sequential decision-making problems +- When incremental evaluation is much cheaper than batch evaluation + +**Consider alternatives when:** +- All elements can be evaluated independently (use standard GRASP) +- Construction order doesn't matter much (use random greedy variant) + +## References + +[1] Feo, T. A., & Resende, M. G. (1995). Greedy randomized adaptive search procedures. *Journal of Global Optimization*, 6(2), 109-133. + +[2] Resende, M. G., & Ribeiro, C. C. (2003). Greedy randomized adaptive search procedures. In *Handbook of Metaheuristics* (pp. 219-249). Springer. diff --git a/docs/concepts/algorithm-components/constructors/random-greedy-grasp.md b/docs/concepts/algorithm-components/constructors/random-greedy-grasp.md new file mode 100644 index 000000000..f1f1c7566 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/random-greedy-grasp.md @@ -0,0 +1,296 @@ +# Random Greedy GRASP + +Random Greedy GRASP is a GRASP variant that first randomizes the order of elements, then applies greedy selection. This approach is useful when the construction order can be predetermined or randomized independently of element costs. + +## Overview + +Instead of evaluating and selecting elements one-by-one, this variant: +1. **Randomizes** the construction order first +2. **Applies greedy selection** within that randomized order + +```mermaid +graph LR + A[All Elements] --> B[Randomize Order] + B --> C[Apply Greedy Selection] + C --> D[Take Element 1] + D --> E[Take Element 2] + E --> F[...] + F --> G[Complete Solution] +``` + +## Key Characteristics + +- **Pre-randomization**: Order determined before construction +- **Greedy within order**: Select greedily respecting the randomized sequence +- **Faster**: Often requires fewer evaluations than element-by-element GRASP +- **Different diversity**: Exploration comes from order randomization, not selection + +## Algorithm Outline + +``` +RandomGreedyGRASP(alpha): + elements = getAllElements() + randomizedOrder = randomize(elements, alpha) + + solution = empty + for element in randomizedOrder { + if (canAdd(element, solution)) { + add(element, solution) + } + } + + return solution +``` + +## How to Use + +### Basic Implementation + +```java +public class MyRandomGreedyGRASP + extends RandomGreedyGRASPConstructive { + + public MyRandomGreedyGRASP(double alpha) { + super(alpha); + } + + @Override + protected List getAllElements(MySolution solution) { + // Return all possible elements + return solution.getInstance().getAllElements(); + } + + @Override + protected List randomizeOrder(List elements, double alpha) { + // Create RCL and randomize order + var costs = elements.stream() + .collect(Collectors.toMap(e -> e, this::evaluateCost)); + + double minCost = costs.values().stream().min(Double::compare).orElse(0.0); + double maxCost = costs.values().stream().max(Double::compare).orElse(1.0); + double threshold = minCost + alpha * (maxCost - minCost); + + // Separate into RCL and non-RCL + var rcl = elements.stream() + .filter(e -> costs.get(e) <= threshold) + .collect(Collectors.toList()); + var rest = elements.stream() + .filter(e -> costs.get(e) > threshold) + .collect(Collectors.toList()); + + // Shuffle RCL, then append rest + Collections.shuffle(rcl); + rcl.addAll(rest); + return rcl; + } + + @Override + protected boolean canAdd(Element element, MySolution solution) { + return solution.isFeasibleToAdd(element); + } + + @Override + protected void add(Element element, MySolution solution) { + solution.addElement(element); + } +} +``` + +### Simple Order Randomization + +```java +public class SimpleRandomGreedyGRASP, I extends Instance> + extends RandomGreedyGRASPConstructive { + + @Override + protected List randomizeOrder(List elements, double alpha) { + // Simple: just shuffle with some bias towards better elements + List result = new ArrayList<>(elements); + + if (alpha == 0.0) { + // Pure greedy: sort by cost + result.sort(Comparator.comparingDouble(this::evaluateCost)); + } else if (alpha == 1.0) { + // Pure random: complete shuffle + Collections.shuffle(result); + } else { + // Partial randomization: weighted shuffle + weightedShuffle(result, alpha); + } + + return result; + } + + private void weightedShuffle(List elements, double alpha) { + // Better elements have higher probability of appearing first + // Implementation depends on problem specifics + } +} +``` + +## Comparison with Other GRASP Variants + +| Aspect | Random Greedy | Greedy Random | Standard GRASP | +|--------|---------------|---------------|----------------| +| **Randomization** | Before construction | During construction | During construction | +| **Evaluations** | Once | At each step | At each step | +| **Speed** | Fastest | Medium | Medium | +| **Diversity** | From order | From selection | From selection | +| **Best for** | Order-sensitive problems | Cost-sensitive problems | General purpose | + +## Implementation Notes + +### Efficient Batch Evaluation + +```java +@Override +protected List randomizeOrder(List elements, double alpha) { + // Evaluate all elements once in batch + Map costs = batchEvaluate(elements); + + // Use costs to create biased random order + return createBiasedOrder(elements, costs, alpha); +} + +private Map batchEvaluate(List elements) { + // Evaluate all elements in parallel if possible + return elements.parallelStream() + .collect(Collectors.toMap( + e -> e, + e -> evaluateCost(e) + )); +} +``` + +### Adaptive Order Generation + +```java +public class AdaptiveRandomGreedy, I extends Instance> + extends RandomGreedyGRASPConstructive { + + private List alphaValues = List.of(0.0, 0.25, 0.5, 0.75, 1.0); + private Map alphaQuality = new HashMap<>(); + + @Override + public S construct(S solution) { + // Select alpha based on past performance + double selectedAlpha = selectBestAlpha(); + + S result = constructWithAlpha(solution, selectedAlpha); + + // Update alpha quality + updateAlphaQuality(selectedAlpha, result.getScore()); + + return result; + } +} +``` + +## Related Java Classes + +- **[`RandomGreedyGRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/RandomGreedyGRASPConstructive.html)**: Main implementation +- **[`GRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GRASPConstructive.html)**: Base GRASP class +- **[`GreedyRandomGRASPConstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/grasp/GreedyRandomGRASPConstructive.html)**: Alternative variant +- **[`Constructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Constructive.html)**: Base constructive class + +## Example Use Cases + +### Graph Coloring + +```java +public class ColoringRandomGreedyGRASP + extends RandomGreedyGRASPConstructive { + + @Override + protected List getAllElements(ColoringSolution solution) { + return solution.getInstance().getVertices(); + } + + @Override + protected List randomizeOrder(List vertices, double alpha) { + // Order by degree with randomization + var sortedByDegree = vertices.stream() + .sorted(Comparator.comparingInt(Vertex::getDegree).reversed()) + .collect(Collectors.toList()); + + // Randomize top (1-alpha)% of list + int rclSize = (int) ((1 - alpha) * vertices.size()); + Collections.shuffle(sortedByDegree.subList(0, rclSize)); + + return sortedByDegree; + } + + @Override + protected void add(Vertex vertex, ColoringSolution solution) { + solution.assignSmallestValidColor(vertex); + } +} +``` + +### Bin Packing + +```java +public class BinPackingRandomGreedyGRASP + extends RandomGreedyGRASPConstructive { + + @Override + protected List getAllElements(BinSolution solution) { + return solution.getInstance().getItems(); + } + + @Override + protected List randomizeOrder(List items, double alpha) { + // Randomize order with bias towards larger items + var sorted = items.stream() + .sorted(Comparator.comparingInt(Item::getSize).reversed()) + .collect(Collectors.toList()); + + // Apply alpha-based partial randomization + applyPartialShuffle(sorted, alpha); + return sorted; + } + + @Override + protected void add(Item item, BinSolution solution) { + solution.packItemInBestBin(item); + } +} +``` + +## Best Practices + +1. **Choose based on problem**: Use when element ordering matters +2. **Batch evaluation**: Evaluate all elements once for efficiency +3. **Bias towards quality**: Even with randomization, favor better elements +4. **Alpha interpretation**: Different from standard GRASP (affects order, not selection) +5. **Combine with local search**: Like all GRASP, typically followed by improvement + +## When to Use + +**Good for:** +- Problems where construction order is critical +- When batch evaluation is available +- Sequential assignment problems +- When faster construction is needed + +**Not ideal for:** +- Problems where each addition significantly changes remaining costs +- When incremental evaluation is much cheaper than batch +- Dynamic problems where costs change frequently + +## Performance Characteristics + +**Advantages:** +- **Speed**: Single evaluation phase, no repeated RCL building +- **Simplicity**: Easier to implement for some problems +- **Predictability**: Deterministic given a random seed + +**Disadvantages:** +- **Less adaptive**: Cannot react to partial solution state +- **Different diversity**: May miss solutions accessible via adaptive selection + +## References + +[1] Feo, T. A., & Resende, M. G. (1995). Greedy randomized adaptive search procedures. *Journal of Global Optimization*, 6(2), 109-133. + +[2] Resende, M. G., & Ribeiro, C. C. (2003). Greedy randomized adaptive search procedures. In *Handbook of Metaheuristics* (pp. 219-249). Springer. diff --git a/docs/concepts/algorithm-components/constructors/reconstructive.md b/docs/concepts/algorithm-components/constructors/reconstructive.md new file mode 100644 index 000000000..9b3df2ed7 --- /dev/null +++ b/docs/concepts/algorithm-components/constructors/reconstructive.md @@ -0,0 +1,356 @@ +# Reconstructive + +Reconstructive methods are specialized constructive methods designed to rebuild partially destroyed solutions. They are commonly used in destruction-reconstruction metaheuristics like Iterated Greedy and Large Neighborhood Search. + +## Overview + +Unlike regular constructive methods that start from empty solutions, reconstructive methods: +- Take **partially complete** solutions as input +- **Rebuild** the missing parts +- Preserve the existing structure where possible + +```mermaid +graph LR + A[Complete Solution] --> B[Destruction] + B --> C[Partial Solution] + C --> D[Reconstructive Method] + D --> E[Rebuild Missing Parts] + E --> F[New Complete Solution] +``` + +## Key Differences from Constructive + +| Aspect | Constructive | Reconstructive | +|--------|--------------|----------------| +| **Input** | Empty solution | Partial solution | +| **Goal** | Build from scratch | Complete existing | +| **Context** | No prior structure | Existing structure to respect | +| **Use** | Initial solutions | Iterated algorithms | + +## Algorithm Outline + +``` +Reconstruct(partialSolution): + while (partialSolution not complete) { + candidates = getUnassignedElements(partialSolution) + selected = selectElement(candidates, partialSolution) + add(selected, partialSolution) + } + + return partialSolution +``` + +## How to Use + +### Basic Implementation + +```java +public class MyReconstructor + extends Reconstructive { + + public MyReconstructor() { + super("MyReconstructor"); + } + + @Override + public MySolution reconstruct(MySolution partial) { + // Get elements that were removed + var missing = partial.getMissingElements(); + + // Add them back using some strategy + while (!missing.isEmpty()) { + var best = findBestToAdd(missing, partial); + partial.add(best); + missing.remove(best); + } + + return partial; + } + + private Element findBestToAdd(List candidates, MySolution solution) { + return candidates.stream() + .min(Comparator.comparingDouble(e -> evaluateInsertion(e, solution))) + .orElseThrow(); + } +} +``` + +### GRASP-based Reconstruction + +```java +public class GRASPReconstructor + extends Reconstructive { + + private final double alpha; + + public GRASPReconstructor(double alpha) { + super("GRASP-Reconstructor"); + this.alpha = alpha; + } + + @Override + public MySolution reconstruct(MySolution partial) { + var missing = partial.getMissingElements(); + + while (!missing.isEmpty()) { + // Evaluate all missing elements + var costs = missing.stream() + .collect(Collectors.toMap( + e -> e, + e -> evaluateInsertion(e, partial) + )); + + // Build RCL + var rcl = buildRCL(missing, costs, alpha); + + // Random selection from RCL + var selected = rcl.get(ThreadLocalRandom.current().nextInt(rcl.size())); + + partial.add(selected); + missing.remove(selected); + } + + return partial; + } +} +``` + +### With Position Awareness + +```java +public class PositionAwareReconstructor + extends Reconstructive { + + @Override + public MySolution reconstruct(MySolution partial) { + var missing = partial.getMissingElements(); + + while (!missing.isEmpty()) { + // For each missing element, find best insertion position + Element bestElement = null; + int bestPosition = -1; + double bestCost = Double.MAX_VALUE; + + for (Element element : missing) { + for (int pos = 0; pos <= partial.size(); pos++) { + double cost = evaluateInsertion(element, pos, partial); + if (cost < bestCost) { + bestCost = cost; + bestElement = element; + bestPosition = pos; + } + } + } + + partial.insert(bestElement, bestPosition); + missing.remove(bestElement); + } + + return partial; + } +} +``` + +## Use in Iterated Greedy + +```java +// Destructor removes elements +Destructive destructor = + new RandomDestructor<>(4); // Remove 4 elements + +// Reconstructor adds them back +Reconstructive reconstructor = + new GRASPReconstructor<>(0.3); + +// Combine in Iterated Greedy +var ig = new IteratedGreedy<>( + "IG", + constructor, // Initial solution + destructor, // Remove 4 elements + reconstructor, // Add them back + improver // Optional LS +); +``` + +## Use in Destroy-Rebuild Shake + +```java +// Combined destroy-rebuild operator +var destroyRebuild = new DestroyRebuild<>( + new WorstDestructor<>(5), // Remove 5 worst elements + new GreedyReconstructor<>() // Add them back greedily +); + +// Use as shake in VNS +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(destroyRebuild) // Use destroy-rebuild as perturbation + .build("VNS"); +``` + +## Implementation Strategies + +### 1. Greedy Reconstruction + +Always add the best element: + +```java +@Override +public MySolution reconstruct(MySolution partial) { + var missing = partial.getMissingElements(); + missing.sort(Comparator.comparingDouble(e -> evaluateCost(e, partial))); + + for (Element element : missing) { + partial.add(element); + } + + return partial; +} +``` + +### 2. Nearest Neighbor Reconstruction + +Add elements based on proximity to existing elements: + +```java +@Override +public MySolution reconstruct(MySolution partial) { + var missing = new HashSet<>(partial.getMissingElements()); + + while (!missing.isEmpty()) { + // Find missing element nearest to any existing element + Element nearest = findNearestToExisting(missing, partial); + partial.add(nearest); + missing.remove(nearest); + } + + return partial; +} +``` + +### 3. Regret-based Reconstruction + +Consider the "regret" of not adding an element now: + +```java +@Override +public MySolution reconstruct(MySolution partial) { + var missing = partial.getMissingElements(); + + while (!missing.isEmpty()) { + Element bestRegret = null; + double maxRegret = Double.MIN_VALUE; + + for (Element element : missing) { + // Regret = difference between best and second-best insertion cost + double regret = calculateRegret(element, partial); + if (regret > maxRegret) { + maxRegret = regret; + bestRegret = element; + } + } + + partial.add(bestRegret); + missing.remove(bestRegret); + } + + return partial; +} +``` + +## Related Java Classes + +- **[`Reconstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Reconstructive.html)**: Base class for reconstruction methods +- **[`Constructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Constructive.html)**: Parent class +- **[`Destructive`](../../../../apidocs/es/urjc/etsii/grafo/shake/Destructive.html)**: Destruction operators +- **[`DestroyRebuild`](../../../../apidocs/es/urjc/etsii/grafo/shake/DestroyRebuild.html)**: Combined operator +- **[`IteratedGreedy`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/IteratedGreedy.html)**: Uses reconstructors + +## Example Use Cases + +### TSP Reconstruction + +```java +public class TSPReconstructor extends Reconstructive { + + @Override + public TSPSolution reconstruct(TSPSolution partial) { + var instance = partial.getInstance(); + var missing = partial.getMissingCities(); + + while (!missing.isEmpty()) { + // For each missing city, find best insertion position + City bestCity = null; + int bestPos = -1; + double minIncrease = Double.MAX_VALUE; + + for (City city : missing) { + for (int i = 0; i < partial.size(); i++) { + // Cost of inserting city between positions i and i+1 + double increase = instance.distance(partial.get(i), city) + + instance.distance(city, partial.get(i+1)) + - instance.distance(partial.get(i), partial.get(i+1)); + + if (increase < minIncrease) { + minIncrease = increase; + bestCity = city; + bestPos = i + 1; + } + } + } + + partial.insert(bestCity, bestPos); + missing.remove(bestCity); + } + + return partial; + } +} +``` + +### Job Scheduling Reconstruction + +```java +public class ScheduleReconstructor extends Reconstructive { + + @Override + public ScheduleSolution reconstruct(ScheduleSolution partial) { + var unscheduled = partial.getUnscheduledJobs(); + + // Sort by priority or earliest due date + unscheduled.sort(Comparator.comparingInt(Job::getDueDate)); + + for (Job job : unscheduled) { + // Insert at position that minimizes tardiness + int bestPos = findBestPosition(job, partial); + partial.insertJob(job, bestPos); + } + + return partial; + } +} +``` + +## Best Practices + +1. **Context awareness**: Use information from the partial solution +2. **Efficient insertion**: Pre-calculate costs when possible +3. **Problem-specific heuristics**: Leverage domain knowledge +4. **Flexible strategies**: Support both greedy and randomized reconstruction +5. **Maintain feasibility**: Ensure reconstructed solutions remain valid + +## Performance Tips + +- **Cache evaluations**: Many insertions have similar costs +- **Incremental updates**: Update costs as elements are added +- **Parallel evaluation**: Evaluate insertion costs in parallel +- **Limit search**: Don't always evaluate all positions for all elements + +## References + +[1] Ruiz, R., & Stützle, T. (2007). A simple and effective iterated greedy algorithm for the permutation flowshop scheduling problem. *European Journal of Operational Research*, 177(3), 2033-2049. + +[2] Shaw, P. (1998). Using constraint programming and local search methods to solve vehicle routing problems. In *CP* (Vol. 98, pp. 417-431). + +[3] Pisinger, D., & Ropke, S. (2007). A general heuristic for vehicle routing problems. *Computers & Operations Research*, 34(8), 2403-2435. diff --git a/docs/concepts/algorithm-components/improvers/.pages b/docs/concepts/algorithm-components/improvers/.pages new file mode 100644 index 000000000..dea9c8662 --- /dev/null +++ b/docs/concepts/algorithm-components/improvers/.pages @@ -0,0 +1,6 @@ +title: Improvers +nav: + - improver.md + - local-search.md + - best-improvement.md + - first-improvement.md diff --git a/docs/concepts/algorithm-components/improvers/best-improvement.md b/docs/concepts/algorithm-components/improvers/best-improvement.md new file mode 100644 index 000000000..3e73d09dd --- /dev/null +++ b/docs/concepts/algorithm-components/improvers/best-improvement.md @@ -0,0 +1,351 @@ +# Best Improvement Local Search + +Best Improvement (also known as Steepest Descent) is a local search strategy that evaluates all moves in the neighborhood and always applies the move with the largest improvement. + +## Overview + +Best Improvement explores the **entire neighborhood** at each iteration and selects the single best move to apply. + +```mermaid +graph TD + A[Current Solution] --> B[Evaluate ALL Moves] + B --> C[Select Best Move] + C --> D{Improvement?} + D -->|Yes| E[Apply Best Move] + E --> A + D -->|No| F[Local Optimum] +``` + +## Algorithm Outline + +``` +BestImprovementLS(solution): + improved = true + + while (improved) { + bestMove = null + bestDelta = 0 + + for (move in neighborhood(solution)) { + delta = evaluate(move, solution) + if (delta < bestDelta) { // For minimization + bestDelta = delta + bestMove = move + } + } + + if (bestMove != null) { + apply(bestMove, solution) + improved = true + } else { + improved = false // No improving move found + } + } + + return solution +``` + +## Key Characteristics + +| Aspect | Best Improvement | +|--------|------------------| +| **Exploration** | Complete neighborhood each iteration | +| **Move Selection** | Best improving move | +| **Evaluations** | O(n×k) where n=neighborhood size, k=iterations | +| **Speed** | Slower per iteration | +| **Quality** | Better local optima | +| **Iterations** | Fewer iterations to convergence | + +## How to Use + +### Basic Usage + +```java +// Create best improvement local search +var bestLS = new LocalSearchBestImprovement() { + @Override + protected Iterable getNeighborhood(MySolution solution) { + return generateAllMoves(solution); + } +}; + +// Apply to solution +solution = bestLS.improve(solution); +``` + +### Custom Implementation + +```java +public class MyBestImprovementLS, I extends Instance> + extends LocalSearchBestImprovement { + + public MyBestImprovementLS() { + super("MyBestLS"); + } + + @Override + protected Iterable getNeighborhood(S solution) { + List moves = new ArrayList<>(); + + // Generate all possible moves + for (int i = 0; i < solution.size(); i++) { + for (int j = i + 1; j < solution.size(); j++) { + moves.add(new SwapMove(i, j)); + } + } + + return moves; + } +} +``` + +### With Early Termination + +```java +public class EfficientBestImprovement, I extends Instance> + extends LocalSearchBestImprovement { + + private final double improvementThreshold; + + public EfficientBestImprovement(double threshold) { + super("EfficientBestLS"); + this.improvementThreshold = threshold; + } + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + Move bestMove = null; + double bestDelta = -improvementThreshold; // Only accept significant improvements + + for (Move move : getNeighborhood(solution)) { + double delta = move.evaluate(solution); + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null && bestDelta < -improvementThreshold) { + bestMove.apply(solution); + improved = true; + } else { + improved = false; + } + } + + return solution; + } +} +``` + +## Comparison with First Improvement + +| Aspect | Best Improvement | First Improvement | +|--------|-----------------|-------------------| +| **Evaluations per iteration** | All moves | Until first improvement | +| **Time per iteration** | Longer | Shorter | +| **Iterations to convergence** | Fewer | More | +| **Total time** | Variable (problem-dependent) | Variable | +| **Local optimum quality** | Often better | Often worse | +| **Recommended for** | Small neighborhoods | Large neighborhoods | + +## When to Use + +### Use Best Improvement When: + +✅ **Small neighborhood**: O(n) or O(n²) moves +✅ **Expensive moves**: Evaluation dominates iteration overhead +✅ **Quality matters**: Need best possible local optimum +✅ **Plateau navigation**: Many equal-value neighbors + +### Use First Improvement When: + +❌ **Large neighborhood**: O(n³) or more moves +❌ **Cheap moves**: Iteration overhead significant +❌ **Speed matters**: Need quick improvement +❌ **Diverse search**: Want more exploration + +## Implementation Techniques + +### Parallel Evaluation + +```java +@Override +public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + var moves = getNeighborhood(solution); + + // Evaluate moves in parallel + Move bestMove = moves.parallelStream() + .min(Comparator.comparingDouble(m -> m.evaluate(solution))) + .filter(m -> m.improves(solution)) + .orElse(null); + + if (bestMove != null) { + bestMove.apply(solution); + improved = true; + } else { + improved = false; + } + } + + return solution; +} +``` + +### Incremental Best Move Selection + +```java +public class IncrementalBestImprovement, I extends Instance> + extends LocalSearchBestImprovement { + + private Map moveDeltas; + + @Override + public S improve(S solution) { + moveDeltas = new HashMap<>(); + + boolean improved = true; + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + // Find best move from cache or evaluate + Move bestMove = null; + double bestDelta = 0; + + for (Move move : getNeighborhood(solution)) { + double delta = moveDeltas.computeIfAbsent(move, + m -> m.evaluate(solution)); + + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null) { + bestMove.apply(solution); + // Invalidate affected moves + invalidateAffectedMoves(bestMove, solution); + improved = true; + } + } + + return solution; + } + + private void invalidateAffectedMoves(Move applied, S solution) { + // Remove moves that are no longer valid or whose deltas changed + moveDeltas.keySet().removeIf(move -> isAffectedBy(move, applied)); + } +} +``` + +## Related Java Classes + +- **[`LocalSearchBestImprovement`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchBestImprovement.html)**: Best improvement implementation +- **[`LocalSearch`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearch.html)**: Base local search class +- **[`FirstImprovementLS`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchFirstImprovement.html)**: Alternative strategy +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base improver class + +## Example Use Cases + +### TSP 2-Opt Best Improvement + +```java +public class TSPBestImprovement2Opt + extends LocalSearchBestImprovement { + + @Override + protected Iterable getNeighborhood(TSPSolution solution) { + List moves = new ArrayList<>(); + int n = solution.size(); + + // Generate all 2-opt moves + for (int i = 0; i < n - 1; i++) { + for (int j = i + 2; j < n; j++) { + moves.add(new TwoOptMove(i, j)); + } + } + + return moves; + } +} +``` + +### Knapsack Flip Best Improvement + +```java +public class KnapsackBestFlip + extends LocalSearchBestImprovement { + + @Override + protected Iterable getNeighborhood(KnapsackSolution solution) { + var instance = solution.getInstance(); + return IntStream.range(0, instance.getNumItems()) + .mapToObj(i -> new FlipMove(i)) // Flip in/out of knapsack + .collect(Collectors.toList()); + } +} +``` + +## Best Practices + +1. **Profile first**: Measure if best improvement is actually faster for your problem +2. **Parallel evaluation**: Use parallel streams for independent move evaluation +3. **Cache smartly**: Cache evaluations but invalidate correctly +4. **Hybrid approach**: Start with best, switch to first when improvement slows +5. **Consider VND**: If you have multiple neighborhoods, use VND with best improvement per neighborhood + +## Performance Optimization + +```java +// Optimize by exploiting problem structure +public class OptimizedBestImprovement, I extends Instance> + extends LocalSearchBestImprovement { + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + // Pre-calculate data structures for fast evaluation + var evalData = prepareEvaluationData(solution); + + Move bestMove = null; + double bestDelta = 0; + + for (Move move : getNeighborhood(solution)) { + // Use pre-calculated data for O(1) evaluation + double delta = move.fastEvaluate(solution, evalData); + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null) { + bestMove.apply(solution); + improved = true; + } + } + + return solution; + } +} +``` + +## References + +[1] Aarts, E., & Lenstra, J. K. (Eds.). (2003). *Local search in combinatorial optimization*. Princeton University Press. + +[2] Johnson, D. S., & McGeoch, L. A. (1997). The traveling salesman problem: A case study in local optimization. *Local Search in Combinatorial Optimization*, 215-310. + +[3] Papadimitriou, C. H., & Steiglitz, K. (1998). *Combinatorial optimization: algorithms and complexity*. Courier Corporation. diff --git a/docs/concepts/algorithm-components/improvers/first-improvement.md b/docs/concepts/algorithm-components/improvers/first-improvement.md new file mode 100644 index 000000000..4d8bde3ff --- /dev/null +++ b/docs/concepts/algorithm-components/improvers/first-improvement.md @@ -0,0 +1,436 @@ +# First Improvement Local Search + +First Improvement (also known as Next Descent) is a local search strategy that applies the first improving move found, without evaluating the entire neighborhood. + +## Overview + +First Improvement stops searching as soon as an improving move is found and immediately applies it. + +```mermaid +graph TD + A[Current Solution] --> B[Start Exploring Neighborhood] + B --> C{Found Improving Move?} + C -->|Yes| D[Apply Move Immediately] + D --> A + C -->|No more moves| E[Local Optimum] +``` + +## Algorithm Outline + +``` +FirstImprovementLS(solution): + improved = true + + while (improved) { + improved = false + + for (move in neighborhood(solution)) { + delta = evaluate(move, solution) + + if (delta < 0) { // For minimization + apply(move, solution) + improved = true + break // Stop searching, restart from beginning + } + } + } + + return solution +``` + +## Key Characteristics + +| Aspect | First Improvement | +|--------|-------------------| +| **Exploration** | Partial neighborhood per iteration | +| **Move Selection** | First improving move | +| **Evaluations** | O(k×m) where k=iterations, m=avg moves to improvement | +| **Speed** | Faster per iteration | +| **Quality** | Potentially weaker local optima | +| **Iterations** | More iterations to convergence | + +## How to Use + +### Basic Usage + +```java +// Create first improvement local search +var firstLS = new LocalSearchFirstImprovement() { + @Override + protected Iterable getNeighborhood(MySolution solution) { + return generateMoves(solution); + } +}; + +// Apply to solution +solution = firstLS.improve(solution); +``` + +### Custom Implementation + +```java +public class MyFirstImprovementLS, I extends Instance> + extends LocalSearchFirstImprovement { + + public MyFirstImprovementLS() { + super("MyFirstLS"); + } + + @Override + protected Iterable getNeighborhood(S solution) { + // Generate moves lazily for efficiency + return () -> new MoveIterator(solution); + } + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + // Stop at first improvement + for (Move move : getNeighborhood(solution)) { + if (move.improves(solution)) { + move.apply(solution); + improved = true; + break; // Key: break immediately + } + } + } + + return solution; + } +} +``` + +### With Randomized Order + +```java +public class RandomFirstImprovement, I extends Instance> + extends LocalSearchFirstImprovement { + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + // Generate moves in random order + var moves = new ArrayList<>(getNeighborhood(solution)); + Collections.shuffle(moves); + + improved = false; + for (Move move : moves) { + if (move.improves(solution)) { + move.apply(solution); + improved = true; + break; + } + } + } + + return solution; + } +} +``` + +## Comparison with Best Improvement + +| Aspect | First Improvement | Best Improvement | +|--------|------------------|------------------| +| **Evaluations per iteration** | Until first improvement | All moves | +| **Time per iteration** | Shorter | Longer | +| **Iterations to convergence** | More | Fewer | +| **Total time** | Often faster | Often slower | +| **Local optimum quality** | Often worse | Often better | +| **Memory usage** | Lower | Higher (if caching) | +| **Recommended for** | Large neighborhoods | Small neighborhoods | + +## When to Use + +### Use First Improvement When: + +✅ **Large neighborhood**: O(n³) or more moves +✅ **Cheap evaluations**: Move evaluation is fast +✅ **Time-critical**: Need results quickly +✅ **Many improvements**: Dense neighborhoods with many improving moves + +### Use Best Improvement When: + +❌ **Small neighborhood**: O(n) or O(n²) moves +❌ **Expensive evaluations**: Move evaluation is costly +❌ **Quality-critical**: Need best local optimum +❌ **Few improvements**: Sparse neighborhoods + +## Implementation Techniques + +### Lazy Move Generation + +```java +// Efficient: generate moves one at a time +@Override +protected Iterable getNeighborhood(S solution) { + return () -> new Iterator() { + private int i = 0; + private int j = 1; + + @Override + public boolean hasNext() { + return i < solution.size() - 1; + } + + @Override + public Move next() { + Move move = new SwapMove(i, j); + j++; + if (j >= solution.size()) { + i++; + j = i + 1; + } + return move; + } + }; +} +``` + +### Early Feasibility Check + +```java +@Override +public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + for (Move move : getNeighborhood(solution)) { + // Check feasibility before expensive evaluation + if (!move.isFeasible(solution)) { + continue; + } + + // Only evaluate if feasible + if (move.improves(solution)) { + move.apply(solution); + improved = true; + break; + } + } + } + + return solution; +} +``` + +### Neighborhood Ordering + +```java +public class OrderedFirstImprovement, I extends Instance> + extends LocalSearchFirstImprovement { + + @Override + protected Iterable getNeighborhood(S solution) { + var moves = generateAllMoves(solution); + + // Order moves by likelihood of improvement + // E.g., based on historical data or problem-specific heuristics + moves.sort(Comparator.comparingDouble(this::estimateImprovement).reversed()); + + return moves; + } + + private double estimateImprovement(Move move) { + // Quick estimate without full evaluation + return move.estimatedDelta(); + } +} +``` + +## Advanced Variants + +### Cycling First Improvement + +```java +public class CyclingFirstImprovement, I extends Instance> + extends LocalSearchFirstImprovement { + + private int lastMoveIndex = 0; + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + var moves = new ArrayList<>(getNeighborhood(solution)); + improved = false; + + // Start from where we left off last time + for (int k = 0; k < moves.size(); k++) { + int idx = (lastMoveIndex + k) % moves.size(); + Move move = moves.get(idx); + + if (move.improves(solution)) { + move.apply(solution); + lastMoveIndex = idx + 1; + improved = true; + break; + } + } + } + + return solution; + } +} +``` + +### Adaptive First Improvement + +```java +public class AdaptiveFirstImprovement, I extends Instance> + extends LocalSearchFirstImprovement { + + private int consecutiveNoImprovements = 0; + private final int switchThreshold = 5; + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + var moves = getNeighborhood(solution); + + // If stuck, switch to randomized order + if (consecutiveNoImprovements > switchThreshold) { + var moveList = new ArrayList<>(moves); + Collections.shuffle(moveList); + moves = moveList; + } + + for (Move move : moves) { + if (move.improves(solution)) { + move.apply(solution); + improved = true; + consecutiveNoImprovements = 0; + break; + } + } + + if (!improved) { + consecutiveNoImprovements++; + } + } + + return solution; + } +} +``` + +## Related Java Classes + +- **[`LocalSearchFirstImprovement`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchFirstImprovement.html)**: First improvement implementation +- **[`LocalSearch`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearch.html)**: Base local search class +- **[`BestImprovementLS`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchBestImprovement.html)**: Alternative strategy +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base improver class + +## Example Use Cases + +### TSP 2-Opt First Improvement + +```java +public class TSPFirstImprovement2Opt + extends LocalSearchFirstImprovement { + + @Override + protected Iterable getNeighborhood(TSPSolution solution) { + // Lazy iterator - generates moves one at a time + return () -> new TwoOptIterator(solution); + } +} + +class TwoOptIterator implements Iterator { + private final TSPSolution solution; + private int i = 0; + private int j = 2; + + TwoOptIterator(TSPSolution solution) { + this.solution = solution; + } + + @Override + public boolean hasNext() { + return i < solution.size() - 1; + } + + @Override + public TwoOptMove next() { + TwoOptMove move = new TwoOptMove(i, j); + j++; + if (j >= solution.size()) { + i++; + j = i + 2; + } + return move; + } +} +``` + +### VRP First Improvement + +```java +public class VRPFirstImprovement + extends LocalSearchFirstImprovement { + + @Override + protected Iterable getNeighborhood(VRPSolution solution) { + return generateVRPMoves(solution); + } + + private Iterable generateVRPMoves(VRPSolution solution) { + return () -> new Iterator() { + // Try intra-route moves first (cheaper) + Iterator intraRoute = new IntraRouteMoveIterator(solution); + // Then inter-route moves (more expensive) + Iterator interRoute = new InterRouteMoveIterator(solution); + + @Override + public boolean hasNext() { + return intraRoute.hasNext() || interRoute.hasNext(); + } + + @Override + public Move next() { + return intraRoute.hasNext() ? intraRoute.next() : interRoute.next(); + } + }; + } +} +``` + +## Best Practices + +1. **Lazy generation**: Generate moves on-demand, don't pre-compute all +2. **Order matters**: Explore more promising moves first +3. **Randomization**: Shuffle move order to avoid bias +4. **Feasibility first**: Check feasibility before expensive evaluation +5. **Monitor progress**: Track improvements to detect when stuck +6. **Combine with diversification**: Use shake/perturbation when stuck + +## Performance Considerations + +**Memory**: Lower than best improvement (no need to store all moves) + +**CPU**: Often faster total time despite more iterations + +**Scalability**: Scales better to large neighborhoods + +**Predictability**: More variation in runtime between runs + +## References + +[1] Aarts, E., & Lenstra, J. K. (Eds.). (2003). *Local search in combinatorial optimization*. Princeton University Press. + +[2] Hoos, H. H., & Stützle, T. (2004). *Stochastic local search: Foundations and applications*. Elsevier. + +[3] Johnson, D. S., & McGeoch, L. A. (1997). The traveling salesman problem: A case study in local optimization. *Local Search in Combinatorial Optimization*, 215-310. diff --git a/docs/concepts/algorithm-components/improvers/improver.md b/docs/concepts/algorithm-components/improvers/improver.md new file mode 100644 index 000000000..d53fdbb00 --- /dev/null +++ b/docs/concepts/algorithm-components/improvers/improver.md @@ -0,0 +1,328 @@ +# 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 implement local optimization strategies. They explore the neighborhood of a solution, looking for improvements, and return a solution that is at least as good as the input. + +```mermaid +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 +``` + +## Base Improver Interface + +```java +public abstract class Improver, I extends Instance> + extends AlgorithmComponent { + + /** + * Improve the given solution + * @param solution Solution to improve + * @return Improved solution (or original if no improvement found) + * @ensures returnedSolution.getScore() <= solution.getScore() + */ + public abstract S improve(S solution); +} +``` + +## Key Principle: Non-Worsening + +**Critical**: Improvers MUST NOT return worse solutions: + +```java +@Override +public S improve(S solution) { + S improved = tryToImprove(solution); + + // Framework validates in development mode + assert !improved.isWorseThan(solution) : "Improver returned worse solution!"; + + return improved; +} +``` + +## Common Improver Types + +| Improver Type | Description | Documentation | +|---------------|-------------|---------------| +| **Local Search** | Explores solution neighborhoods | [Local Search](local-search.md) | +| **Best Improvement** | Always selects best move | [Best Improvement](best-improvement.md) | +| **First Improvement** | Accepts first improvement found | [First Improvement](first-improvement.md) | +| **VND** | Systematic multi-neighborhood search | [VND](../metaheuristics/vnd.md) | +| **Simulated Annealing** | Probabilistic acceptance (used as improver) | [SA](../metaheuristics/simulated-annealing.md) | + +## How to Use + +### Standalone + +```java +var improver = new MyLocalSearch(); +var solution = constructor.construct(instance); +solution = improver.improve(solution); // solution is now at local optimum +``` + +### In Multi-Start Algorithm + +```java +var multiStart = new MultiStartAlgorithm<>( + "GRASP", + constructor, + improver, // Applied to each constructed solution + 100 +); +``` + +### Chain Multiple Improvers + +```java +// Sequential application of multiple improvers +var chainedImprover = new SequentialImprover<>( + new FastLocalSearch<>(), + new SlowButThoroughSearch<>() +); + +// Or manually chain +solution = improver1.improve(solution); +solution = improver2.improve(solution); +solution = improver3.improve(solution); +``` + +## Implementation Guidelines + +### Basic Pattern + +```java +public class MyImprover, I extends Instance> + extends Improver { + + public MyImprover() { + super("MyImprover"); + } + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + + // Try to find an improving move + for (Move move : generateMoves(solution)) { + double delta = move.evaluateDelta(solution); + + if (delta < 0) { // Improvement found (minimization) + move.apply(solution); + improved = true; + break; // First improvement + } + } + } + + return solution; + } +} +``` + +### With Move Management + +```java +public abstract class MoveBasedImprover, I extends Instance> + extends Improver { + + @Override + public S improve(S solution) { + while (!isLocalOptimum(solution) && !TimeControl.isTimeUp()) { + Move bestMove = selectMove(solution); + if (bestMove != null && bestMove.improves()) { + bestMove.apply(solution); + } else { + break; // Local optimum reached + } + } + return solution; + } + + protected abstract Move selectMove(S solution); + protected abstract boolean isLocalOptimum(S solution); +} +``` + +### Efficient Implementation + +```java +public class EfficientImprover, I extends Instance> + extends Improver { + + private MoveCache moveCache; + + @Override + public S improve(S solution) { + // Initialize data structures once + moveCache = new MoveCache<>(solution); + + while (!moveCache.isEmpty() && !TimeControl.isTimeUp()) { + Move move = moveCache.getBestMove(); + + if (move.getDelta() < 0) { + move.apply(solution); + // Incrementally update only affected moves + moveCache.updateAffectedMoves(move, solution); + } else { + break; // No improving moves + } + } + + return solution; + } +} +``` + +## Advanced Features + +### Null Improver + +For algorithms that don't need improvement: + +```java +var algorithm = new MultiStartAlgorithm<>( + "PureConstruction", + constructor, + new NullImprover<>(), // No improvement phase + 100 +); +``` + +### Conditional Improver + +Apply improvement conditionally: + +```java +public class ConditionalImprover, I extends Instance> + extends Improver { + + private final Improver wrapped; + private final Predicate condition; + + @Override + public S improve(S solution) { + if (condition.test(solution)) { + return wrapped.improve(solution); + } + return solution; // Skip improvement + } +} + +// Usage: only improve every 10th solution +var conditional = new ConditionalImprover<>( + localSearch, + solution -> iteration % 10 == 0 +); +``` + +### Time-Limited Improver + +```java +public class TimeLimitedImprover, I extends Instance> + extends Improver { + + private final Improver wrapped; + private final long maxMillis; + + @Override + public S improve(S solution) { + long start = System.currentTimeMillis(); + S current = solution; + + while (System.currentTimeMillis() - start < maxMillis) { + S improved = wrapped.improve(current); + if (improved.isBetterThan(current)) { + current = improved; + } else { + break; // Local optimum reached + } + } + + return current; + } +} +``` + +## Related Java Classes + +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base class +- **[`LocalSearch`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearch.html)**: Local search base +- **[`VND`](../../../../apidocs/es/urjc/etsii/grafo/improve/VND.html)**: Variable neighborhood descent +- **[`SequentialImprover`](../../../../apidocs/es/urjc/etsii/grafo/improve/SequentialImprover.html)**: Chain improvers +- **[`NullImprover`](../../../../apidocs/es/urjc/etsii/grafo/improve/NullImprover.html)**: No-op improver + +## Best Practices + +1. **Guarantee non-worsening**: Always check `returnedSolution.getScore() <= inputSolution.getScore()` +2. **Check time limits**: Use `TimeControl.isTimeUp()` in loops +3. **Efficient neighborhoods**: Use incremental evaluation +4. **Track improvements**: Log or metric when improvements occur +5. **Local optimum detection**: Stop when no improving move exists +6. **In-place vs cloning**: Document whether improver modifies input solution + +## Common Patterns + +### First Improvement Pattern + +```java +while (improved) { + improved = false; + for (Move move : neighborhood) { + if (move.improves()) { + move.apply(solution); + improved = true; + break; // Accept first improvement + } + } +} +``` + +### Best Improvement Pattern + +```java +while (true) { + Move bestMove = null; + double bestDelta = 0; + + for (Move move : neighborhood) { + double delta = move.evaluate(); + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null) { + bestMove.apply(solution); + } else { + break; // No improving move found + } +} +``` + +## Performance Considerations + +- **Move evaluation**: Most expensive operation, optimize carefully +- **Neighborhood size**: Balance completeness vs speed +- **Caching**: Cache move evaluations when possible +- **Incremental updates**: Update data structures incrementally +- **Early termination**: Stop when time is up or quality threshold reached + +## References + +[1] Aarts, E., & Lenstra, J. K. (Eds.). (2003). *Local search in combinatorial optimization*. Princeton University Press. + +[2] Hoos, H. H., & Stützle, T. (2004). *Stochastic local search: Foundations and applications*. Elsevier. + +[3] Gendreau, M., & Potvin, J. Y. (Eds.). (2010). *Handbook of metaheuristics* (Vol. 2). Springer. diff --git a/docs/concepts/algorithm-components/improvers/local-search.md b/docs/concepts/algorithm-components/improvers/local-search.md new file mode 100644 index 000000000..95f79ab97 --- /dev/null +++ b/docs/concepts/algorithm-components/improvers/local-search.md @@ -0,0 +1,392 @@ +# Local Search + +Local Search is a fundamental improvement method that iteratively explores the neighborhood of a solution, applying moves that improve the objective function until a local optimum is reached. + +## Overview + +Local Search defines a **neighborhood structure** (set of possible moves) and systematically explores it, accepting moves that improve the solution. + +```mermaid +graph TD + A[Current Solution] --> B[Generate Neighborhood] + B --> C[Evaluate Moves] + C --> D{Improving Move?} + D -->|Yes| E[Apply Best/First Move] + E --> A + D -->|No| F[Local Optimum Reached] +``` + +## Key Concepts + +### Neighborhood + +A **neighborhood** N(s) of a solution s is the set of solutions reachable by applying a single move: + +``` +N(s) = {s' : s' can be obtained from s by applying one move} +``` + +### Local Optimum + +A solution s* is a **local optimum** if: + +``` +∀s' ∈ N(s*): f(s*) ≤ f(s') (for minimization) +``` + +### Move + +A **move** is an operation that transforms one solution into another: +- **Swap**: Exchange two elements +- **Insert**: Move an element to a different position +- **2-opt**: Reverse a subsequence (TSP) +- **k-opt**: More complex rearrangements + +## Algorithm Outline + +``` +LocalSearch(solution): + while (true) { + neighborhood = generateNeighborhood(solution) + bestMove = null + + for (move in neighborhood) { + if (move.improves(solution)) { + if (bestMove == null || move.isBetterThan(bestMove)) { + bestMove = move + if (FIRST_IMPROVEMENT) break // Accept immediately + } + } + } + + if (bestMove != null) { + apply(bestMove, solution) + } else { + break // Local optimum + } + } + + return solution +``` + +## How to Use + +### Basic Implementation + +```java +public abstract class LocalSearch, I extends Instance> + extends Improver { + + protected final boolean firstImprovement; + + public LocalSearch(String name, boolean firstImprovement) { + super(name); + this.firstImprovement = firstImprovement; + } + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + improved = false; + var moves = generateMoves(solution); + + if (firstImprovement) { + // First improvement: accept first improving move + for (Move move : moves) { + if (move.improves(solution)) { + move.apply(solution); + improved = true; + break; + } + } + } else { + // Best improvement: find best move + Move bestMove = null; + double bestDelta = 0; + + for (Move move : moves) { + double delta = move.evaluate(solution); + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null) { + bestMove.apply(solution); + improved = true; + } + } + } + + return solution; + } + + protected abstract Iterable generateMoves(S solution); +} +``` + +### Concrete Example: TSP 2-Opt + +```java +public class TwoOptLS extends LocalSearch { + + public TwoOptLS(boolean firstImprovement) { + super("2-Opt", firstImprovement); + } + + @Override + protected Iterable generateMoves(TSPSolution solution) { + List moves = new ArrayList<>(); + int n = solution.size(); + + for (int i = 0; i < n - 1; i++) { + for (int j = i + 2; j < n; j++) { + moves.add(new TwoOptMove(i, j)); + } + } + + return moves; + } +} + +class TwoOptMove implements Move { + private final int i, j; + + @Override + public double evaluate(TSPSolution solution) { + // Calculate change in tour length if we reverse segment [i+1, j] + var instance = solution.getInstance(); + int before_i = solution.get(i); + int after_i = solution.get(i + 1); + int before_j = solution.get(j); + int after_j = solution.get((j + 1) % solution.size()); + + double currentCost = instance.distance(before_i, after_i) + + instance.distance(before_j, after_j); + double newCost = instance.distance(before_i, before_j) + + instance.distance(after_i, after_j); + + return newCost - currentCost; + } + + @Override + public void apply(TSPSolution solution) { + solution.reverse(i + 1, j); + } +} +``` + +## Variants + +### 1. Best Improvement + +See [Best Improvement Local Search](best-improvement.md) - explores entire neighborhood, applies best move. + +### 2. First Improvement + +See [First Improvement Local Search](first-improvement.md) - applies first improving move found. + +### 3. Random Descent + +```java +public class RandomDescent, I extends Instance> + extends LocalSearch { + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved && !TimeControl.isTimeUp()) { + var moves = new ArrayList<>(generateMoves(solution)); + Collections.shuffle(moves); // Random order + + improved = false; + for (Move move : moves) { + if (move.improves(solution)) { + move.apply(solution); + improved = true; + break; + } + } + } + + return solution; + } +} +``` + +## Implementation Strategies + +### Efficient Move Evaluation + +```java +// Bad: Full re-evaluation +double evaluate(Move move, Solution solution) { + Solution copy = solution.clone(); + move.apply(copy); + return copy.getScore() - solution.getScore(); +} + +// Good: Incremental evaluation +double evaluate(Move move, Solution solution) { + return move.evaluateDelta(solution); // Calculate change only +} +``` + +### Move Caching + +```java +public class CachedLocalSearch, I extends Instance> + extends LocalSearch { + + private List cachedMoves; + private Map cachedDeltas; + + @Override + public S improve(S solution) { + // Generate and cache all moves once + cachedMoves = new ArrayList<>(generateMoves(solution)); + cachedDeltas = new HashMap<>(); + + boolean improved = true; + while (improved && !TimeControl.isTimeUp()) { + improved = false; + Move bestMove = null; + double bestDelta = 0; + + for (Move move : cachedMoves) { + // Use cached delta if valid + double delta = cachedDeltas.computeIfAbsent(move, + m -> m.evaluate(solution)); + + if (delta < bestDelta) { + bestDelta = delta; + bestMove = move; + } + } + + if (bestMove != null) { + bestMove.apply(solution); + // Invalidate affected deltas + invalidateAffectedDeltas(bestMove); + improved = true; + } + } + + return solution; + } +} +``` + +## Neighborhood Design + +### Small Neighborhoods + +Fast but may get stuck: +- **Swap**: O(n²) moves +- **Insert**: O(n²) moves + +### Large Neighborhoods + +Slower but better quality: +- **k-opt (k>2)**: Exponential in k +- **Block moves**: O(n³) or more +- **Variable size**: Adaptive neighborhood size + +### Multiple Neighborhoods + +Use VND to systematically explore multiple neighborhoods: + +```java +var vnd = new VND<>( + "Multi-Neighborhood", + List.of( + new SwapLS<>(), + new InsertLS<>(), + new TwoOptLS<>() + ) +); +``` + +## Related Java Classes + +- **[`LocalSearch`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearch.html)**: Base local search class +- **[`BestImprovementLS`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchBestImprovement.html)**: Best improvement variant +- **[`FirstImprovementLS`](../../../../apidocs/es/urjc/etsii/grafo/improve/ls/LocalSearchFirstImprovement.html)**: First improvement variant +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base improver class +- **[`VND`](../../../../apidocs/es/urjc/etsii/grafo/improve/VND.html)**: Variable neighborhood descent + +## Example Use Cases + +### TSP with Multiple Neighborhoods + +```java +// 2-opt +public class TwoOptLS extends LocalSearch { /* ... */ } + +// Or-opt +public class OrOptLS extends LocalSearch { /* ... */ } + +// 3-opt +public class ThreeOptLS extends LocalSearch { /* ... */ } + +// Combine with VND +var vnd = new VND<>("TSP-VND", List.of( + new TwoOptLS(true), // Fast 2-opt + new OrOptLS(true), // Medium Or-opt + new ThreeOptLS(false) // Slower 3-opt (best improvement) +)); +``` + +### VRP Intra-Route and Inter-Route + +```java +// Within same route +public class IntraRouteSwap extends LocalSearch { /* ... */ } + +// Between different routes +public class InterRouteSwap extends LocalSearch { /* ... */ } + +// Relocate customer +public class RelocateLS extends LocalSearch { /* ... */ } +``` + +## Best Practices + +1. **Incremental evaluation**: Never clone solutions to evaluate moves +2. **Early termination**: Check `TimeControl.isTimeUp()` in loops +3. **Choose appropriate variant**: First improvement for speed, best for quality +4. **Multiple neighborhoods**: Use VND for better local optima +5. **Problem-specific moves**: Design moves that exploit problem structure +6. **Efficient data structures**: Use appropriate structures for move generation + +## Performance Tips + +- **Lazy evaluation**: Only evaluate moves that pass cheap feasibility checks +- **Move ordering**: Evaluate more promising moves first +- **Parallel evaluation**: Evaluate independent moves in parallel +- **Aspiration criteria**: Sometimes accept slightly worsening moves to escape plateaus + +## When to Use + +**Local Search is ideal for:** +- Finding good solutions quickly +- Improving constructed solutions +- As part of metaheuristics (GRASP, ILS, VNS) +- Problems with well-defined neighborhoods + +**Consider alternatives when:** +- Solutions have many local optima (use metaheuristics) +- Evaluation is very expensive (use sampling-based methods) +- Need global optimum guarantee (use exact methods) + +## References + +[1] Aarts, E., & Lenstra, J. K. (Eds.). (2003). *Local search in combinatorial optimization*. Princeton University Press. + +[2] Hoos, H. H., & Stützle, T. (2004). *Stochastic local search: Foundations and applications*. Elsevier. + +[3] Johnson, D. S., & McGeoch, L. A. (1997). The traveling salesman problem: A case study in local optimization. *Local Search in Combinatorial Optimization*, 215-310. diff --git a/docs/concepts/algorithm-components/metaheuristics/.pages b/docs/concepts/algorithm-components/metaheuristics/.pages new file mode 100644 index 000000000..5dfdbcf0c --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/.pages @@ -0,0 +1,8 @@ +title: Metaheuristics +nav: + - vns.md + - simulated-annealing.md + - iterated-greedy.md + - scatter-search.md + - multi-start.md + - vnd.md diff --git a/docs/concepts/algorithm-components/metaheuristics/iterated-greedy.md b/docs/concepts/algorithm-components/metaheuristics/iterated-greedy.md new file mode 100644 index 000000000..2055f33b2 --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/iterated-greedy.md @@ -0,0 +1,230 @@ +# Iterated Greedy (IG) + +Iterated Greedy is a simple yet powerful metaheuristic that iteratively destructs parts of a solution and reconstructs them, keeping the better solution at each iteration. It's particularly effective for sequencing and scheduling problems. + +## Algorithm Overview + +IG operates in cycles, alternating between destruction and reconstruction phases, optionally followed by local search improvement. + +```mermaid +graph TD + A[Generate Initial Solution] --> B[Apply Local Search] + B --> C[Destruct: Remove Elements] + C --> D[Reconstruct: Add Elements Back] + D --> E{Acceptance Criterion} + E -->|Accept| F[Update Current Solution] + E -->|Reject| G[Keep Current Solution] + F --> H{Stopping Criteria?} + G --> H + H -->|No| I[Optional: Local Search] + I --> C + H -->|Yes| J[Return Best Solution] +``` + +## Algorithm Outline + +``` +s = Construct() +s = LocalSearch(s) +best = s + +while (not StoppingCriteria()) { + s' = Destruct(s, d) // Remove d elements + s' = Reconstruct(s') // Add elements back + s' = LocalSearch(s') // Optional improvement + + if (AcceptanceCriterion(s, s')) { + s = s' + if (s.isBetterThan(best)) { + best = s + } + } +} +return best +``` + +## Key Components + +### Destruction Phase + +The destruction operator removes `d` elements from the solution: + +- **Random Destruction**: Remove random elements +- **Worst Destruction**: Remove elements contributing most to objective +- **Related Destruction**: Remove related/similar elements + +### Reconstruction Phase + +The reconstruction operator adds removed elements back: + +- **Greedy Reconstruction**: Insert at best position +- **Random Reconstruction**: Insert at random positions +- **GRASP Reconstruction**: Randomized greedy insertion + +### Acceptance Criterion + +Determines whether to accept the new solution: + +- **Better Only**: Accept only if improvement +- **Simulated Annealing**: Probabilistic acceptance +- **Random Walk**: Always accept + +## How to Use + +### Basic Example + +```java +// Define destruction operator +Destructive destructor = + new RandomDestructor<>(4); // Remove 4 elements + +// Define reconstruction operator +Reconstructive reconstructor = + new GreedyReconstructor<>(); + +// Optional: local search +Improver improver = + new MyLocalSearch(); + +// Build Iterated Greedy +var ig = new IteratedGreedy<>( + "IG", + constructor, // Initial construction + destructor, // Destruction phase + reconstructor, // Reconstruction phase + improver // Optional improvement +); +``` + +### With Acceptance Criterion + +```java +// Simple: accept only improvements +AcceptanceCriterion acceptance = + (current, candidate) -> candidate.isBetterThan(current); + +// Or: SA-like acceptance +AcceptanceCriterion saAcceptance = + (current, candidate) -> { + if (candidate.isBetterThan(current)) return true; + double delta = candidate.getScore() - current.getScore(); + double temp = 100.0; // Could vary over time + return Math.random() < Math.exp(-delta / temp); + }; + +var ig = new IteratedGreedy<>( + "IG-SA", + constructor, + destructor, + reconstructor, + improver, + saAcceptance +); +``` + +### Adaptive Destruction Size + +```java +// Vary destruction size over iterations +var adaptiveDestructor = new AdaptiveDestructor() { + private int iteration = 0; + + @Override + public MySolution destruct(MySolution solution) { + iteration++; + // Start with small destruction, increase over time + int d = Math.min(2 + iteration / 100, 10); + return removeElements(solution, d); + } +}; +``` + +## Implementation Notes + +### Choosing Destruction Size + +The parameter `d` (destruction size) is critical: + +- **Small `d` (2-5)**: Intensification, small changes +- **Medium `d` (5-10)**: Balanced exploration +- **Large `d` (>10)**: Diversification, larger changes + +### Performance Considerations + +```java +// Efficient: reuse partial solutions +var cachedReconstructor = new CachedReconstructor<>(baseReconstructor); + +// Consider not always using local search +var conditionalLS = new ConditionalImprover<>( + localSearch, + (solution) -> iteration % 10 == 0 // LS every 10 iterations +); +``` + +### Common Variants + +**Iterated Local Search (ILS)**: Special case where destruction is very limited (d=1 or 2) + +**Iterated Greedy with Random Restarts**: Periodically restart from a new random solution + +## Related Java Classes + +- **[`IteratedGreedy`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/IteratedGreedy.html)**: Main IG implementation +- **[`Destructive`](../../../../apidocs/es/urjc/etsii/grafo/shake/Destructive.html)**: Interface for destruction operators +- **[`Reconstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Reconstructive.html)**: Base class for reconstruction +- **[`DestroyRebuild`](../../../../apidocs/es/urjc/etsii/grafo/shake/DestroyRebuild.html)**: Combined destroy-rebuild operator + +## Example Use Cases + +### Flow Shop Scheduling + +```java +// Remove jobs from schedule +var destructor = new RemoveJobsDestructor<>(5); + +// Reinsert using NEH-like heuristic +var reconstructor = new NEHReconstructor<>(); + +var ig = new IteratedGreedy<>( + "FlowShop-IG", + nehConstructor, + destructor, + reconstructor, + new FirstImprovementLS<>() +); +``` + +### Permutation-based Problems + +```java +// Remove random positions +var destructor = new RandomPositionDestructor<>(3); + +// Insert at best positions +var reconstructor = new BestPositionReconstructor<>(); + +var ig = new IteratedGreedy<>( + "Permutation-IG", + randomConstructor, + destructor, + reconstructor, + null // No local search +); +``` + +## Best Practices + +1. **Tune `d` parameter**: Critical for performance +2. **Balance phases**: Too much destruction → random search; too little → local search +3. **Local search**: Not always necessary, can be expensive +4. **Track best**: Always keep the best solution found +5. **Time control**: Check `TimeControl.isTimeUp()` in the loop + +## References + +[1] Ruiz, R., & Stützle, T. (2007). A simple and effective iterated greedy algorithm for the permutation flowshop scheduling problem. *European Journal of Operational Research*, 177(3), 2033-2049. + +[2] Jacobs, L. W., & Brusco, M. J. (1995). Note: A local-search heuristic for large set-covering problems. *Naval Research Logistics*, 42(7), 1129-1140. + +[3] Stützle, T. (1998). Applying iterated local search to the permutation flow shop problem. *Technical Report AIDA-98-04*, Darmstadt University of Technology. diff --git a/docs/concepts/algorithm-components/metaheuristics/multi-start.md b/docs/concepts/algorithm-components/metaheuristics/multi-start.md new file mode 100644 index 000000000..36d2ec65d --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/multi-start.md @@ -0,0 +1,318 @@ +# Multi-Start Algorithm + +Multi-Start is a simple yet effective metaheuristic strategy that repeatedly generates and improves solutions, keeping track of the best solution found. It's one of the easiest metaheuristics to implement and often serves as a baseline for comparison. + +## Algorithm Overview + +Multi-Start alternates between construction and improvement phases, typically using different random seeds or initial configurations each time. + +```mermaid +graph TD + A[Start] --> B[Construct Initial Solution] + B --> C[Improve Solution] + C --> D{Better than Best?} + D -->|Yes| E[Update Best Solution] + D -->|No| F[Discard Solution] + E --> G{Stopping Criteria?} + F --> G + G -->|No| B + G -->|Yes| H[Return Best Solution] +``` + +## Algorithm Outline + +``` +best = null + +while (not StoppingCriteria()) { + s = Construct() + s = Improve(s) + + if (best == null || s.isBetterThan(best)) { + best = s + } +} + +return best +``` + +## Key Components + +### Construction Phase + +Generates initial solutions using various strategies: + +- **Random**: Completely random solutions +- **Greedy**: Deterministic greedy construction +- **GRASP**: Randomized greedy with alpha parameter +- **Mixed**: Alternate between different constructive methods + +### Improvement Phase + +Improves each constructed solution: + +- **Local Search**: Best/first improvement +- **VND**: Variable neighborhood descent +- **Simulated Annealing**: For more exploration +- **None**: Pure multi-start without improvement + +### Stopping Criteria + +Determines when to stop: + +- **Time limit**: Stop after time expires +- **Iteration limit**: Stop after N iterations +- **No improvement**: Stop after K iterations without improvement +- **Target reached**: Stop when objective threshold is met + +## How to Use + +### Basic Example + +```java +// Constructor +Constructive constructor = + new MyGRASPConstructive(0.3); // alpha = 0.3 + +// Improver +Improver improver = + new MyLocalSearch(); + +// Build Multi-Start +var multiStart = new MultiStartAlgorithm<>( + "MultiStart-GRASP", + constructor, + improver, + 100 // iterations +); +``` + +### With Time Limit + +```java +// Use TimeControl for stopping +var multiStart = new MultiStartAlgorithm( + "MultiStart-Timed", + constructor, + improver +) { + @Override + protected boolean shouldContinue(int iteration) { + return !TimeControl.isTimeUp() && iteration < 1000; + } +}; +``` + +### Without Improvement Phase + +```java +// Pure multi-start: just construct multiple solutions +var pureMultiStart = new MultiStartAlgorithm<>( + "PureMultiStart", + constructor, + null, // No improvement + 500 +); +``` + +### With Alternating Constructors + +```java +// Cycle through different constructive methods +List> constructors = List.of( + new RandomConstructive<>(), + new GreedyConstructive<>(), + new GRASPConstructive<>(0.2), + new GRASPConstructive<>(0.5) +); + +var multiStart = new MultiStartAlgorithm( + "MultiStart-Mixed" +) { + private int iteration = 0; + + @Override + protected MySolution construct(MyInstance instance) { + var constructor = constructors.get(iteration % constructors.size()); + iteration++; + return constructor.construct(newSolution(instance)); + } + + @Override + protected MySolution improve(MySolution solution) { + return localSearch.improve(solution); + } + + @Override + protected boolean shouldContinue(int iter) { + return iter < 200; + } +}; +``` + +## Implementation Notes + +### Tracking Progress + +```java +public class MonitoredMultiStart, I extends Instance> + extends MultiStartAlgorithm { + + private int improvementCount = 0; + private int iterationsSinceImprovement = 0; + + @Override + public S algorithm(I instance) { + S best = null; + int iteration = 0; + + while (shouldContinue(iteration)) { + S solution = construct(instance); + solution = improve(solution); + + if (best == null || solution.isBetterThan(best)) { + best = solution; + improvementCount++; + iterationsSinceImprovement = 0; + log.info("New best at iteration {}: {}", iteration, best.getScore()); + } else { + iterationsSinceImprovement++; + } + + iteration++; + } + + log.info("Total improvements: {}", improvementCount); + return best; + } +} +``` + +### Performance Considerations + +**Parallel Multi-Start**: Easy to parallelize since iterations are independent + +```java +// Note: Framework handles parallelization automatically +// Just configure in application.yml: +// executor: +// parallel: true +// threads: 4 +``` + +**Memory Efficiency**: Don't store all solutions, just the best + +**Early Stopping**: Stop if no improvement for K iterations + +```java +@Override +protected boolean shouldContinue(int iteration) { + return !TimeControl.isTimeUp() + && iteration < maxIterations + && iterationsSinceImprovement < patienceLimit; +} +``` + +## Variants + +### GRASP (Greedy Randomized Adaptive Search Procedure) + +GRASP is essentially Multi-Start with a specific type of randomized greedy constructor: + +```java +var grasp = new MultiStartAlgorithm<>( + "GRASP", + new GRASPConstructive<>(0.3), + new BestImprovementLS<>(), + 100 +); +``` + +### Iterated Local Search (ILS) + +ILS is Multi-Start but starts from a perturbed version of the current solution instead of from scratch. See [Iterated Greedy](iterated-greedy.md) for more details. + +### Random Restart + +Pure random construction without improvement: + +```java +var randomRestart = new MultiStartAlgorithm<>( + "RandomRestart", + new RandomConstructive<>(), + null, // No improvement + 10000 +); +``` + +## Related Java Classes + +- **[`MultiStartAlgorithm`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/multistart/MultiStartAlgorithm.html)**: Main multi-start implementation +- **[`Algorithm`](../../../../apidocs/es/urjc/etsii/grafo/algorithm/Algorithm.html)**: Base class for all algorithms +- **[`Constructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Constructive.html)**: Base class for constructive methods +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base class for improvement methods + +## Example Use Cases + +### TSP with GRASP + +```java +var tspMultiStart = new MultiStartAlgorithm<>( + "TSP-GRASP", + new NearestNeighborGRASP<>(0.25), // alpha = 0.25 + new TwoOptLS<>(), + 200 +); +``` + +### VRP with Random Initialization + +```java +var vrpMultiStart = new MultiStartAlgorithm<>( + "VRP-Random", + new RandomVRPConstructive<>(), + new SwapImprovementLS<>(), + 500 +); +``` + +### Knapsack Problem + +```java +var knapsackMultiStart = new MultiStartAlgorithm<>( + "Knapsack-Greedy", + new ValueDensityGRASP<>(0.3), + new FlipImprover<>(), + 1000 +); +``` + +## Best Practices + +1. **Choose appropriate constructor**: Balance between speed and quality +2. **Tune GRASP alpha**: If using GRASP, alpha ∈ [0.1, 0.5] typically works well +3. **Use time control**: Prefer time-based stopping over fixed iterations +4. **Log progress**: Track when improvements occur +5. **Compare with baselines**: Multi-Start is a good baseline for new algorithms +6. **Consider parallelization**: Multi-Start is embarrassingly parallel + +## When to Use Multi-Start + +**Good for:** +- Quick baseline implementation +- Problems where construction phase is fast +- Embarrassingly parallel execution +- Benchmarking other algorithms + +**Not ideal for:** +- Problems where construction is expensive +- When solutions need to evolve (use evolutionary algorithms instead) +- When local search is very expensive (use metaheuristics with fewer evaluations) + +## References + +[1] Martí, R., Resende, M. G., & Ribeiro, C. C. (2013). Multi-start methods for combinatorial optimization. *European Journal of Operational Research*, 226(1), 1-8. + +[2] Feo, T. A., & Resende, M. G. (1995). Greedy randomized adaptive search procedures. *Journal of Global Optimization*, 6(2), 109-133. + +[3] Lourenço, H. R., Martin, O. C., & Stützle, T. (2003). Iterated local search. In *Handbook of Metaheuristics* (pp. 320-353). Springer. diff --git a/docs/concepts/algorithm-components/metaheuristics/scatter-search.md b/docs/concepts/algorithm-components/metaheuristics/scatter-search.md new file mode 100644 index 000000000..5ea33003b --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/scatter-search.md @@ -0,0 +1,284 @@ +# Scatter Search + +Scatter Search is a population-based metaheuristic that operates on a set of diverse, high-quality solutions (reference set). It systematically combines solutions to create new candidate solutions and maintains solution diversity. + +## Algorithm Overview + +Scatter Search maintains a reference set of solutions and iteratively improves them through combination, improvement, and diversification. + +```mermaid +graph TD + A[Generate Initial Solutions] --> B[Create Reference Set] + B --> C[Select Solutions to Combine] + C --> D[Combination Method] + D --> E[Improvement Method] + E --> F{Solution Quality Check} + F -->|Good| G[Update Reference Set] + F -->|Not Good| H[Discard] + G --> I{Stopping Criteria?} + H --> I + I -->|No| J{Reference Set Changed?} + J -->|Yes| C + J -->|No| K[Diversification] + K --> C + I -->|Yes| L[Return Best Solution] +``` + +## Algorithm Outline + +``` +P = GenerateInitialPopulation(popSize) +RefSet = SelectDiverseElite(P, refSetSize) + +while (not StoppingCriteria()) { + NewSolutions = {} + + // Generate new solutions by combining reference solutions + for each pair (s1, s2) in RefSet { + s' = Combine(s1, s2) + s' = Improve(s') + NewSolutions.add(s') + } + + // Update reference set + RefSet = UpdateRefSet(RefSet, NewSolutions) + + // Diversification if needed + if (no improvement) { + RefSet = Diversify(RefSet) + } +} + +return best solution in RefSet +``` + +## Key Components + +### Reference Set + +The reference set contains the elite solutions used for combination: + +- **Size**: Typically 5-20 solutions +- **Quality**: Top solutions based on objective value +- **Diversity**: Solutions should be different from each other + +### Combination Method + +Creates new solutions by combining two or more reference solutions: + +- **Path Relinking**: Create intermediate solutions between two solutions +- **Crossover**: Exchange components between solutions +- **Voting**: Select components that appear in multiple solutions + +### Improvement Method + +Applied to newly generated solutions: + +- **Local Search**: Best/first improvement +- **Simulated Annealing**: For more exploration +- **VND**: Multiple neighborhood structures + +### Diversification + +Ensures the reference set doesn't become too homogeneous: + +- **Random Generation**: Add random solutions +- **Frequency-based**: Penalize frequently used components +- **Distance-based**: Generate solutions far from current set + +## How to Use + +### Basic Example + +```java +// Population generator +PopulationGenerator popGen = + new RandomPopulationGenerator<>(100); // 100 initial solutions + +// Combination method +CombinationMethod combiner = + new PathRelinkingCombiner<>(); + +// Improvement method +Improver improver = + new MyLocalSearch(); + +// Diversification method +DiversificationMethod diversifier = + new RandomDiversification<>(); + +// Build Scatter Search +var ss = new ScatterSearch<>( + "ScatterSearch", + popGen, + combiner, + improver, + diversifier, + 10, // refSetSize + 100 // maxIterations +); +``` + +### With Custom Reference Set Selection + +```java +// Custom selection: balance quality and diversity +ReferenceSetSelector selector = + new BalancedReferenceSetSelector<>( + 0.7, // 70% based on quality + 0.3 // 30% based on diversity + ); + +var ss = new ScatterSearch<>( + "CustomSS", + popGen, + combiner, + improver, + diversifier, + selector, + 10, + 100 +); +``` + +### Path Relinking Example + +```java +// Path relinking: generate intermediate solutions +PathRelinking pathRelinking = + new PathRelinking() { + @Override + public List relink(MySolution from, MySolution to) { + List path = new ArrayList<>(); + MySolution current = from.cloneSolution(); + + while (!current.equals(to)) { + // Apply move that makes current more similar to 'to' + Move move = findBestMoveTowards(current, to); + move.apply(current); + path.add(current.cloneSolution()); + } + + return path; + } + }; + +var ss = new ScatterSearch<>( + "SS-PathRelinking", + popGen, + pathRelinking, + improver, + diversifier, + 10, + 100 +); +``` + +## Implementation Notes + +### Reference Set Size + +- **Small (5-10)**: Faster, more focused search +- **Large (15-20)**: More diversity, slower +- **Typical**: 10 solutions + +### Population Initialization + +```java +// Mixed initialization for diversity +var mixedPopGen = new MixedPopulationGenerator<>( + List.of( + new GreedyConstructive<>(), + new RandomConstructive<>(), + new GRASPConstructive<>(0.3) + ), + 100 // total population size +); +``` + +### Measuring Diversity + +```java +// Distance-based diversity +DiversityMeasure diversity = (s1, s2) -> { + int differences = 0; + for (int i = 0; i < s1.size(); i++) { + if (s1.get(i) != s2.get(i)) { + differences++; + } + } + return differences; +}; +``` + +### Performance Tips + +- **Lazy Improvement**: Don't always apply full local search +- **Caching**: Store previously evaluated solutions +- **Parallel Evaluation**: Evaluate combinations in parallel +- **Early Stopping**: Stop if reference set hasn't changed in N iterations + +## Related Java Classes + +- **[`ScatterSearch`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/scattersearch/ScatterSearch.html)**: Main SS implementation +- **[`ReferenceSet`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/scattersearch/ReferenceSet.html)**: Container for reference solutions +- **[`CombinationMethod`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/scattersearch/CombinationMethod.html)**: Interface for solution combination +- **[`DiversificationMethod`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/scattersearch/DiversificationMethod.html)**: Interface for diversification + +## Example Use Cases + +### Graph Coloring + +```java +// Combine by voting on vertex colors +var combiner = new VotingCombiner() { + @Override + public GraphSolution combine(GraphSolution s1, GraphSolution s2) { + GraphSolution result = new GraphSolution(s1.getInstance()); + for (int vertex = 0; vertex < s1.getVertexCount(); vertex++) { + // Assign the more frequent color + int color1 = s1.getColor(vertex); + int color2 = s2.getColor(vertex); + result.setColor(vertex, mostFrequent(color1, color2)); + } + return result; + } +}; +``` + +### Scheduling Problems + +```java +// Combine job sequences +var combiner = new OrderCrossover(); + +// Reference set with good makespan solutions +var selector = new QualityBasedSelector<>(10); + +var ss = new ScatterSearch<>( + "Schedule-SS", + popGen, + combiner, + new VND<>(), + new RandomDiversification<>(), + selector, + 10, + 500 +); +``` + +## Best Practices + +1. **Balance quality and diversity** in reference set +2. **Efficient combination**: Avoid expensive operations +3. **Selective improvement**: Not every solution needs full LS +4. **Monitor convergence**: Track reference set changes +5. **Diversification**: Apply when search stagnates + +## References + +[1] Glover, F., Laguna, M., & Martí, R. (2000). Fundamentals of scatter search and path relinking. *Control and Cybernetics*, 29(3), 653-684. + +[2] Laguna, M., & Martí, R. (2003). *Scatter search: Methodology and implementations in C*. Springer. + +[3] Martí, R., Laguna, M., & Glover, F. (2006). Principles of scatter search. *European Journal of Operational Research*, 169(2), 359-372. diff --git a/docs/concepts/algorithm-components/metaheuristics/simulated-annealing.md b/docs/concepts/algorithm-components/metaheuristics/simulated-annealing.md new file mode 100644 index 000000000..c339d7c5b --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/simulated-annealing.md @@ -0,0 +1,167 @@ +# Simulated Annealing (SA) + +Simulated Annealing is a probabilistic metaheuristic inspired by the annealing process in metallurgy. It accepts worse solutions with a probability that decreases over time, allowing the algorithm to escape local optima early in the search while converging to good solutions later. + +## Algorithm Overview + +Simulated Annealing explores the solution space by occasionally accepting worse solutions based on a temperature parameter that gradually decreases (cools down) over time. + +```mermaid +graph TD + A[Start with Initial Solution] --> B[Generate Neighbor] + B --> C{Better Solution?} + C -->|Yes| D[Accept New Solution] + C -->|No| E{Accept with Probability?} + E -->|Yes| D + E -->|No| F[Keep Current Solution] + D --> G[Decrease Temperature] + F --> G + G --> H{Stopping Criteria?} + H -->|No| B + H -->|Yes| I[Return Best Solution Found] +``` + +## Algorithm Outline + +``` +s = GenerateInitialSolution() +T = InitialTemperature +while (not StoppingCriteria()) { + s' = GenerateNeighbor(s) + delta = score(s') - score(s) + + if (delta < 0 || random() < exp(-delta / T)) { + s = s' + } + + T = CoolingSchedule(T) +} +return best solution found +``` + +## Key Components + +### Temperature Control + +The temperature parameter controls the probability of accepting worse solutions: + +- **High Temperature**: More likely to accept worse solutions (exploration) +- **Low Temperature**: Less likely to accept worse solutions (exploitation) + +### Cooling Schedules + +Mork provides several cooling strategies: + +| Strategy | Formula | Description | +|----------|---------|-------------| +| **Exponential** | `T(k+1) = α * T(k)` | Geometric decrease, where 0 < α < 1 (e.g., 0.95) | +| **Linear** | `T(k+1) = T(k) - β` | Constant decrease by β | +| **Logarithmic** | `T(k) = T₀ / log(k+1)` | Slower decrease, theoretical convergence guarantees | + +## How to Use + +### Basic Example + +```java +// Define temperature schedule +CoolingSchedule cooling = + new ExponentialCooling<>(1000.0, 0.95); // Initial temp = 1000, alpha = 0.95 + +// Create neighborhood explorer +NeighborhoodExplorer explorer = + new MyNeighborhoodExplorer(); + +// Build Simulated Annealing +var sa = new SimulatedAnnealing<>( + "SA", + cooling, + explorer, + 10000 // max iterations +); + +// Use in your algorithm +Solution solution = constructive.construct(instance); +solution = sa.improve(solution); // SA can be used as an improver +``` + +### With Custom Acceptance Criterion + +```java +// Custom acceptance function +AcceptanceCriterion acceptance = + (current, candidate, temperature) -> { + double delta = candidate.getScore() - current.getScore(); + return delta < 0 || Math.random() < Math.exp(-delta / temperature); + }; + +var sa = new SimulatedAnnealing<>( + "CustomSA", + cooling, + explorer, + acceptance, + 10000 +); +``` + +## Implementation Notes + +### As an Algorithm vs As an Improver + +Simulated Annealing can be used in two ways: + +1. **As a standalone algorithm**: Combines construction + SA improvement +2. **As an improver component**: Can be plugged into other metaheuristics + +### Best Practices + +- **Initial Temperature**: Should be high enough to accept ~50-80% of worse moves initially +- **Cooling Rate**: Slower cooling (e.g., α = 0.99) often gives better results but takes longer +- **Stopping Criteria**: Use time limits or a minimum temperature threshold +- **Track Best**: Always keep track of the best solution found, as SA can end in a non-optimal state + +### Performance Tips + +```java +// Efficient: reuse neighbor generation +var explorer = new CachedNeighborhoodExplorer<>(baseExplorer); + +// Consider adaptive cooling +var adaptiveCooling = new AdaptiveCooling<>( + initialTemp, + minTemp, + acceptanceRateTarget // e.g., 0.3 for 30% acceptance +); +``` + +## Related Java Classes + +- **[`SimulatedAnnealing`](../../../../apidocs/es/urjc/etsii/grafo/improve/sa/SimulatedAnnealing.html)**: Main SA implementation +- **[`CoolingSchedule`](../../../../apidocs/es/urjc/etsii/grafo/improve/sa/CoolingSchedule.html)**: Interface for temperature schedules +- **[`AcceptanceCriterion`](../../../../apidocs/es/urjc/etsii/grafo/improve/sa/AcceptanceCriterion.html)**: Interface for acceptance criteria +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base class for improvement methods + +## Example Use Cases + +### TSP (Traveling Salesman Problem) + +```java +var cooling = new ExponentialCooling<>(100.0, 0.95); +var explorer = new TwoOptNeighborhood<>(); +var sa = new SimulatedAnnealing<>("TSP-SA", cooling, explorer, 50000); +``` + +### VRP (Vehicle Routing Problem) + +```java +var cooling = new LinearCooling<>(500.0, 0.1); +var explorer = new SwapRoutesNeighborhood<>(); +var sa = new SimulatedAnnealing<>("VRP-SA", cooling, explorer, 100000); +``` + +## References + +[1] Kirkpatrick, S., Gelatt, C. D., & Vecchi, M. P. (1983). Optimization by simulated annealing. *Science*, 220(4598), 671-680. + +[2] Černý, V. (1985). Thermodynamical approach to the traveling salesman problem: An efficient simulation algorithm. *Journal of Optimization Theory and Applications*, 45(1), 41-51. + +[3] Van Laarhoven, P. J., & Aarts, E. H. (1987). *Simulated annealing: Theory and applications*. Springer. diff --git a/docs/concepts/algorithm-components/metaheuristics/vnd.md b/docs/concepts/algorithm-components/metaheuristics/vnd.md new file mode 100644 index 000000000..555932e5d --- /dev/null +++ b/docs/concepts/algorithm-components/metaheuristics/vnd.md @@ -0,0 +1,319 @@ +# Variable Neighborhood Descent (VND) + +Variable Neighborhood Descent (VND) is a deterministic improvement method that systematically explores multiple neighborhood structures to escape local optima. Unlike VNS, VND doesn't use random shakes—it systematically tries different neighborhoods until no improvement is found. + +## Algorithm Overview + +VND cycles through a set of neighborhood structures, applying local search in each until a local optimum with respect to all neighborhoods is reached. + +```mermaid +graph TD + A[Start with Solution s] --> B[k = 1] + B --> C[Apply LS in Neighborhood k] + C --> D{Improvement Found?} + D -->|Yes| E[Accept New Solution] + E --> B + D -->|No| F[k = k + 1] + F --> G{k > kmax?} + G -->|No| C + G -->|Yes| H[Return Solution - Local Optimum] +``` + +## Algorithm Outline + +``` +VND(solution s) { + k = 1 + while (k <= kmax) { + s' = LocalSearch(s, neighborhood_k) + + if (s'.isBetterThan(s)) { + s = s' + k = 1 // Restart from first neighborhood + } else { + k = k + 1 // Try next neighborhood + } + } + return s +} +``` + +## Key Concepts + +### Systematic Exploration + +VND explores neighborhoods in a **deterministic order**: + +1. Start with the first neighborhood +2. Apply local search +3. If improvement found, restart from first neighborhood +4. If no improvement, try next neighborhood +5. Stop when all neighborhoods exhausted + +### Neighborhood Ordering + +The order matters! Common strategies: + +- **Simple to Complex**: Start with fast, simple neighborhoods +- **Complex to Simple**: Start with more thorough neighborhoods +- **Most Effective First**: Based on empirical performance +- **Problem-Specific**: Order based on problem structure + +### Difference from VNS + +| Aspect | VND | VNS | +|--------|-----|-----| +| **Exploration** | Deterministic | Stochastic (uses shake) | +| **Usage** | Local search component | Complete metaheuristic | +| **Speed** | Faster (no random perturbations) | Slower (includes shakes) | +| **Typical Use** | Inside other algorithms | Standalone algorithm | + +## How to Use + +### Basic Example + +```java +// Define multiple local search operators (neighborhoods) +List> improvers = List.of( + new SwapLS<>(), // Neighborhood 1: swap two elements + new InsertLS<>(), // Neighborhood 2: move one element + new TwoOptLS<>() // Neighborhood 3: 2-opt moves +); + +// Build VND +var vnd = new VND<>( + "VND-3Neighborhoods", + improvers +); + +// Use as an improver +Solution improved = vnd.improve(solution); +``` + +### With Custom Neighborhood Order + +```java +// Order neighborhoods by expected effectiveness +var vnd = new VND<>( + "VND-Ordered", + List.of( + new FastGreedySwap<>(), // Fast, often effective + new TwoOpt<>(), // Medium speed + new ThreeOpt<>(), // Slower, more thorough + new OrOpt<>() // Comprehensive + ) +); +``` + +### Inside VNS + +```java +// VND is commonly used as the improvement method in VNS +var vnd = new VND<>("VND", List.of(swap, insert, twoOpt)); + +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(vnd) // Use VND as the improver + .withShake(shake) + .withNeighChange(5) + .build("VNS-with-VND"); +``` + +### Conditional Neighborhoods + +```java +// Only explore expensive neighborhoods if cheap ones fail +var vnd = new ConditionalVND( + "Adaptive-VND" +) { + @Override + protected List> selectNeighborhoods( + MySolution solution, int attemptsSinceImprovement) { + + if (attemptsSinceImprovement < 3) { + // Try fast neighborhoods first + return List.of(fastSwap, fastInsert); + } else { + // If stuck, try more expensive neighborhoods + return List.of(fastSwap, fastInsert, twoOpt, threeOpt); + } + } +}; +``` + +## Implementation Notes + +### Creating Neighborhood Operators + +Each neighborhood should implement the `Improver` interface: + +```java +public class SwapLS, I extends Instance> + extends Improver { + + @Override + public S improve(S solution) { + boolean improved = true; + + while (improved) { + improved = false; + + for (int i = 0; i < solution.size(); i++) { + for (int j = i + 1; j < solution.size(); j++) { + double delta = evaluateSwap(solution, i, j); + + if (delta < 0) { // Improvement found + applySwap(solution, i, j); + improved = true; + break; + } + } + if (improved) break; // First improvement + } + } + + return solution; + } +} +``` + +### Performance Optimization + +```java +// Cache expensive calculations +var cachedVND = new VND<>( + "Cached-VND", + List.of( + new CachedImprover<>(swap), + new CachedImprover<>(insert), + new CachedImprover<>(twoOpt) + ) +); + +// Limit iterations per neighborhood +var limitedVND = new VND( + "Limited-VND" +) { + @Override + protected MySolution applyNeighborhood( + MySolution solution, Improver improver, int k) { + + int maxIterations = 1000 / (k + 1); // Fewer iterations for later neighborhoods + return improver.improve(solution, maxIterations); + } +}; +``` + +### Tracking Effectiveness + +```java +public class MonitoredVND, I extends Instance> + extends VND { + + private Map neighborhoodImprovements = new HashMap<>(); + + @Override + public S improve(S solution) { + int k = 0; + S current = solution; + + while (k < improvers.size()) { + S improved = improvers.get(k).improve(current); + + if (improved.isBetterThan(current)) { + String name = improvers.get(k).getClass().getSimpleName(); + neighborhoodImprovements.merge(name, 1, Integer::sum); + current = improved; + k = 0; // Restart + } else { + k++; + } + } + + log.info("Neighborhood effectiveness: {}", neighborhoodImprovements); + return current; + } +} +``` + +## Related Java Classes + +- **[`VND`](../../../../apidocs/es/urjc/etsii/grafo/improve/VND.html)**: Main VND implementation +- **[`Improver`](../../../../apidocs/es/urjc/etsii/grafo/improve/Improver.html)**: Base class for improvement methods +- **[`SequentialImprover`](../../../../apidocs/es/urjc/etsii/grafo/improve/SequentialImprover.html)**: Chain multiple improvers +- **[`VNS`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/vns/VNS.html)**: Variable Neighborhood Search + +## Example Use Cases + +### TSP with Multiple Neighborhoods + +```java +var vnd = new VND<>( + "TSP-VND", + List.of( + new TwoOptLS<>(), // 2-opt: swap two edges + new OrOptLS<>(1), // Or-opt-1: relocate one city + new OrOptLS<>(2), // Or-opt-2: relocate two consecutive cities + new ThreeOptLS<>() // 3-opt: more complex moves + ) +); +``` + +### VRP with Route and Inter-Route Neighborhoods + +```java +var vnd = new VND<>( + "VRP-VND", + List.of( + new IntraRouteSwap<>(), // Within route swaps + new IntraRouteRelocate<>(), // Within route relocations + new InterRouteSwap<>(), // Between route swaps + new InterRouteRelocate<>(), // Between route relocations + new TwoOptStar<>() // Cross exchange + ) +); +``` + +### Scheduling Problem + +```java +var vnd = new VND<>( + "Schedule-VND", + List.of( + new JobSwapLS<>(), // Swap jobs in sequence + new JobInsertLS<>(), // Move job to different position + new BlockSwapLS<>(), // Swap blocks of jobs + new ResourceReassignLS<>() // Change resource assignments + ) +); +``` + +## Best Practices + +1. **Order matters**: Start with fast, effective neighborhoods +2. **Balance speed and quality**: Don't use too many expensive neighborhoods +3. **3-5 neighborhoods**: Usually sufficient for most problems +4. **Test neighborhood effectiveness**: Monitor which neighborhoods find improvements +5. **Consider problem structure**: Order should reflect problem characteristics +6. **Use first improvement**: In each neighborhood, accept first improvement for speed + +## When to Use VND + +**Good for:** +- Problems with multiple natural neighborhood structures +- As improvement method inside other metaheuristics +- When deterministic behavior is desired +- Fast improvement without randomness + +**Not ideal for:** +- Single neighborhood problems (use simple local search) +- When strong diversification is needed (use VNS instead) +- Very large neighborhoods (computational cost) + +## References + +[1] Hansen, P., & Mladenović, N. (2001). Variable neighborhood search: Principles and applications. *European Journal of Operational Research*, 130(3), 449-467. + +[2] Mladenović, N., & Hansen, P. (1997). Variable neighborhood search. *Computers & Operations Research*, 24(11), 1097-1100. + +[3] Hansen, P., Mladenović, N., Brimberg, J., & Pérez, J. A. M. (2010). Variable neighborhood search. In *Handbook of Metaheuristics* (pp. 61-86). Springer. diff --git a/docs/concepts/algorithm-components/shakes/.pages b/docs/concepts/algorithm-components/shakes/.pages new file mode 100644 index 000000000..c93bfaa3e --- /dev/null +++ b/docs/concepts/algorithm-components/shakes/.pages @@ -0,0 +1,6 @@ +title: Shakes & Perturbations +nav: + - shake.md + - random-move.md + - destructive.md + - destroy-rebuild.md diff --git a/docs/concepts/algorithm-components/shakes/destroy-rebuild.md b/docs/concepts/algorithm-components/shakes/destroy-rebuild.md new file mode 100644 index 000000000..068f0879d --- /dev/null +++ b/docs/concepts/algorithm-components/shakes/destroy-rebuild.md @@ -0,0 +1,335 @@ +# Destroy-Rebuild + +Destroy-Rebuild is a combined shake operator that first destroys part of a solution and then immediately rebuilds it. It combines [Destructive](destructive.md) and [Reconstructive](../constructors/reconstructive.md) operations into a single perturbation method. + +## Overview + +Destroy-Rebuild implements Large Neighborhood Search (LNS) principles: remove parts of the solution and rebuild them, potentially in a better configuration. + +```mermaid +graph LR + A[Complete Solution] --> B[Destroy: Remove d Elements] + B --> C[Partial Solution] + C --> D[Rebuild: Add Elements Back] + D --> E[New Complete Solution] +``` + +## Key Concept + +Destroy-Rebuild is a **composite shake** that: +1. Applies a **destructive** operator to remove elements +2. Applies a **reconstructive** operator to add them back +3. Returns a **complete** solution (unlike pure destruction) + +## Base Implementation + +```java +public class DestroyRebuild, I extends Instance> + extends Shake { + + private final Destructive destructor; + private final Reconstructive reconstructor; + + public DestroyRebuild( + Destructive destructor, + Reconstructive reconstructor) { + super("DestroyRebuild"); + this.destructor = destructor; + this.reconstructor = reconstructor; + } + + @Override + public S shake(S solution) { + // 1. Destroy: create partial solution + S partial = destructor.shake(solution); + + // 2. Rebuild: complete the solution + S rebuilt = reconstructor.reconstruct(partial); + + return rebuilt; + } +} +``` + +## How to Use + +### Basic Usage + +```java +// Define destruction strategy +Destructive destructor = + new RandomDestructor<>(5); // Remove 5 elements + +// Define reconstruction strategy +Reconstructive reconstructor = + new GreedyReconstructor<>(); // Add back greedily + +// Combine into shake +var destroyRebuild = new DestroyRebuild<>(destructor, reconstructor); + +// Use in VNS +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(destroyRebuild) // Use as perturbation + .build("VNS"); +``` + +### Multiple Destroy-Rebuild Operators + +```java +// Different operators for different neighborhoods in VNS +var shake1 = new DestroyRebuild<>( + new RandomDestructor<>(3), // Small destruction + new GreedyReconstructor<>() +); + +var shake2 = new DestroyRebuild<>( + new WorstDestructor<>(6), // Medium destruction + new GRASPReconstructor<>(0.3) +); + +var shake3 = new DestroyRebuild<>( + new RelatedDestructor<>(10), // Large destruction + new RandomReconstructor<>() +); + +// Use in VND or adaptive VNS +var adaptiveShake = new AdaptiveShake<>( + List.of(shake1, shake2, shake3) +); +``` + +### With Local Search + +```java +// Destroy-rebuild followed by improvement +public class DestroyRebuildLS, I extends Instance> + extends Shake { + + private final DestroyRebuild destroyRebuild; + private final Improver improver; + + @Override + public S shake(S solution) { + S rebuilt = destroyRebuild.shake(solution); + return improver.improve(rebuilt); + } +} +``` + +## Destruction-Reconstruction Combinations + +Different combinations yield different search behaviors: + +| Destruction | Reconstruction | Behavior | +|-------------|----------------|----------| +| **Random** + **Greedy** | Diversification + Quality | Balanced | +| **Worst** + **GRASP** | Strategic + Randomized | Exploration | +| **Related** + **Greedy** | Clustered + Deterministic | Intensification | +| **Adaptive** + **Adaptive** | Both parameters vary | Reactive | + +## Strategies + +### 1. Random Destroy + Greedy Rebuild + +```java +var shake = new DestroyRebuild<>( + new RandomDestructor<>(d), + new GreedyReconstructor<>() +); +``` + +**Effect**: Remove random elements, rebuild optimally + +**Good for**: General purpose, balanced search + +### 2. Worst Destroy + GRASP Rebuild + +```java +var shake = new DestroyRebuild<>( + new WorstDestructor<>(d), + new GRASPReconstructor<>(0.3) +); +``` + +**Effect**: Remove bad elements, rebuild with randomization + +**Good for**: Escaping poor solution regions + +### 3. Related Destroy + Related Rebuild + +```java +var shake = new DestroyRebuild<>( + new RelatedDestructor<>(d), + new SimilarityBasedReconstructor<>() +); +``` + +**Effect**: Restructure related components + +**Good for**: Problems with structure (clusters, routes, etc.) + +## Adaptive Destroy-Rebuild + +```java +public class AdaptiveDestroyRebuild, I extends Instance> + extends Shake { + + private List> destructors; + private List> reconstructors; + private double[] weights; + + public AdaptiveDestroyRebuild( + List> destructors, + List> reconstructors) { + super("AdaptiveDestroyRebuild"); + this.destructors = destructors; + this.reconstructors = reconstructors; + this.weights = new double[destructors.size()]; + Arrays.fill(weights, 1.0); + } + + @Override + public S shake(S solution) { + // Select destructor based on weights + int destructorIdx = selectWeighted(weights); + int reconstructorIdx = selectWeighted(weights); + + // Apply selected operators + S partial = destructors.get(destructorIdx).shake(solution); + S rebuilt = reconstructors.get(reconstructorIdx).reconstruct(partial); + + // Update weights based on quality + updateWeights(destructorIdx, reconstructorIdx, rebuilt); + + return rebuilt; + } +} +``` + +## Parameter Tuning + +### Destruction Size + +```java +// Small destruction: fine-grained search +var smallShake = new DestroyRebuild<>( + new RandomDestructor<>(2), // Remove 2 + new GreedyReconstructor<>() +); + +// Large destruction: exploration +var largeShake = new DestroyRebuild<>( + new RandomDestructor<>(10), // Remove 10 + new GRASPReconstructor<>(0.5) +); + +// Adaptive: vary destruction size +var adaptiveShake = new DestroyRebuild<>( + new AdaptiveDestructor<>(solution -> calculateDestructionSize(solution)), + new GreedyReconstructor<>() +); +``` + +### Reconstruction Randomness + +```java +// Deterministic: same input → same output +var deterministicShake = new DestroyRebuild<>( + new RandomDestructor<>(d), + new GreedyReconstructor<>() +); + +// Randomized: same input → different outputs +var randomizedShake = new DestroyRebuild<>( + new RandomDestructor<>(d), + new GRASPReconstructor<>(0.3) // alpha = 0.3 +); +``` + +## Related Java Classes + +- **[`DestroyRebuild`](../../../../apidocs/es/urjc/etsii/grafo/shake/DestroyRebuild.html)**: Main implementation +- **[`Destructive`](../../../../apidocs/es/urjc/etsii/grafo/shake/Destructive.html)**: Destruction operators +- **[`Reconstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Reconstructive.html)**: Reconstruction methods +- **[`Shake`](../../../../apidocs/es/urjc/etsii/grafo/shake/Shake.html)**: Base shake interface +- **[`VNS`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/vns/VNS.html)**: Can use as perturbation + +## Example Use Cases + +### TSP Destroy-Rebuild + +```java +// Destroy: remove segment +var destructor = new TSPSegmentDestructor(5); + +// Rebuild: cheapest insertion +var reconstructor = new TSPCheapestInsertionReconstructor(); + +var shake = new DestroyRebuild<>(destructor, reconstructor); +``` + +### VRP Destroy-Rebuild + +```java +// Destroy: remove customers from worst routes +var destructor = new VRPWorstRouteDestructor(8); + +// Rebuild: insert with regret heuristic +var reconstructor = new VRPRegretReconstructor(); + +var shake = new DestroyRebuild<>(destructor, reconstructor); +``` + +### Scheduling Destroy-Rebuild + +```java +// Destroy: unschedule jobs on critical path +var destructor = new CriticalPathDestructor(6); + +// Rebuild: schedule with earliest due date +var reconstructor = new EDDReconstructor(); + +var shake = new DestroyRebuild<>(destructor, reconstructor); +``` + +## Best Practices + +1. **Match destruction size**: Larger destruction for more diversification +2. **Reconstruction quality**: Balance between greedy and randomized +3. **Combine strategies**: Use multiple destroy-rebuild pairs +4. **Adaptive selection**: Learn which combinations work best +5. **Time consideration**: Destruction+reconstruction can be expensive +6. **Maintain feasibility**: Both phases must preserve validity + +## Performance Considerations + +**Cost**: O(d × eval) where: +- d = number of elements destroyed +- eval = cost of evaluating insertions + +**Memory**: Usually works in-place after initial clone + +**Tradeoff**: Larger d → more expensive but potentially better solutions + +## When to Use + +**Good for:** +- Problems with insertable/removable elements +- When simple moves are too local +- Large neighborhood search scenarios +- Problems with structure to exploit + +**Consider alternatives when:** +- Destruction/reconstruction is very expensive +- Problem structure doesn't suit partial solutions +- Simple perturbations are sufficient + +## References + +[1] Shaw, P. (1998). Using constraint programming and local search methods to solve vehicle routing problems. In *CP* (Vol. 98, pp. 417-431). + +[2] Pisinger, D., & Ropke, S. (2007). A general heuristic for vehicle routing problems. *Computers & Operations Research*, 34(8), 2403-2435. + +[3] Ropke, S., & Pisinger, D. (2006). An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows. *Transportation Science*, 40(4), 455-472. diff --git a/docs/concepts/algorithm-components/shakes/destructive.md b/docs/concepts/algorithm-components/shakes/destructive.md new file mode 100644 index 000000000..e6193d8ee --- /dev/null +++ b/docs/concepts/algorithm-components/shakes/destructive.md @@ -0,0 +1,371 @@ +# Destructive + +Destructive operators remove elements from a solution, creating a partial solution that must later be completed. They are commonly used in destruction-reconstruction metaheuristics like Iterated Greedy. + +## Overview + +Destructive methods implement the **destruction phase** where parts of a solution are removed. The resulting partial solution is then reconstructed by a [Reconstructive](../constructors/reconstructive.md) method. + +```mermaid +graph LR + A[Complete Solution] --> B[Destructive Operator] + B --> C[Partial Solution] + C --> D[Missing Elements] + D --> E[Reconstructive] + E --> F[New Complete Solution] +``` + +## Base Destructive Interface + +```java +public interface Destructive, I extends Instance> + extends Shake { + + /** + * Destroy part of the solution + * @param solution Complete solution to partially destroy + * @return Partial solution with some elements removed + */ + @Override + S shake(S solution); // Also called destruct() in implementations +} +``` + +## Key Concept: Partial Solutions + +Unlike regular shakes, destructive operators create **incomplete** solutions: + +```java +@Override +public S shake(S solution) { + S partial = solution.cloneSolution(); + + // Remove d elements + var toRemove = selectElementsToRemove(partial, d); + for (Element element : toRemove) { + partial.remove(element); + } + + // partial is now incomplete! + assert !partial.isComplete(); + + return partial; +} +``` + +## Destruction Strategies + +### 1. Random Destruction + +Remove random elements: + +```java +public class RandomDestructor, I extends Instance> + implements Destructive { + + private final int d; // Number of elements to remove + + public RandomDestructor(int d) { + this.d = d; + } + + @Override + public S shake(S solution) { + S partial = solution.cloneSolution(); + + // Remove d random elements + List elements = new ArrayList<>(partial.getElements()); + Collections.shuffle(elements); + + for (int i = 0; i < Math.min(d, elements.size()); i++) { + partial.remove(elements.get(i)); + } + + return partial; + } +} +``` + +### 2. Worst Destruction + +Remove elements contributing most to objective: + +```java +public class WorstDestructor, I extends Instance> + implements Destructive { + + private final int d; + + public WorstDestructor(int d) { + this.d = d; + } + + @Override + public S shake(S solution) { + S partial = solution.cloneSolution(); + + // Sort elements by their contribution (worst first) + var elements = partial.getElements().stream() + .sorted(Comparator.comparingDouble(e -> -evaluateContribution(e, partial))) + .collect(Collectors.toList()); + + // Remove d worst elements + for (int i = 0; i < Math.min(d, elements.size()); i++) { + partial.remove(elements.get(i)); + } + + return partial; + } + + private double evaluateContribution(Element element, S solution) { + // How much does this element contribute to the objective? + return solution.evaluateRemoval(element); + } +} +``` + +### 3. Related Destruction + +Remove elements that are related/similar: + +```java +public class RelatedDestructor, I extends Instance> + implements Destructive { + + private final int d; + + @Override + public S shake(S solution) { + S partial = solution.cloneSolution(); + + // Pick a random seed element + Element seed = partial.getRandomElement(); + partial.remove(seed); + + // Remove d-1 elements most related to seed + var remaining = new ArrayList<>(partial.getElements()); + remaining.sort(Comparator.comparingDouble(e -> + -similarity(seed, e, partial))); + + for (int i = 0; i < Math.min(d - 1, remaining.size()); i++) { + partial.remove(remaining.get(i)); + } + + return partial; + } + + protected abstract double similarity(Element e1, Element e2, S solution); +} +``` + +### 4. Shaw Removal (Cluster-based) + +Remove elements that are similar to each other: + +```java +public class ShawDestructor, I extends Instance> + implements Destructive { + + @Override + public S shake(S solution) { + S partial = solution.cloneSolution(); + + Element seed = partial.getRandomElement(); + List toRemove = new ArrayList<>(); + toRemove.add(seed); + + // Build cluster of related elements + while (toRemove.size() < d) { + Element mostRelated = findMostRelatedNotRemoved( + toRemove, + partial.getElements() + ); + if (mostRelated != null) { + toRemove.add(mostRelated); + } else { + break; + } + } + + // Remove cluster + toRemove.forEach(partial::remove); + + return partial; + } +} +``` + +## Use in Iterated Greedy + +```java +// Destructor removes d elements +Destructive destructor = + new RandomDestructor<>(4); + +// Reconstructor adds them back +Reconstructive reconstructor = + new GreedyReconstructor<>(); + +// Combine in IG +var ig = new IteratedGreedy<>( + "IG", + constructor, + destructor, // Destruction phase + reconstructor, // Reconstruction phase + improver +); +``` + +## Use in DestroyRebuild Shake + +```java +// Combined operator for VNS +var destroyRebuild = new DestroyRebuild<>( + new WorstDestructor<>(5), + new GRASPReconstructor<>(0.3) +); + +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(destroyRebuild) + .build("VNS"); +``` + +## Parameter: Destruction Size (d) + +The number of elements to remove is crucial: + +| Size | Effect | Use Case | +|------|--------|----------| +| **Small (1-3)** | Minor perturbation | Intensification | +| **Medium (4-10)** | Balanced | General purpose | +| **Large (>10)** | Major restructuring | Diversification | +| **Percentage (10-30%)** | Adaptive to problem size | Variable-size problems | + +### Adaptive Destruction Size + +```java +public class AdaptiveDestructor, I extends Instance> + implements Destructive { + + private double destructionRatio = 0.2; // 20% of elements + + @Override + public S shake(S solution) { + S partial = solution.cloneSolution(); + int d = (int) (solution.size() * destructionRatio); + + // Remove d elements randomly + var toRemove = selectRandomElements(partial, d); + toRemove.forEach(partial::remove); + + return partial; + } + + public void setDestructionRatio(double ratio) { + this.destructionRatio = ratio; + } +} +``` + +## Related Java Classes + +- **[`Destructive`](../../../../apidocs/es/urjc/etsii/grafo/shake/Destructive.html)**: Destruction interface +- **[`DestroyRebuild`](../../../../apidocs/es/urjc/etsii/grafo/shake/DestroyRebuild.html)**: Combined destroy-rebuild +- **[`Reconstructive`](../../../../apidocs/es/urjc/etsii/grafo/create/Reconstructive.html)**: Reconstruction methods +- **[`IteratedGreedy`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/IteratedGreedy.html)**: Uses destructors +- **[`Shake`](../../../../apidocs/es/urjc/etsii/grafo/shake/Shake.html)**: Base shake interface + +## Example Use Cases + +### TSP Segment Destruction + +```java +public class TSPSegmentDestructor implements Destructive { + + private final int segmentLength; + + @Override + public TSPSolution shake(TSPSolution solution) { + TSPSolution partial = solution.cloneSolution(); + + // Remove a continuous segment + int start = random.nextInt(solution.size() - segmentLength); + for (int i = 0; i < segmentLength; i++) { + partial.removeCity(start); // Always remove at start since list shifts + } + + return partial; + } +} +``` + +### VRP Route Destruction + +```java +public class VRPRouteDestructor implements Destructive { + + private final int numCustomers; + + @Override + public VRPSolution shake(VRPSolution solution) { + VRPSolution partial = solution.cloneSolution(); + + // Remove customers from random routes + for (int i = 0; i < numCustomers; i++) { + Route route = partial.getRandomNonEmptyRoute(); + Customer customer = route.getRandomCustomer(); + partial.unassignCustomer(customer); + } + + return partial; + } +} +``` + +### Job Shop Destruction + +```java +public class JobShopDestructor implements Destructive { + + @Override + public ScheduleSolution shake(ScheduleSolution solution) { + ScheduleSolution partial = solution.cloneSolution(); + + // Remove jobs from critical path + var criticalJobs = partial.getCriticalPathJobs(); + int toRemove = Math.min(d, criticalJobs.size()); + + Collections.shuffle(criticalJobs); + for (int i = 0; i < toRemove; i++) { + partial.unscheduleJob(criticalJobs.get(i)); + } + + return partial; + } +} +``` + +## Best Practices + +1. **Maintain feasibility**: Even partial solutions should be feasible +2. **Track removed elements**: Solution should know what's missing +3. **Parameterize destruction size**: Allow external control +4. **Problem-specific**: Design destruction strategies based on problem structure +5. **Balance randomness**: Mix random and strategic element selection +6. **Efficient removal**: Use efficient data structures for element removal + +## Performance Considerations + +- **Removal cost**: O(d) removals, each potentially O(n) +- **Element selection**: Pre-sort or use priority queues for efficiency +- **Memory**: Clone solution or modify in-place depending on needs + +## References + +[1] Ruiz, R., & Stützle, T. (2007). A simple and effective iterated greedy algorithm for the permutation flowshop scheduling problem. *European Journal of Operational Research*, 177(3), 2033-2049. + +[2] Shaw, P. (1998). Using constraint programming and local search methods to solve vehicle routing problems. In *CP* (Vol. 98, pp. 417-431). + +[3] Ropke, S., & Pisinger, D. (2006). An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows. *Transportation Science*, 40(4), 455-472. diff --git a/docs/concepts/algorithm-components/shakes/random-move.md b/docs/concepts/algorithm-components/shakes/random-move.md new file mode 100644 index 000000000..13f03016e --- /dev/null +++ b/docs/concepts/algorithm-components/shakes/random-move.md @@ -0,0 +1,416 @@ +# Random Move Shake + +Random Move Shake is a simple perturbation method that applies a sequence of random moves to a solution. It's one of the most straightforward shake implementations and serves as a baseline perturbation strategy. + +## Overview + +Random Move Shake perturbs a solution by applying random valid moves without considering whether they improve the objective function. + +```mermaid +graph LR + A[Solution] --> B[Generate Random Move 1] + B --> C[Apply Move 1] + C --> D[Generate Random Move 2] + D --> E[Apply Move 2] + E --> F[...] + F --> G[After k Moves] +``` + +## Algorithm Outline + +``` +RandomMoveShake(solution, k): + perturbed = clone(solution) + + for (i = 0; i < k; i++) { + move = generateRandomMove(perturbed) + apply(move, perturbed) + } + + return perturbed +``` + +## How to Use + +### Basic Implementation + +```java +public class RandomMoveShake, I extends Instance> + extends Shake { + + private final int numMoves; + + public RandomMoveShake(int numMoves) { + super("RandomMove-" + numMoves); + this.numMoves = numMoves; + } + + @Override + public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + for (int i = 0; i < numMoves; i++) { + Move move = generateRandomMove(perturbed); + move.apply(perturbed); + } + + return perturbed; + } + + protected Move generateRandomMove(S solution) { + var allMoves = solution.getAllPossibleMoves(); + return allMoves.get(ThreadLocalRandom.current().nextInt(allMoves.size())); + } +} +``` + +### With Move Types + +```java +public abstract class TypedRandomMoveShake, I extends Instance> + extends RandomMoveShake { + + protected enum MoveType { SWAP, INSERT, REVERSE } + + @Override + protected Move generateRandomMove(S solution) { + // Randomly select move type + MoveType type = MoveType.values()[ + ThreadLocalRandom.current().nextInt(MoveType.values().length) + ]; + + switch (type) { + case SWAP: + return generateRandomSwap(solution); + case INSERT: + return generateRandomInsert(solution); + case REVERSE: + return generateRandomReverse(solution); + default: + throw new IllegalStateException(); + } + } + + protected abstract Move generateRandomSwap(S solution); + protected abstract Move generateRandomInsert(S solution); + protected abstract Move generateRandomReverse(S solution); +} +``` + +### Parameterized by Neighborhood + +```java +public class NeighborhoodRandomShake, I extends Instance> + extends Shake { + + private final int k; // Neighborhood parameter + + public NeighborhoodRandomShake(int k) { + super("RandomShake-" + k); + this.k = k; + } + + @Override + public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + // Apply k random moves from neighborhood k + for (int i = 0; i < k; i++) { + Move move = generateRandomMoveFromNeighborhood(perturbed, k); + move.apply(perturbed); + } + + return perturbed; + } + + protected Move generateRandomMoveFromNeighborhood(S solution, int neighborhood) { + // Different move types based on neighborhood + switch (neighborhood) { + case 1: return generateSmallMove(solution); + case 2: return generateMediumMove(solution); + case 3: return generateLargeMove(solution); + default: return generateRandomMove(solution); + } + } +} +``` + +## Use Cases + +### In VNS + +```java +// Create random shake with increasing intensity +var shake = new RandomMoveShake<>(5); + +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(shake) + .withNeighChange((sol, k) -> k >= 10 ? VNSNeighChange.STOPNOW : k + 1) + .build("VNS"); +``` + +### Multiple Neighborhoods in VNS + +```java +// Different random shake for each neighborhood +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(new NeighborhoodRandomShake<>()) // Uses k from VNS + .withNeighChange(5) + .build("VNS"); +``` + +### In Iterated Local Search + +```java +var ils = new IteratedLocalSearch<>( + "ILS", + constructor, + improver, + new RandomMoveShake<>(3), // Light perturbation + 100 +); +``` + +## Move Types + +### Common Random Moves + +**Swap**: Exchange two elements +```java +protected Move generateRandomSwap(S solution) { + int i = random.nextInt(solution.size()); + int j = random.nextInt(solution.size()); + return new SwapMove(i, j); +} +``` + +**Insert/Relocate**: Move element to different position +```java +protected Move generateRandomInsert(S solution) { + int from = random.nextInt(solution.size()); + int to = random.nextInt(solution.size()); + return new InsertMove(from, to); +} +``` + +**Reverse/2-opt**: Reverse a segment +```java +protected Move generateRandomReverse(S solution) { + int i = random.nextInt(solution.size()); + int j = random.nextInt(solution.size()); + if (i > j) { int temp = i; i = j; j = temp; } + return new ReverseMove(i, j); +} +``` + +## Shake Strength + +Control perturbation intensity via number of moves: + +| Number of Moves | Intensity | Use Case | +|-----------------|-----------|----------| +| **1-2** | Very light | Fine-tuning | +| **3-5** | Light | General VNS | +| **6-10** | Medium | Escaping local optima | +| **>10** | Heavy | Strong diversification | + +### Adaptive Strength + +```java +public class AdaptiveRandomShake, I extends Instance> + extends RandomMoveShake { + + private int baseNumMoves; + private int currentStrength; + private int iterationsSinceImprovement; + + @Override + public S shake(S solution) { + // Increase strength if stuck + int movesToApply = baseNumMoves + (iterationsSinceImprovement / 10); + + S perturbed = solution.cloneSolution(); + for (int i = 0; i < movesToApply; i++) { + Move move = generateRandomMove(perturbed); + move.apply(perturbed); + } + + return perturbed; + } + + public void recordImprovement(boolean improved) { + if (improved) { + iterationsSinceImprovement = 0; + } else { + iterationsSinceImprovement++; + } + } +} +``` + +## Implementation Variants + +### With Feasibility Check + +```java +@Override +public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + int appliedMoves = 0; + int attempts = 0; + int maxAttempts = numMoves * 10; + + while (appliedMoves < numMoves && attempts < maxAttempts) { + Move move = generateRandomMove(perturbed); + + // Only apply if maintains feasibility + if (move.isFeasible(perturbed)) { + move.apply(perturbed); + appliedMoves++; + } + + attempts++; + } + + return perturbed; +} +``` + +### Weighted Move Selection + +```java +public class WeightedRandomShake, I extends Instance> + extends RandomMoveShake { + + private Map, Double> moveWeights; + + @Override + protected Move generateRandomMove(S solution) { + // Select move type based on weights + Class selectedType = selectWeighted(moveWeights); + return createMove(selectedType, solution); + } + + public void updateWeights(Class moveType, double quality) { + // Update weight based on success + moveWeights.merge(moveType, quality, (old, val) -> 0.9 * old + 0.1 * val); + } +} +``` + +## Related Java Classes + +- **[`RandomMoveShake`](../../../../apidocs/es/urjc/etsii/grafo/shake/RandomMoveShake.html)**: Framework implementation +- **[`Shake`](../../../../apidocs/es/urjc/etsii/grafo/shake/Shake.html)**: Base shake interface +- **[`VNS`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/vns/VNS.html)**: Uses shake as perturbation +- **[`Move`]**: Interface for solution modifications + +## Example Implementations + +### TSP Random Moves + +```java +public class TSPRandomShake extends RandomMoveShake { + + @Override + protected Move generateRandomMove(TSPSolution solution) { + int type = random.nextInt(3); + + switch (type) { + case 0: // Swap two cities + int i = random.nextInt(solution.size()); + int j = random.nextInt(solution.size()); + return new TSPSwapMove(i, j); + + case 1: // 2-opt + int a = random.nextInt(solution.size()); + int b = random.nextInt(solution.size()); + if (a > b) { int temp = a; a = b; b = temp; } + return new TSP2OptMove(a, b); + + case 2: // Relocate city + int from = random.nextInt(solution.size()); + int to = random.nextInt(solution.size()); + return new TSPRelocateMove(from, to); + + default: + throw new IllegalStateException(); + } + } +} +``` + +### VRP Random Moves + +```java +public class VRPRandomShake extends RandomMoveShake { + + @Override + protected Move generateRandomMove(VRPSolution solution) { + // 50% intra-route, 50% inter-route + if (random.nextBoolean()) { + return generateIntraRouteMove(solution); + } else { + return generateInterRouteMove(solution); + } + } + + private Move generateIntraRouteMove(VRPSolution solution) { + Route route = solution.getRandomRoute(); + int i = random.nextInt(route.size()); + int j = random.nextInt(route.size()); + return new IntraRouteSwap(route, i, j); + } + + private Move generateInterRouteMove(VRPSolution solution) { + Route route1 = solution.getRandomRoute(); + Route route2 = solution.getRandomRoute(); + int pos1 = random.nextInt(route1.size()); + int pos2 = random.nextInt(route2.size()); + return new InterRouteSwap(route1, pos1, route2, pos2); + } +} +``` + +## Best Practices + +1. **Clone first**: Always clone the solution before modifying +2. **Feasibility**: Ensure moves maintain solution validity +3. **Parameterize strength**: Allow control over number of moves +4. **Move diversity**: Use multiple move types for better exploration +5. **Efficient generation**: Generate moves quickly without evaluation +6. **No evaluation**: Don't evaluate moves, apply them randomly + +## Comparison with Other Shakes + +| Shake Type | Cost | Quality | Diversity | +|------------|------|---------|-----------| +| **Random Move** | Low | Medium | High | +| **Destroy-Rebuild** | High | High | High | +| **Single Move** | Very Low | Low | Low | +| **Guided Moves** | Medium | High | Medium | + +## When to Use + +**Good for:** +- Fast perturbations needed +- When any diversification helps +- Simple baseline shake +- Large solution spaces + +**Consider alternatives when:** +- Need targeted perturbations +- Problem has specific structure to exploit +- Moves are expensive to apply +- Need guaranteed minimum perturbation quality + +## References + +[1] Hansen, P., & Mladenović, N. (2001). Variable neighborhood search: Principles and applications. *European Journal of Operational Research*, 130(3), 449-467. + +[2] Lourenço, H. R., Martin, O. C., & Stützle, T. (2003). Iterated local search. In *Handbook of Metaheuristics* (pp. 320-353). Springer. diff --git a/docs/concepts/algorithm-components/shakes/shake.md b/docs/concepts/algorithm-components/shakes/shake.md new file mode 100644 index 000000000..8cb12a42c --- /dev/null +++ b/docs/concepts/algorithm-components/shakes/shake.md @@ -0,0 +1,379 @@ +# Shake / Perturbation Methods + +Shake methods (also called perturbation or diversification methods) are algorithm components that modify solutions in ways that may worsen their objective value. Unlike improvers, shakes are allowed to—and expected to—return worse solutions. + +## Overview + +Shakes help algorithms escape local optima by perturbing solutions. They introduce diversification into the search process. + +```mermaid +graph TD + A[Current Solution] --> B[Shake Method] + B --> C[Perturbed Solution] + C --> D{May be Worse} + D --> E[Explore New Region] + E --> F[Apply Improver] + F --> G[New Local Optimum] +``` + +## Base Shake Interface + +```java +public abstract class Shake, I extends Instance> + extends AlgorithmComponent { + + /** + * Perturb the given solution + * @param solution Solution to shake + * @return Perturbed solution (may be worse) + */ + public abstract S shake(S solution); +} +``` + +## Key Principle: Diversification + +**Critical difference from Improvers**: Shakes CAN return worse solutions: + +```java +@Override +public S shake(S solution) { + S perturbed = applyPerturbation(solution); + + // No requirement that perturbed is better! + // perturbed.getScore() can be > solution.getScore() + + return perturbed; +} +``` + +## Common Shake Types + +| Shake Type | Description | Documentation | +|------------|-------------|---------------| +| **Random Moves** | Apply random modifications | [Random Move](random-move.md) | +| **Destructive** | Remove elements from solution | [Destructive](destructive.md) | +| **Destroy-Rebuild** | Destroy part and reconstruct | [Destroy-Rebuild](destroy-rebuild.md) | +| **Large Moves** | Apply significant structural changes | See examples below | + +## When to Use Shakes + +Shakes are essential in: + +- **Variable Neighborhood Search (VNS)**: Core perturbation mechanism +- **Iterated Local Search (ILS)**: Escape local optima between LS phases +- **Simulated Annealing**: Random moves with probabilistic acceptance +- **Iterated Greedy**: Destruction phase + +## How to Use + +### In VNS + +```java +var shake = new MyShake(); + +var vns = new VNSBuilder() + .withConstructive(constructor) + .withImprover(improver) + .withShake(shake) // Perturbation mechanism + .withNeighChange(5) + .build("VNS"); +``` + +### In Iterated Local Search + +```java +var ils = new IteratedLocalSearch<>( + "ILS", + constructor, + improver, + shake, // Perturbation between LS phases + 100 +); +``` + +### Standalone + +```java +// Escape from local optimum +var localOptimum = improver.improve(solution); +var perturbed = shake.shake(localOptimum); +var newLocalOptimum = improver.improve(perturbed); +``` + +## Implementation Guidelines + +### Basic Pattern + +```java +public class MyShake, I extends Instance> + extends Shake { + + private final int strength; + + public MyShake(int strength) { + super("MyShake"); + this.strength = strength; + } + + @Override + public S shake(S solution) { + // Clone to avoid modifying original + S perturbed = solution.cloneSolution(); + + // Apply 'strength' random moves + for (int i = 0; i < strength; i++) { + Move randomMove = generateRandomMove(perturbed); + randomMove.apply(perturbed); + } + + return perturbed; + } + + private Move generateRandomMove(S solution) { + // Generate a random valid move + var moves = solution.getAllPossibleMoves(); + return moves.get(ThreadLocalRandom.current().nextInt(moves.size())); + } +} +``` + +### With Adaptive Strength + +```java +public class AdaptiveShake, I extends Instance> + extends Shake { + + private int baseStrength; + private int currentStrength; + + public AdaptiveShake(int baseStrength) { + super("AdaptiveShake"); + this.baseStrength = baseStrength; + this.currentStrength = baseStrength; + } + + @Override + public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + // Apply current strength moves + for (int i = 0; i < currentStrength; i++) { + Move move = generateRandomMove(perturbed); + move.apply(perturbed); + } + + return perturbed; + } + + /** + * Increase shake strength if stuck + */ + public void increaseStrength() { + currentStrength = Math.min(currentStrength + 1, baseStrength * 3); + } + + /** + * Reset to base strength when improvement found + */ + public void reset() { + currentStrength = baseStrength; + } +} +``` + +### Parameterized by Neighborhood + +```java +public class NeighborhoodShake, I extends Instance> + extends Shake { + + private final int k; // Neighborhood index + + public NeighborhoodShake(int k) { + super("Shake-" + k); + this.k = k; + } + + @Override + public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + // Different shake depending on k + switch (k) { + case 1: + applySmallPerturbation(perturbed); + break; + case 2: + applyMediumPerturbation(perturbed); + break; + case 3: + applyLargePerturbation(perturbed); + break; + default: + applyRandomPerturbation(perturbed, k); + } + + return perturbed; + } +} +``` + +## Shake Strength + +The **strength** or **intensity** of a shake determines how much the solution changes: + +| Strength | Perturbation | Use Case | +|----------|--------------|----------| +| **Small (1-3 moves)** | Minor changes | Fine-grained exploration | +| **Medium (4-10 moves)** | Moderate changes | Balance exploration/exploitation | +| **Large (>10 moves)** | Major changes | Strong diversification | + +## Design Considerations + +### 1. Preserve Feasibility + +Ensure shaken solutions remain feasible: + +```java +@Override +public S shake(S solution) { + S perturbed = solution.cloneSolution(); + + // Apply moves + for (int i = 0; i < strength; i++) { + Move move = generateRandomMove(perturbed); + // Only apply if maintains feasibility + if (move.isFeasible(perturbed)) { + move.apply(perturbed); + } + } + + return perturbed; +} +``` + +### 2. Controllable Intensity + +Allow external control of shake strength: + +```java +public interface ParameterizedShake, I extends Instance> + extends Shake { + + S shake(S solution, int intensity); +} +``` + +### 3. Problem-Specific Perturbations + +Design shakes that exploit problem structure: + +```java +// TSP: reverse random segments +public class TSPSegmentReverse extends Shake { + @Override + public TSPSolution shake(TSPSolution solution) { + TSPSolution perturbed = solution.cloneSolution(); + int i = random.nextInt(perturbed.size()); + int j = random.nextInt(perturbed.size()); + if (i > j) { + int temp = i; i = j; j = temp; + } + perturbed.reverse(i, j); + return perturbed; + } +} +``` + +## Related Java Classes + +- **[`Shake`](../../../../apidocs/es/urjc/etsii/grafo/shake/Shake.html)**: Base shake class +- **[`Destructive`](../../../../apidocs/es/urjc/etsii/grafo/shake/Destructive.html)**: Destruction operators +- **[`DestroyRebuild`](../../../../apidocs/es/urjc/etsii/grafo/shake/DestroyRebuild.html)**: Combined destroy-rebuild +- **[`RandomMoveShake`](../../../../apidocs/es/urjc/etsii/grafo/shake/RandomMoveShake.html)**: Random move perturbation +- **[`VNS`](../../../../apidocs/es/urjc/etsii/grafo/algorithms/vns/VNS.html)**: Uses shake as core component + +## Example Use Cases + +### TSP Random Swap + +```java +public class TSPRandomSwap extends Shake { + private final int numSwaps; + + @Override + public TSPSolution shake(TSPSolution solution) { + TSPSolution perturbed = solution.cloneSolution(); + + for (int i = 0; i < numSwaps; i++) { + int idx1 = random.nextInt(perturbed.size()); + int idx2 = random.nextInt(perturbed.size()); + perturbed.swap(idx1, idx2); + } + + return perturbed; + } +} +``` + +### VRP Route Shake + +```java +public class VRPRouteShake extends Shake { + @Override + public VRPSolution shake(VRPSolution solution) { + VRPSolution perturbed = solution.cloneSolution(); + + // Move random customers between routes + for (int i = 0; i < 3; i++) { + Customer customer = perturbed.getRandomCustomer(); + Route newRoute = perturbed.getRandomRoute(); + perturbed.moveCustomer(customer, newRoute); + } + + return perturbed; + } +} +``` + +## Best Practices + +1. **Clone solutions**: Don't modify the input solution +2. **Maintain feasibility**: Ensure perturbed solutions are valid +3. **Parameterize strength**: Allow control over perturbation intensity +4. **Problem-aware**: Design shakes that respect problem structure +5. **Efficient operations**: Use fast move types for perturbations +6. **Test effectiveness**: Monitor if shakes help escape local optima + +## Common Patterns + +### Progressive Intensity + +```java +// Start with small perturbations, increase if stuck +int strength = 1; +while (!improved && strength < maxStrength) { + perturbed = shake(solution, strength); + improved = improver.improve(perturbed).isBetterThan(best); + strength++; +} +``` + +### Random Walk + +```java +// Pure random walk: always accept perturbed solution +for (int iter = 0; iter < maxIter; iter++) { + solution = shake(solution); + trackBest(solution); +} +``` + +## References + +[1] Hansen, P., & Mladenović, N. (2001). Variable neighborhood search: Principles and applications. *European Journal of Operational Research*, 130(3), 449-467. + +[2] Lourenço, H. R., Martin, O. C., & Stützle, T. (2003). Iterated local search. In *Handbook of Metaheuristics* (pp. 320-353). Springer. + +[3] Gendreau, M., & Potvin, J. Y. (Eds.). (2010). *Handbook of metaheuristics* (Vol. 2). Springer.