com.github.rinde.opt.localsearch.Swaps.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rinde.opt.localsearch.Swaps.java

Source

/*
 * Copyright (C) 2013-2016 Rinde van Lon, iMinds-DistriNet, KU Leuven
 *
 * 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 com.github.rinde.opt.localsearch;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newLinkedHashSet;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.commons.math3.random.RandomAdaptor;
import org.apache.commons.math3.random.RandomGenerator;

import com.github.rinde.opt.localsearch.Insertions.InsertionIndexGenerator;
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import com.google.common.collect.Range;

import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.doubles.DoubleLists;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.ints.IntLists;
import it.unimi.dsi.fastutil.objects.Object2DoubleLinkedOpenHashMap;

/**
 * Class for swap algorithms. Currently supports two variants of 2-opt:
 * <ul>
 * <li>Breadth-first 2-opt search:
 * {@link #bfsOpt2(ImmutableList, IntList, Object, RouteEvaluator, Optional)}.
 * </li>
 * <li>Depth-first 2-opt search:
 * {@link #dfsOpt2(ImmutableList, IntList, Object, RouteEvaluator, RandomGenerator,Optional)}
 * .</li>
 * </ul>
 * @author Rinde van Lon
 */
public final class Swaps {

    private static final int CACHE_SIZE = 1000;

    private Swaps() {
    }

    /**
     * 2-opt local search procedure for schedules. Performs breadth-first search
     * in 2-swap space, picks <i>best swap</i> and uses that as starting point for
     * next iteration. Stops as soon as there is no improving swap anymore. This
     * algorithm is deterministic on the input, that is, the method will have the
     * same result when provided with the same arguments.
     * @param schedule The schedule to improve.
     * @param startIndices Indices indicating which part of the schedule can be
     *          modified. <code>startIndices[j] = n</code> indicates that
     *          <code>schedule[j][n]</code> can be modified but
     *          <code>schedule[j][n-1]</code> not.
     * @param context The context to the schedule, used by the evaluator to
     *          compute the cost of a swap.
     * @param evaluator {@link RouteEvaluator} that can compute the cost of a
     *          single route.
     * @param <C> The context type.
     * @param <T> The route item type (i.e. the locations that are part of a
     *          route).
     * @param listener An optional progress listener. If provided,
     *          {@link ProgressListener#notify(ImmutableList, double)} will be
     *          called each time an <i>improving</i> schedule is found.
     * @return An improved schedule (or the input schedule if no improvement could
     *         be made).
     * @throws InterruptedException When execution is interrupted.
     */
    public static <C, T> ImmutableList<ImmutableList<T>> bfsOpt2(ImmutableList<ImmutableList<T>> schedule,
            IntList startIndices, C context, RouteEvaluator<C, T> evaluator,
            Optional<? extends ProgressListener<T>> listener) throws InterruptedException {
        return opt2(schedule, startIndices, context, evaluator, false, Optional.<RandomGenerator>absent(),
                listener);
    }

    /**
     * 2-opt local search procedure for schedules. Performs depth-first search in
     * 2-swap space, picks <i>first improving</i> (from random ordering of swaps)
     * swap and uses that as starting point for next iteration. Stops as soon as
     * there is no improving swap anymore.
     * @param schedule The schedule to improve.
     * @param startIndices Indices indicating which part of the schedule can be
     *          modified. <code>startIndices[j] = n</code> indicates that
     *          <code>schedule[j][n]</code> can be modified but
     *          <code>schedule[j][n-1]</code> not.
     * @param context The context to the schedule, used by the evaluator to
     *          compute the cost of a swap.
     * @param evaluator {@link RouteEvaluator} that can compute the cost of a
     *          single route.
     * @param rng The random number generator that is used to randomize the
     *          ordering of the swaps.
     * @param <C> The context type.
     * @param <T> The route item type (i.e. the locations that are part of a
     *          route).
     * @param listener An optional progress listener. If provided,
     *          {@link ProgressListener#notify(ImmutableList, double)} will be
     *          called each time an <i>improving</i> schedule is found.
     * @return An improved schedule (or the input schedule if no improvement could
     *         be made).
     * @throws InterruptedException When execution is interrupted.
     */
    public static <C, T> ImmutableList<ImmutableList<T>> dfsOpt2(ImmutableList<ImmutableList<T>> schedule,
            IntList startIndices, C context, RouteEvaluator<C, T> evaluator, RandomGenerator rng,
            Optional<? extends ProgressListener<T>> listener) throws InterruptedException {
        return opt2(schedule, startIndices, context, evaluator, true, Optional.of(rng), listener);
    }

