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

org.jamesframework.core.search.neigh.subset.SinglePerturbationNeighbourhood Maven / Gradle / Ivy

Go to download

The James core module is part of the James framework for optimization using local search metaheuristics in Java. The core contains general components to model problems, objectives and constraints, as well as generic algorithms to solve the problems. Moreover, the core provides implementations of specific utilities for subset selection.

There is a newer version: 1.2
Show newest version
//  Copyright 2014 Herman De Beukelaer
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

package org.jamesframework.core.search.neigh.subset;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import org.jamesframework.core.problems.solutions.SubsetSolution;
import org.jamesframework.core.search.neigh.Move;
import org.jamesframework.core.search.neigh.Neighbourhood;
import org.jamesframework.core.util.RouletteSelector;
import org.jamesframework.core.util.SetUtilities;

/**
 * 

* A subset neighbourhood that generates swap moves (see {@link SwapMove}), addition moves (see {@link AdditionMove}) and deletion * moves (see {@link DeletionMove}). Applying an addition or deletion move to a given subset solution will increase, respectively * decrease, the number of selected items. Therefore, this neighbourhood is also suited for variable size subset selection problems, * in contrast to {@link SingleSwapNeighbourhood}, which can only handle fixed size subset problems. *

*

* A single perturbation neighbourhood respects the minimum and maximum subset size specified at construction. When the given * subset solution has minimal size, no deletion moves will be generated. Similarly, when the current solution has maximum size, no * addition moves will be generated. *

*

* If desired, a set of fixed IDs can be provided which are not allowed to be added, deleted nor swapped. None of the moves * generated by this neighbourhood will ever select nor deselect any of the provided fixed IDs. *

*

* Note that this neighbourhood is thread-safe: it can be safely used to concurrently generate moves in different searches running * in separate threads. *

* * @author Herman De Beukelaer */ public class SinglePerturbationNeighbourhood implements Neighbourhood { // move type enum used for randomly picking a move type private enum MoveType { ADDITION, DELETION, SWAP; } // minimum and maximum subset size private final int minSubsetSize; private final int maxSubsetSize; // set of fixed IDs private final Set fixedIDs; /** * Creates a new single perturbation neighbourhood with given minimum and maximum subset size. * Only moves that result in a valid solution size after application to the current solution * will ever be generated. Positive values are required for the minimum and maximum size, * with minimum smaller than or equal to maximum; else, an exception is thrown. * * @throws IllegalArgumentException if minimum and maximum size are not both positive, or minimum > maximum * @param minSubsetSize minimum subset size * @param maxSubsetSize maximum subset size */ public SinglePerturbationNeighbourhood(int minSubsetSize, int maxSubsetSize){ this(minSubsetSize, maxSubsetSize, null); } /** * Creates a new single perturbation neighbourhood with given minimum and maximum subset size, * providing a set of fixed IDs which are not allowed to be added, deleted nor swapped. * Only moves that result in a valid solution size after application to the current solution * will ever be generated. Positive values are required for the minimum and maximum size, * with minimum smaller than or equal to maximum; else, an exception is thrown. * * @throws IllegalArgumentException if minimum and maximum size are not both positive, or minimum > maximum * @param minSubsetSize minimum subset size * @param maxSubsetSize maximum subset size * @param fixedIDs set of fixed IDs */ public SinglePerturbationNeighbourhood(int minSubsetSize, int maxSubsetSize, Set fixedIDs){ // validate sizes if(minSubsetSize < 0){ throw new IllegalArgumentException("Error while creating single perturbation neighbourhood: minimum subset size should be non-negative."); } if(maxSubsetSize < 0){ throw new IllegalArgumentException("Error while creating single perturbation neighbourhood: maximum subset size should be non-negative."); } if(minSubsetSize > maxSubsetSize){ throw new IllegalArgumentException("Error while creating single perturbation neighbourhood: " + "minimum subset size should be smaller than or equal to maximum subset size."); } this.minSubsetSize = minSubsetSize; this.maxSubsetSize = maxSubsetSize; this.fixedIDs = fixedIDs; } /** *

* Generates a random swap, deletion or addition move that transforms the given subset solution into * a neighbour within the minimum and maximum allowed subset size. If no valid move can be generated, * null is returned. If any fixed IDs have been specified, these will not be considered * for deletion nor addition. *

*

* Note that every individual move is generated with equal probability, taking into account the possibly * different number of possible moves of each type. *

* * @param solution solution for which a random move is generated * @return random move, null if no valid move can be generated */ @Override public Move getRandomMove(SubsetSolution solution) { // use thread local random for concurrent performance Random rg = ThreadLocalRandom.current(); // get set of candidate IDs for deletion and addition Set deleteCandidates = solution.getSelectedIDs(); Set addCandidates = solution.getUnselectedIDs(); // remove fixed IDs, if any, from candidates if(fixedIDs != null && !fixedIDs.isEmpty()){ deleteCandidates = new HashSet<>(deleteCandidates); addCandidates = new HashSet<>(addCandidates); deleteCandidates.removeAll(fixedIDs); addCandidates.removeAll(fixedIDs); } // compute number of possible moves of each type (addition, deletion, swap) int numAdd = numValidAdditionMoves(solution, addCandidates); int numDel = numValidDeletionMoves(solution, deleteCandidates); int numSwap = numValidSwapMoves(solution, addCandidates, deleteCandidates); // pick move type using roulette selector MoveType selectedMoveType = new RouletteSelector(rg).select( Arrays.asList(MoveType.ADDITION, MoveType.DELETION, MoveType.SWAP), Arrays.asList((double) numAdd, (double) numDel, (double) numSwap) ); // in case of no valid moves: return null if(selectedMoveType == null){ return null; } else { // generate random move of chosen type switch(selectedMoveType){ case ADDITION : return new AdditionMove(SetUtilities.getRandomElement(addCandidates, rg)); case DELETION : return new DeletionMove(SetUtilities.getRandomElement(deleteCandidates, rg)); case SWAP : return new SwapMove( SetUtilities.getRandomElement(addCandidates, rg), SetUtilities.getRandomElement(deleteCandidates, rg) ); default : throw new Error("This should never happen. If this exception is thrown, " + "there is a serious bug in SinglePerturbationNeighbourhood."); } } } /** * Generate all valid swap, deletion and addition moves that transform the given subset solution into * a neighbour within the minimum and maximum allowed subset size. The returned set may be empty, * if no valid moves exist. If any fixed IDs have been specified, these will not be considered * for deletion nor addition. * * @param solution solution for which a set of all valid moves is generated * @return set of all valid swap, deletion and addition moves */ @Override public Set> getAllMoves(SubsetSolution solution) { // get set of candidate IDs for deletion and addition Set deleteCandidates = solution.getSelectedIDs(); Set addCandidates = solution.getUnselectedIDs(); // remove fixed IDs, if any, from candidates if(fixedIDs != null && !fixedIDs.isEmpty()){ deleteCandidates = new HashSet<>(deleteCandidates); addCandidates = new HashSet<>(addCandidates); deleteCandidates.removeAll(fixedIDs); addCandidates.removeAll(fixedIDs); } // create empty set of moves Set> moves = new HashSet<>(); // generate all addition moves, if valid if(numValidAdditionMoves(solution, addCandidates) > 0){ // go through candidate IDs for addition for(int ID : addCandidates){ // create addition move that adds this ID moves.add(new AdditionMove(ID)); } } // generate all deletion moves, if valid if(numValidDeletionMoves(solution, deleteCandidates) > 0){ // go through candidate IDs for deletion for(int ID : deleteCandidates){ // create deletion move that deletes this ID moves.add(new DeletionMove(ID)); } } // generate all swap moves, if valid if(numValidSwapMoves(solution, addCandidates, deleteCandidates) > 0){ // go through candidates for addition for(int add : addCandidates){ // go through candidates for deletion for(int del : deleteCandidates){ // create corresponding swap move moves.add(new SwapMove(add, del)); } } } // return generated moves return moves; } /** * Compute the number of possible addition moves that yield a neighbour * within the valid subset size range, for the given solution. * * @param solution solution for which moves are generated * @param addCandidates set of candidate IDs to be added * @return number of possible addition moves */ private int numValidAdditionMoves(SubsetSolution solution, Set addCandidates){ return isValidSubsetSize(solution.getNumSelectedIDs()+1) ? addCandidates.size() : 0; } /** * Compute the number of possible deletion moves that yield a neighbour * within the valid subset size range, for the given solution. * * @param solution solution for which moves are generated * @param deleteCandidates set of candidate IDs to be deleted * @return number of possible deletion moves */ private int numValidDeletionMoves(SubsetSolution solution, Set deleteCandidates){ return isValidSubsetSize(solution.getNumSelectedIDs()-1) ? deleteCandidates.size() : 0; } /** * Compute the number of possible swap moves that yield a neighbour * within the valid subset size range, for the given solution. * * @param solution solution for which moves are generated * @param addCandidates set of candidate IDs to be added * @param deleteCandidates set of candidate IDs to be deleted * @return number of possible swap moves */ private int numValidSwapMoves(SubsetSolution solution, Set addCandidates, Set deleteCandidates){ return isValidSubsetSize(solution.getNumSelectedIDs()) ? addCandidates.size()*deleteCandidates.size() : 0; } /** * Verifies whether the given subset size is valid, taking into account * the minimum and maximum size specified at construction. * * @param size size to verify * @return true if size falls within bounds */ private boolean isValidSubsetSize(int size){ return size >= minSubsetSize && size <= maxSubsetSize; } /** * Get the minimum subset size specified at construction. * * @return minimum subset size */ public int getMinSubsetSize() { return minSubsetSize; } /** * Get the maximum subset size specified at construction. * * @return maximum subset size */ public int getMaxSubsetSize() { return maxSubsetSize; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy