/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.mahout.ga.watchmaker.travellingsalesman; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Random; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import org.apache.mahout.common.RandomUtils; import org.apache.mahout.ga.watchmaker.MahoutFitnessEvaluator; import org.uncommons.maths.random.PoissonGenerator; import org.uncommons.watchmaker.framework.CandidateFactory; import org.uncommons.watchmaker.framework.ConcurrentEvolutionEngine; import org.uncommons.watchmaker.framework.EvolutionEngine; import org.uncommons.watchmaker.framework.EvolutionObserver; import org.uncommons.watchmaker.framework.EvolutionaryOperator; import org.uncommons.watchmaker.framework.FitnessEvaluator; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.SelectionStrategy; import org.uncommons.watchmaker.framework.SequentialEvolutionEngine; import org.uncommons.watchmaker.framework.factories.ListPermutationFactory; import org.uncommons.watchmaker.framework.operators.EvolutionPipeline; import org.uncommons.watchmaker.framework.operators.ListOrderCrossover; import org.uncommons.watchmaker.framework.operators.ListOrderMutation; import org.uncommons.watchmaker.framework.termination.GenerationCount; /** * Evolutionary algorithm for finding (approximate) solutions to the travelling salesman problem. * * * <br> * The original code is from <b>the Watchmaker project</b> (https://watchmaker.dev.java.net/).<br> * Modified to use Mahout whenever requested. */ public class EvolutionaryTravellingSalesman implements TravellingSalesmanStrategy { private final DistanceLookup distances; private final SelectionStrategy<? super List<String>> selectionStrategy; private final int populationSize; private final int eliteCount; private final int generationCount; private final boolean crossover; private final boolean mutation; private final boolean mahout; /** * Creates an evolutionary Travelling Salesman solver with the specified configuration. * * @param distances * Information about the distances between cities. * @param selectionStrategy * The selection implementation to use for the evolutionary algorithm. * @param populationSize * The number of candidates in the population of evolved routes. * @param eliteCount * The number of candidates to preserve via elitism at each generation. * @param generationCount * The number of iterations of evolution to perform. * @param crossover * Whether or not to use a cross-over operator in the evolution. * @param mutation * Whether or not to use a mutation operator in the evolution. * @param mahout * Whether or not to use Mahout for evaluation. */ public EvolutionaryTravellingSalesman(DistanceLookup distances, SelectionStrategy<? super List<String>> selectionStrategy, int populationSize, int eliteCount, int generationCount, boolean crossover, boolean mutation, boolean mahout) { Preconditions.checkArgument(eliteCount >= 0 && eliteCount < populationSize, "Elite count must be non-zero and less than population size."); Preconditions.checkArgument(crossover || mutation, "At least one of cross-over or mutation must be selected."); this.distances = distances; this.selectionStrategy = selectionStrategy; this.populationSize = populationSize; this.eliteCount = eliteCount; this.generationCount = generationCount; this.crossover = crossover; this.mutation = mutation; this.mahout = mahout; } @Override public String getDescription() { String selectionName = selectionStrategy.getClass().getSimpleName(); return (mahout ? "Mahout " : "") + "Evolution (pop: " + populationSize + ", gen: " + generationCount + ", elite: " + eliteCount + ", " + selectionName + ')'; } /** * Calculates the shortest route using a generational evolutionary algorithm with a single ordered mutation * operator and truncation selection. * * @param cities * The list of destinations, each of which must be visited once. * @param progressListener * Call-back for receiving the status of the algorithm as it progresses. * @return The (approximate) shortest route that visits each of the specified cities once. */ @Override public List<String> calculateShortestRoute(Collection<String> cities, final ProgressListener progressListener) { Random rng = RandomUtils.getRandom(); // Set-up evolution pipeline (cross-over followed by mutation). List<EvolutionaryOperator<List<String>>> operators = Lists.newArrayListWithCapacity(2); if (crossover) { operators.add(new ListOrderCrossover()); } if (mutation) { operators.add(new ListOrderMutation(new PoissonGenerator(1.5, rng), new PoissonGenerator(1.5, rng))); } EvolutionaryOperator<List<String>> pipeline = new EvolutionPipeline<List<String>>(operators); CandidateFactory<List<String>> candidateFactory = new ListPermutationFactory<String>( new LinkedList<String>(cities)); EvolutionEngine<List<String>> engine = getEngine(candidateFactory, pipeline, rng); engine.addEvolutionObserver(new EvolutionObserver<List<String>>() { @Override public void populationUpdate(PopulationData<? extends List<String>> data) { if (progressListener != null) { progressListener.updateProgress(((double) data.getGenerationNumber() + 1) / generationCount * 100); } } }); return engine.evolve(populationSize, eliteCount, new GenerationCount(generationCount)); } private EvolutionEngine<List<String>> getEngine(CandidateFactory<List<String>> candidateFactory, EvolutionaryOperator<List<String>> pipeline, Random rng) { if (mahout) { // This is what we need to do to distribute the fitness evaluation. // First create a STFitnessEvaluator that wraps our FitnessEvaluator FitnessEvaluator<? super List<String>> evaluator = new MahoutFitnessEvaluator<List<String>>( new RouteEvaluator(distances)); // Then use a SequentialEvolutionEngine instead of a StandaloneEvolutionEngine. // Its parameters remain the same. return new SequentialEvolutionEngine<List<String>>(candidateFactory, pipeline, evaluator, selectionStrategy, rng); } else { return new ConcurrentEvolutionEngine<List<String>>(candidateFactory, pipeline, new RouteEvaluator( distances), selectionStrategy, rng); } } }