    static <C, T> ImmutableList<ImmutableList<T>> opt2(ImmutableList<ImmutableList<T>> schedule,
            IntList startIndices, C context, RouteEvaluator<C, T> evaluator, boolean depthFirst,
            Optional<RandomGenerator> rng, Optional<? extends ProgressListener<T>> listener)
            throws InterruptedException {

        checkArgument(schedule.size() == startIndices.size());

        final Schedule<C, T> baseSchedule = Schedule.create(context, schedule, startIndices, evaluator);

        final Object2DoubleLinkedOpenHashMap<ImmutableList<T>> routeCostCache = new Object2DoubleLinkedOpenHashMap<>(
                CACHE_SIZE);

        for (int i = 0; i < baseSchedule.routes.size(); i++) {
            routeCostCache.put(baseSchedule.routes.get(i), baseSchedule.objectiveValues.getDouble(i));
        }

        Schedule<C, T> bestSchedule = baseSchedule;
        boolean isImproving = true;
        while (isImproving) {
            isImproving = false;

            final Schedule<C, T> curBest = bestSchedule;
            Iterator<Swap<T>> it = swapIterator(curBest);
            if (depthFirst) {
                // randomize ordering of swaps
                final List<Swap<T>> swaps = newArrayList(it);
                Collections.shuffle(swaps, new RandomAdaptor(rng.get()));
                it = swaps.iterator();
            }

            while (it.hasNext()) {
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                final Swap<T> swapOperation = it.next();
                final Optional<Schedule<C, T>> newSchedule = swap(curBest, swapOperation,
                        bestSchedule.objectiveValue - curBest.objectiveValue, routeCostCache);

                if (newSchedule.isPresent()) {
                    isImproving = true;
                    bestSchedule = newSchedule.get();

                    if (listener.isPresent()) {
                        listener.get().notify(bestSchedule.routes, bestSchedule.objectiveValue);
                    }
                    if (depthFirst) {
                        // first improving swap is chosen as new starting point (depth
                        // first).
                        break;
                    }
                }
            }
        }
        return bestSchedule.routes;
    }

    static <C, T> Iterator<Swap<T>> swapIterator(Schedule<C, T> schedule) {
        final ImmutableList.Builder<Iterator<Swap<T>>> iteratorBuilder = ImmutableList.builder();
        final Set<T> seen = newLinkedHashSet();
        for (int i = 0; i < schedule.routes.size(); i++) {
            final ImmutableList<T> row = schedule.routes.get(i);
            for (int j = 0; j < row.size(); j++) {
                final T t = row.get(j);
                if (j >= schedule.startIndices.getInt(i) && !seen.contains(t)) {
                    iteratorBuilder.add(oneItemSwapIterator(schedule, schedule.startIndices, t, i));
                }
                seen.add(t);
            }
        }
        return Iterators.concat(iteratorBuilder.build().iterator());
    }

    static <C, T> Iterator<Swap<T>> oneItemSwapIterator(Schedule<C, T> schedule, IntList startIndices, T item,
            int fromRow) {
        final IntList indices = indices(schedule.routes.get(fromRow), item);
        final ImmutableList.Builder<Iterator<Swap<T>>> iteratorBuilder = ImmutableList.builder();

        Range<Integer> range;
        if (indices.size() == 1) {
            range = Range.closedOpen(fromRow, fromRow + 1);
        } else {
            range = Range.closedOpen(0, schedule.routes.size());
        }

        for (int i = range.lowerEndpoint(); i < range.upperEndpoint(); i++) {
            int rowSize = schedule.routes.get(i).size();
            if (fromRow == i) {
                rowSize -= indices.size();
            }
            Iterator<IntList> it = new InsertionIndexGenerator(indices.size(), rowSize, startIndices.getInt(i));
            // filter out swaps that have existing result
            if (fromRow == i) {
                it = Iterators.filter(it, Predicates.not(Predicates.equalTo(indices)));
            }
            iteratorBuilder.add(Iterators.transform(it, new IndexToSwapTransform<T>(item, fromRow, i)));
        }
        return Iterators.concat(iteratorBuilder.build().iterator());
    }

    static <C, T> Optional<Schedule<C, T>> swap(Schedule<C, T> s, Swap<T> swap, double threshold) {
        return swap(s, swap, threshold, new Object2DoubleLinkedOpenHashMap<ImmutableList<T>>());
    }

