All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cicirello.search.ss.HybridConstructiveHeuristic Maven / Gradle / Ivy

Go to download

Chips-n-Salsa is a Java library of customizable, hybridizable, iterative, parallel, stochastic, and self-adaptive local search algorithms. The library includes implementations of several stochastic local search algorithms, including simulated annealing, hill climbers, as well as constructive search algorithms such as stochastic sampling. Chips-n-Salsa now also includes genetic algorithms as well as evolutionary algorithms more generally. The library very extensively supports simulated annealing. It includes several classes for representing solutions to a variety of optimization problems. For example, the library includes a BitVector class that implements vectors of bits, as well as classes for representing solutions to problems where we are searching for an optimal vector of integers or reals. For each of the built-in representations, the library provides the most common mutation operators for generating random neighbors of candidate solutions, as well as common crossover operators for use with evolutionary algorithms. Additionally, the library provides extensive support for permutation optimization problems, including implementations of many different mutation operators for permutations, and utilizing the efficiently implemented Permutation class of the JavaPermutationTools (JPT) library. Chips-n-Salsa is customizable, making extensive use of Java's generic types, enabling using the library to optimize other types of representations beyond what is provided in the library. It is hybridizable, providing support for integrating multiple forms of local search (e.g., using a hill climber on a solution generated by simulated annealing), creating hybrid mutation operators (e.g., local search using multiple mutation operators), as well as support for running more than one type of search for the same problem concurrently using multiple threads as a form of algorithm portfolio. Chips-n-Salsa is iterative, with support for multistart metaheuristics, including implementations of several restart schedules for varying the run lengths across the restarts. It also supports parallel execution of multiple instances of the same, or different, stochastic local search algorithms for an instance of a problem to accelerate the search process. The library supports self-adaptive search in a variety of ways, such as including implementations of adaptive annealing schedules for simulated annealing, such as the Modified Lam schedule, implementations of the simpler annealing schedules but which self-tune the initial temperature and other parameters, and restart schedules that adapt to run length.

There is a newer version: 7.0.1
Show newest version
/*
 * Chips-n-Salsa: A library of parallel self-adaptive local search algorithms.
 * Copyright (C) 2002-2022 Vincent A. Cicirello
 *
 * This file is part of Chips-n-Salsa (https://chips-n-salsa.cicirello.org/).
 *
 * Chips-n-Salsa is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Chips-n-Salsa is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */

package org.cicirello.search.ss;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import org.cicirello.math.rand.RandomIndexer;
import org.cicirello.search.problems.Problem;
import org.cicirello.util.Copyable;

/**
 * A HybridConstructiveHeuristic maintains a list of {@link ConstructiveHeuristic} objects for a
 * problem, for use in a multiheuristic stochastic sampling search, where each full iteration of the
 * stochastic sampler uses a single heuristic for all decisions, but where a different heuristic is
 * chosen for each iteration.
 *
 * 

The HybridConstructiveHeuristic supports the following heuristic selection strategies: * *

    *
  • Choose a heuristic uniformly at random at the start of the iteration. *
  • Use a round robin strategy that uses the heuristics in order as determined by the order * they were passed to the constructor, cycling around to the start of the list when * necessary. *
  • Choose a heuristic using a weighted random decision, where each heuristic has an associated * weight. For example, if the weight of heuristic 1 is 2 and the weight of heuristic 2 is 3, * then on average you can expect 2 out of every 5 iterations to use heuristic 1, and 3 out of * every 5 iterations to use heuristic 2. *
* *

See the documentation of the various constructors to make your choice of which of these * strategies to use. * * @param The type of Partial object for which this HybridConstructiveHeuristic guides * construction, which is assumed to be an object that is a sequence of integers (e.g., vector * of integers, permutation, or some other indexable type that stores integers). * @author Vincent A. Cicirello, https://www.cicirello.org/ */ public final class HybridConstructiveHeuristic> implements ConstructiveHeuristic { private final ArrayList> heuristics; private final int NUM_H; private final IntSupplier heuristicSelector; /** * Constructs the HybridConstructiveHeuristic, where the heuristic is chosen uniformly at random * at the start of each iteration of the stochastic sampler (i.e., each time {@link * #createIncrementalEvaluation} is called). * * @param heuristics A list of ConstructiveHeuristic, all of which must be configured to solve the * same problem instance. The list of heuristics must be non-empty. * @throws IllegalArgumentException if not all of the heuristics are configured for the same * problem instance. * @throws IllegalArgumentException if heuristics.size() equals 0. */ public HybridConstructiveHeuristic(List> heuristics) { this(heuristics, false); } /** * Constructs the HybridConstructiveHeuristic, where the heuristic is either chosen uniformly at * random at the start of each iteration of the stochastic sampler (i.e., each time {@link * #createIncrementalEvaluation} is called), or using the round robin strategy. * * @param heuristics A list of ConstructiveHeuristic, all of which must be configured to solve the * same problem instance. The list of heuristics must be non-empty. * @param roundRobin If true, then each time {@link #createIncrementalEvaluation} is called, the * HybridConstructiveHeuristic cycles to the next heuristic systematically. Otherwise, if * false, it chooses uniformly at random. * @throws IllegalArgumentException if not all of the heuristics are configured for the same * problem instance. * @throws IllegalArgumentException if heuristics.size() equals 0. */ public HybridConstructiveHeuristic( List> heuristics, boolean roundRobin) { this.heuristics = initializeHeuristics(heuristics); NUM_H = heuristics.size(); if (roundRobin) { heuristicSelector = new IntSupplier() { AtomicInteger lastHeuristic = new AtomicInteger(NUM_H - 1); @Override public int getAsInt() { return lastHeuristic.updateAndGet( (h) -> { h++; if (h == NUM_H) h = 0; return h; }); } }; } else { heuristicSelector = () -> RandomIndexer.nextBiasedInt(NUM_H); } } /** * Constructs the HybridConstructiveHeuristic, where the heuristic is chosen using a weighted * random decision at the start of each iteration of the stochastic sampler (i.e., each time * {@link #createIncrementalEvaluation} is called). If this constructor is used, it will choose a * heuristic using a weighted random decision, where each heuristic has an associated weight. For * example, if the weight of heuristic 1 is 2 and the weight of heuristic 2 is 3, then on average * you can expect 2 out of every 5 iterations to use heuristic 1, and 3 out of every 5 iterations * to use heuristic 2. * * @param heuristics A list of ConstructiveHeuristics, all of which must be configured to solve * the same problem instance. The list of heuristics must be non-empty. * @param weights An array of weights, which must be the same length as heuristics. Each weight * corresponds to the heuristic in the same position in the sequence. All weights must be * positive. * @throws IllegalArgumentException if not all of the heuristics are configured for the same * problem instance. * @throws IllegalArgumentException if heuristics.size() equals 0. * @throws IllegalArgumentException if heuristics.size() is not equal to weights.length. * @throws IllegalArgumentException if there exists an i, such that weights[i] < 1. */ public HybridConstructiveHeuristic( List> heuristics, int[] weights) { if (weights.length != heuristics.size()) { throw new IllegalArgumentException( "The number of weights must be the same as the number of heuristics."); } this.heuristics = initializeHeuristics(heuristics); NUM_H = weights.length; final int[] choice = weights.clone(); if (choice[0] <= 0) { throw new IllegalArgumentException("All weights must be positive."); } for (int i = 1; i < NUM_H; i++) { if (choice[i] <= 0) { throw new IllegalArgumentException("All weights must be positive."); } choice[i] += choice[i - 1]; } heuristicSelector = () -> { int which = Arrays.binarySearch(choice, RandomIndexer.nextInt(choice[NUM_H - 1])); return which < 0 ? -(which + 1) : which + 1; }; } private ArrayList> initializeHeuristics( List> heuristics) { if (heuristics.size() == 0) { throw new IllegalArgumentException("Must pass at least one heuristic."); } ConstructiveHeuristic first = null; for (ConstructiveHeuristic h : heuristics) { if (first == null) { first = h; } else if (h.getProblem() != first.getProblem()) { throw new IllegalArgumentException( "All heuristics must be configured for the same problem."); } } return new ArrayList>(heuristics); } /** * This method handles choosing the heuristic for the next iteration of the stochastic sampling * search, and then delegates the usual function of this method to the chosen heuristic. See the * {@link ConstructiveHeuristic} interface for full details of the functionality of this method. * * @return An IncrementalEvaluation for an empty Partial to be used for incrementally computing * any data required by the {@link #h} method. */ @Override public IncrementalEvaluation createIncrementalEvaluation() { int which = heuristicSelector.getAsInt(); IncrementalEvaluationWrapper wrapped = new IncrementalEvaluationWrapper( heuristics.get(which).createIncrementalEvaluation(), which); return wrapped; } @Override public double h(Partial p, int element, IncrementalEvaluation incEval) { IncrementalEvaluationWrapper wrapped = (IncrementalEvaluationWrapper) incEval; return heuristics.get(wrapped.which).h(p, element, wrapped.incEval); } @Override public Partial createPartial(int n) { return heuristics.get(0).createPartial(n); } @Override public int completeLength() { return heuristics.get(0).completeLength(); } @Override public Problem getProblem() { return heuristics.get(0).getProblem(); } private static class IncrementalEvaluationWrapper> implements IncrementalEvaluation { private final IncrementalEvaluation incEval; private final int which; /** * Constructs an IncrementalEvaluationWrapper. * * @param incEval The IncrementalEvaluation to wrap. * @param which The heuristic index to which incEval corresponds. */ public IncrementalEvaluationWrapper(IncrementalEvaluation incEval, int which) { this.incEval = incEval; this.which = which; } @Override public void extend(Partial p, int element) { incEval.extend(p, element); } } }