    /**
     * Swap an item from <code>fromRow</code> to <code>toRow</code>. All
     * occurrences are removed from <code>fromRow</code> and will be added in
     * <code>toRow</code> at the specified indices. The modified schedule is only
     * returned if it improves over the specified <code>threshold</code> value.
     * The quality of a schedule is determined by its {@link Schedule#evaluator}.
     *
     * @param s The schedule to perform the swap on.
     * @param itemToSwap The item to swap.
     * @param fromRow The originating row of the item.
     * @param toRow The destination row for the item.
     * @param insertionIndices The indices where the item will be inserted in the
     *          new row. The number of indices must equal the number of
     *          occurrences of item in the <code>fromRow</code>. If
     *          <code>fromRow == toRow</code> the insertion indices point to the
     *          indices of the row <b>without</b> the original item in it.
     * @param threshold The threshold value which decides whether a schedule is
     *          returned.
     * @return The swapped schedule if the cost of the new schedule is better
     *         (lower) than the threshold, {@link Optional#absent()} otherwise.
     */
    static <C, T> Optional<Schedule<C, T>> swap(Schedule<C, T> s, Swap<T> swap, double threshold,
            Object2DoubleLinkedOpenHashMap<ImmutableList<T>> cache) {

        checkArgument(swap.fromRow() >= 0 && swap.fromRow() < s.routes.size(),
                "fromRow must be >= 0 and < %s, it is %s.", s.routes.size(), swap.fromRow());
        checkArgument(swap.toRow() >= 0 && swap.toRow() < s.routes.size(), "toRow must be >= 0 and < %s, it is %s.",
                s.routes.size(), swap.toRow());

        if (swap.fromRow() == swap.toRow()) {
            // 1. swap within same vehicle
            // compute cost of original ordering
            // compute cost of new ordering
            final double originalCost = s.objectiveValues.getDouble(swap.fromRow());
            final ImmutableList<T> newRoute = inListSwap(s.routes.get(swap.fromRow()), swap.toIndices(),
                    swap.item());

            final double newCost = computeCost(s, swap.fromRow(), newRoute, cache);
            final double diff = newCost - originalCost;

            if (diff < threshold) {
                // it improves
                final ImmutableList<ImmutableList<T>> newRoutes = replace(s.routes, asIntList(swap.fromRow()),
                        ImmutableList.of(newRoute));
                final double newObjectiveValue = s.objectiveValue + diff;
                final DoubleList newObjectiveValues = replace(s.objectiveValues, asIntList(swap.fromRow()),
                        asDoubleList(newCost));
                return Optional.of(Schedule.create(s.context, newRoutes, s.startIndices, newObjectiveValues,
                        newObjectiveValue, s.evaluator));
            } else {
                return Optional.absent();
            }
        } else {
            // 2. swap between vehicles

            // compute cost of removal from original vehicle
            final double originalCostA = s.objectiveValues.getDouble(swap.fromRow());
            final ImmutableList<T> newRouteA = ImmutableList
                    .copyOf(filter(s.routes.get(swap.fromRow()), not(equalTo(swap.item()))));
            final int itemCount = s.routes.get(swap.fromRow()).size() - newRouteA.size();
            checkArgument(itemCount > 0,
                    "The item (%s) is not in row %s, hence it cannot be swapped to another " + "row.", swap.item(),
                    swap.fromRow());
            checkArgument(itemCount == swap.toIndices().size(),
                    "The number of occurences in the fromRow (%s) should equal the number "
                            + "of insertion indices (%s).",
                    itemCount, swap.toIndices().size());

            final double newCostA = computeCost(s, swap.fromRow(), newRouteA, cache);
            final double diffA = newCostA - originalCostA;

            // compute cost of insertion in new vehicle
            final double originalCostB = s.objectiveValues.getDouble(swap.toRow());
            final ImmutableList<T> newRouteB = Insertions.insert(s.routes.get(swap.toRow()), swap.toIndices(),
                    swap.item());

            final double newCostB = computeCost(s, swap.toRow(), newRouteB, cache);
            final double diffB = newCostB - originalCostB;

            final double diff = diffA + diffB;
            if (diff < threshold) {
                final IntList rows = asIntList(swap.fromRow(), swap.toRow());
                final ImmutableList<ImmutableList<T>> newRoutes = replace(s.routes, rows,
                        ImmutableList.of(newRouteA, newRouteB));
                final double newObjectiveValue = s.objectiveValue + diff;
                final DoubleList newObjectiveValues = replace(s.objectiveValues, rows,
                        asDoubleList(newCostA, newCostB));

                return Optional.of(Schedule.create(s.context, newRoutes, s.startIndices, newObjectiveValues,
                        newObjectiveValue, s.evaluator));
            } else {
                return Optional.absent();
            }
        }
    }

    static IntList asIntList(final int... values) {
        return IntLists.unmodifiable(new IntArrayList(values));
    }

    static DoubleList asDoubleList(double... values) {
        return DoubleLists.unmodifiable(new DoubleArrayList(values));
    }

    static <C, T> double computeCost(Schedule<C, T> s, int row, ImmutableList<T> newRoute,
            Object2DoubleLinkedOpenHashMap<ImmutableList<T>> cache) {
        if (cache.containsKey(newRoute)) {
            return cache.getAndMoveToFirst(newRoute);
        }
        final double newCost = s.evaluator.computeCost(s.context, row, newRoute);
        cache.putAndMoveToFirst(newRoute, newCost);
        if (cache.size() > CACHE_SIZE) {
            cache.removeLastDouble();
        }
        return newCost;
    }

    /**
     * Moves the occurrences of <code>item</code> to their new positions. This
     * does not change the relative ordering of any other items in the list.
     * @param originalList The original list that will be swapped.
     * @param insertionIndices The indices where item should be inserted relative
     *          to the positions of the <code>originalList</code> <b>without</b>
     *          <code>item</code>. The number of indices must equal the number of
     *          occurrences of item in the original list.
     * @param item The item to swap.
     * @return The swapped list.
     * @throws IllegalArgumentException if an attempt is made to move the item to
     *           the previous location(s), this would have no effect and is
     *           therefore considered a bug.
     */
    static <T> ImmutableList<T> inListSwap(ImmutableList<T> originalList, IntList insertionIndices, T item) {
        checkArgument(!originalList.isEmpty(), "The list may not be empty.");
        final List<T> newList = newArrayList(originalList);
        final IntList indices = removeAll(newList, item);
        checkArgument(newList.size() == originalList.size() - insertionIndices.size(),
                "The number of occurrences (%s) of item should equal the number of "
                        + "insertionIndices (%s), original list: %s, item %s, " + "insertionIndices %s.",
                indices.size(), insertionIndices.size(), originalList, item, insertionIndices);
        checkArgument(!indices.equals(insertionIndices),
                "Attempt to move the item to exactly the same locations as the input. "
                        + "Indices in original list %s, insertion indices %s.",
                indices, insertionIndices);
        return Insertions.insert(newList, insertionIndices, item);
    }

    /**
     * Removes all items from list and returns the indices of the removed items.
     * @param list The list to remove items from.
     * @param item The item to remove from the list.
     * @return The indices of the removed items, or an empty list if the item was
     *         not found in list.
     */
    static <T> IntList removeAll(List<T> list, T item) {
        final Iterator<T> it = list.iterator();
        final IntArrayList indices = new IntArrayList();
        int i = 0;
        while (it.hasNext()) {
            if (it.next().equals(item)) {
                it.remove();
                indices.add(i);
            }
            i++;
        }
        return IntLists.unmodifiable(indices);
    }

    /**
     * Finds all indices of item in the specified list.
     * @param list The list.
     * @param item The item.
     * @return A list of indices.
     */
    static <T> IntList indices(List<T> list, T item) {
        final IntList indices = new IntArrayList();
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals(item)) {
                indices.add(i);
            }
        }
        return IntLists.unmodifiable(indices);
    }

    static <T> void checkIndices(IntList indices, List<T> elements) {
        checkArgument(indices.size() == elements.size(),
                "Number of indices (%s) must equal number of elements (%s).", indices.size(), elements.size());
    }

    static <T> ImmutableList<T> replace(ImmutableList<T> list, IntList indices, ImmutableList<T> elements) {
        checkIndices(indices, elements);
        final List<T> newL = newArrayList(list);
        for (int i = 0; i < indices.size(); i++) {
            newL.set(indices.getInt(i), elements.get(i));
        }
        return ImmutableList.copyOf(newL);
    }

    static DoubleList replace(DoubleList list, IntList indices, DoubleList elements) {
        checkIndices(indices, elements);
        final DoubleList newL = new DoubleArrayList(list);
        for (int i = 0; i < indices.size(); i++) {
            newL.set(indices.getInt(i), elements.getDouble(i));
        }
        return DoubleLists.unmodifiable(newL);
    }

    @AutoValue
    abstract static class Swap<T> {
        abstract T item();

        abstract int fromRow();

        abstract int toRow();

        abstract IntList toIndices();

        static <T> Swap<T> create(T i, int from, int to, IntList toInd) {
            return new AutoValue_Swaps_Swap<T>(i, from, to, toInd);
        }
    }

    static class IndexToSwapTransform<T> implements Function<IntList, Swap<T>> {
        private final T item;
        private final int fromRow;
        private final int toRow;

        IndexToSwapTransform(T it, int from, int to) {
            item = it;
            fromRow = from;
            toRow = to;
        }

        @Nullable
        @Override
        public Swap<T> apply(@Nullable IntList input) {
            return Swap.create(item, fromRow, toRow, checkNotNull(input));
        }
    }
}