com.github.rinde.rinsim.central.arrays.ArraysSolverValidator.java Source code

Java tutorial

Introduction

Here is the source code for com.github.rinde.rinsim.central.arrays.ArraysSolverValidator.java

Source

/*
 * Copyright (C) 2011-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.rinsim.central.arrays;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

import com.google.common.collect.ContiguousSet;
import com.google.common.collect.DiscreteDomain;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;

/**
 * Provides methods for validating input to and output from
 * {@link SingleVehicleArraysSolver}s and {@link MultiVehicleArraysSolver}. Also
 * provides <code>wrap(..)</code> methods which decorates any solver such that
 * both inputs and outputs are validated every time <code>solve(..)</code> is
 * called.
 * @author Rinde van Lon
 */
public final class ArraysSolverValidator {

    private ArraysSolverValidator() {
    }

    /**
     * Decorates the original {@link SingleVehicleArraysSolver} such that both the
     * inputs to the solver and the outputs from the solver are validated. When an
     * invalid input or output is detected a {@link IllegalArgumentException is
     * thrown}.
     * @param delegate The {@link SingleVehicleArraysSolver} that will be used for
     *          the actual solving.
     * @return The wrapped solver.
     */
    public static SingleVehicleArraysSolver wrap(SingleVehicleArraysSolver delegate) {
        return new SingleValidator(delegate);
    }

    /**
     * Decorates the original {@link MultiVehicleArraysSolver} such that both the
     * inputs to the solver and the outputs from the solver are validated. When an
     * invalid input or output is detected an {@link IllegalArgumentException} is
     * thrown.
     * @param delegate The {@link MultiVehicleArraysSolver} that will be used for
     *          the actual solving.
     * @return The wrapped solver.
     */
    public static MultiVehicleArraysSolver wrap(MultiVehicleArraysSolver delegate) {
        return new MultiValidator(delegate);
    }

    /**
     * Validates the inputs for the {@link SingleVehicleArraysSolver}. This method
     * checks all properties as defined in
     * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     * . If the inputs are not correct an {@link IllegalArgumentException} is
     * thrown.
     * @param travelTime Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param releaseDates Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param dueDates Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param servicePairs Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param serviceTimes Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     */
    public static void validateInputs(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
            int[] serviceTimes) {

        final int n = travelTime.length;
        checkArgument(n > 0, "Travel time matrix cannot be empty");
        // check that matrix is n x n
        for (int i = 0; i < n; i++) {
            checkArgument(travelTime[i].length == n, "row %s has invalid length %s", i, travelTime[i].length);
        }
        checkArgument(releaseDates.length == n, "ReleaseDates array has incorrect length (%s) should be %s",
                releaseDates.length, n);
        checkArgument(dueDates.length == n, "dueDates array has incorrect length (%s) should be %s",
                dueDates.length, n);
        checkArgument(serviceTimes.length == n, "serviceTimes has incorrect length (%s) should be %s",
                serviceTimes.length, n);
        for (int i = 0; i < n; i++) {
            if (i == 0 || i == n - 1) {
                checkArgument(serviceTimes[i] == 0, "first and last index in serviceTimes should be 0");
            } else {
                checkArgument(serviceTimes[i] >= 0, "serviceTimes should be >= 0");
            }
        }

        // check time windows validity
        for (int i = 0; i < n; i++) {
            checkArgument(releaseDates[i] <= dueDates[i],
                    "Index %s, release date (%s) should always be before the due date " + "(%s)", i,
                    releaseDates[i], dueDates[i]);
        }

        checkArgument(releaseDates[0] == 0 && dueDates[0] == 0,
                "Start location should have release date and due date 0");
        // checkArgument(releaseDates[n - 1] == 0,
        // "Depot should have release date 0");

        // check that every pair consists of valid ids and that a location is in
        // only one pair
        final Set<Integer> set = newHashSet();
        for (int i = 0; i < servicePairs.length; i++) {
            checkArgument(servicePairs[i].length == 2, "Each pair entry should consist of exactly two locations.");
            for (int j = 0; j < 2; j++) {
                checkArgument(servicePairs[i][j] > 0 && servicePairs[i][j] < n - 1,
                        "Pair consists of an invalid location (start location and depot are"
                                + " not allowed), index is %s, location is %s",
                        i, servicePairs[i][j]);
                checkArgument(!set.contains(servicePairs[i][j]),
                        "Location can be part of only one pair, duplicate location: %s " + "(index %s,%s)",
                        servicePairs[i][j], i, j);
                set.add(servicePairs[i][j]);
            }
        }
    }

    /**
     * Validates the inputs for the {@link MultiVehicleArraysSolver}. This method
     * checks all properties as defined in
     * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     * . If the inputs are not correct an {@link IllegalArgumentException} is
     * thrown.
     * @param travelTime Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param releaseDates Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param dueDates Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param servicePairs Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param serviceTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param vehicleTravelTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param inventories Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param remainingServiceTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param currentDestinations Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param currentSolutions Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     */
    public static void validateInputs(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
            int[] serviceTimes, int[][] vehicleTravelTimes, int[][] inventories, int[] remainingServiceTimes,
            int[] currentDestinations, @Nullable SolutionObject[] currentSolutions) {

        validateInputs(travelTime, releaseDates, dueDates, servicePairs, serviceTimes);

        // number of vehicles v
        final int v = vehicleTravelTimes.length;
        final int n = travelTime.length;
        checkArgument(v > 0, "At least one vehicle is required.");

        checkArgument(v == remainingServiceTimes.length,
                "Expected a remainingServiceTimes array of size %s, but found one with " + "size %s.", v,
                remainingServiceTimes.length);
        checkArgument(currentDestinations.length == v,
                "The currentDestinations array should be of length v=%s, it is %s.", v, currentDestinations.length);

        validateVehicleTravelTimes(v, n, vehicleTravelTimes, currentDestinations);

        final ImmutableSet.Builder<Integer> b = ImmutableSet.builder();
        for (int i = 0; i < servicePairs.length; i++) {
            b.add(servicePairs[i][0]);
            b.add(servicePairs[i][1]);
        }
        final Set<Integer> availLocs = b.build();

        final int m = n - 2 - servicePairs.length * 2;
        checkArgument(inventories.length == m, "Invalid number of inventory entries, must be equal to number of "
                + "delivery locations: %s, found: %s.", m, servicePairs.length);

        final Multimap<Integer, Integer> inventoriesMap = HashMultimap.create();
        final Set<Integer> parcelsInInventory = newHashSet();
        for (int i = 0; i < m; i++) {
            checkArgument(2 == inventories[i].length,
                    "We expected inventories matrix of size m x 2, but we found m x %s " + "at index %s.",
                    inventories[i].length, i);
            checkArgument(inventories[i][0] >= 0 && inventories[i][0] < v,
                    "Found a reference to a non-existing vehicle (%s) in inventories at " + "row %s.",
                    inventories[i][0], i);
            checkArgument(inventories[i][1] >= 1 && inventories[i][1] < n - 1,
                    "Found a reference to a non-existing location (%s) in inventories at" + " row %s.",
                    inventories[i][1], i);
            checkArgument(!availLocs.contains(inventories[i][1]),
                    "Found a reference to a location (%s) in inventories at row %s which "
                            + "is available, as such, it can not be in the inventory.",
                    inventories[i][1], i);
            checkArgument(!parcelsInInventory.contains(inventories[i][1]),
                    "Found a duplicate inventory entry, first duplicate at row %s.", i);
            parcelsInInventory.add(inventories[i][1]);
            inventoriesMap.put(inventories[i][0], inventories[i][1]);
        }

        for (int i = 0; i < v; i++) {
            checkArgument(remainingServiceTimes[i] >= 0, "Remaining service time must be >= 0, found %s.",
                    remainingServiceTimes[i]);
        }

        final ImmutableBiMap.Builder<Integer, Integer> servicePairsBuilder = ImmutableBiMap.builder();
        for (int i = 0; i < servicePairs.length; i++) {
            servicePairsBuilder.put(servicePairs[i][0], servicePairs[i][1]);
        }
        final ImmutableBiMap<Integer, Integer> servicePairsMap = servicePairsBuilder.build();

        for (int i = 0; i < v; i++) {
            if (remainingServiceTimes[i] != 0) {
                checkArgument(currentDestinations[i] != 0);
            }

            if (currentDestinations[i] != 0) {
                final int dest = currentDestinations[i];
                checkArgument(dest >= 1 && dest < n - 1,
                        "The destination must be a valid location, it can not be the " + "depot. It is %s.", dest);

                final boolean isAvailablePickupLoc = servicePairsMap.keySet().contains(dest);
                final boolean isInInventory = inventoriesMap.containsValue(dest);
                checkArgument(isAvailablePickupLoc != isInInventory,
                        "The destination location %s must be an available pickup location "
                                + "OR a delivery location which is in the inventory, available "
                                + "pickup loc: %s, in inventory: %s.",
                        dest, isAvailablePickupLoc, isInInventory);

                if (parcelsInInventory.contains(dest)) {
                    checkArgument(inventoriesMap.get(i).contains(dest),
                            "When a vehicle is moving towards a destination which is a "
                                    + "delivery location, it must contain this parcel in its "
                                    + "cargo. Vehicle %s, destination %s.",
                            i, dest);
                }
            }
        }

        if (currentSolutions != null) {
            validateCurrentSolutions(v, n, currentSolutions, currentDestinations, inventoriesMap, servicePairsMap);
        }
    }

    private static void validateCurrentSolutions(int v, int n, SolutionObject[] currentSolutions,
            int[] currentDestinations, Multimap<Integer, Integer> inventoriesMap,
            ImmutableBiMap<Integer, Integer> servicePairsMap) {
        checkArgument(currentSolutions.length == v,
                "The number of currentSolutions (%s) should equal the number of " + "vehicles (%s).",
                currentSolutions.length, v);

        for (int i = 0; i < currentSolutions.length; i++) {
            final List<Integer> route = ImmutableList.copyOf(Ints.asList(currentSolutions[i].route));

            checkArgument(route.get(0) == 0, "First item in route should always be 0, it is %s.", route.get(0));
            checkArgument(route.get(route.size() - 1) == n - 1,
                    "Last item in route should always be depot (%s), it is %s.", n - 1,
                    route.get(route.size() - 1));

            if (currentDestinations[i] > 0) {
                // there is a current destination
                checkArgument(currentDestinations[i] == route.get(1),
                        "The vehicle has a current destination (%s) but it is not the "
                                + "first item in its route: %s.",
                        currentDestinations[i], route);
            }
            final Collection<Integer> inventory = inventoriesMap.get(i);
            checkArgument(ImmutableSet.copyOf(route).containsAll(inventory),
                    "The route should contain all locations in its inventory. Vehicle "
                            + "%s, route: %s, inventory: %s.",
                    i, route, inventory);

            for (int j = 1; j < route.size() - 1; j++) {
                final Integer item = route.get(j);
                final int freq = Collections.frequency(route, item);
                checkArgument(freq == 1, "Vehicle %s: each location should occur only once, found %s "
                        + "instances of location %s. Route: %s.", i, freq, item, route);
                if (!inventoriesMap.containsEntry(i, item)) {
                    // not in cargo, so the pair should appear in the route
                    if (servicePairsMap.containsKey(item)) {
                        checkArgument(route.contains(servicePairsMap.get(item)),
                                "Couldn't find %s in regular mapping.", item);
                    } else {
                        checkArgument(route.contains(servicePairsMap.inverse().get(item)),
                                "Couldn't find %s in inverse mapping.", item);
                    }
                }
            }
        }
    }

    private static void validateVehicleTravelTimes(int v, int n, int[][] vehicleTravelTimes,
            int[] currentDestinations) {
        for (int i = 0; i < v; i++) {
            checkArgument(n == vehicleTravelTimes[i].length,
                    "We expected vehicleTravelTimes matrix of size v x %s, but we found " + "v x %s at index %s.",
                    n, vehicleTravelTimes[i].length, i);

            for (int j = 0; j < n; j++) {
                checkArgument(vehicleTravelTimes[i][j] >= 0,
                        "Found an invalid vehicle travel time (%s) at position %s,%s. All " + "times must be >= 0.",
                        vehicleTravelTimes[i][j], i, j);

                if (j == 0) {
                    checkArgument(0 == vehicleTravelTimes[i][j], "vehicleTravelTimes[%s][%s] == %s, but must be 0.",
                            i, j, vehicleTravelTimes[i][j]);
                } else if (currentDestinations[i] != 0 && j != currentDestinations[i]) {
                    checkArgument(vehicleTravelTimes[i][j] == Integer.MAX_VALUE,
                            "vehicleTravelTimes[%s][%s] == %s, but must be " + "Integer.MAX_VALUE.", i, j,
                            vehicleTravelTimes[i][j]);
                } else {
                    checkArgument(vehicleTravelTimes[i][j] != Integer.MAX_VALUE,
                            "vehicleTravelTimes[%s][%s] == Integer.MAX_VALUE, but must be " + "normal value.", i,
                            j);
                }
            }
        }
    }

    /**
     * Validates the {@link SolutionObject} that is produced by a
     * {@link SingleVehicleArraysSolver} . If the {@link SolutionObject} is
     * infeasible, an {@link IllegalArgumentException} is thrown.
     * @param sol The {@link SolutionObject} that is validated.
     * @param travelTime Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param releaseDates Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param dueDates Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param servicePairs Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param serviceTimes Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @param currentSolution Parameter as specified by
     *          {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     *          .
     * @return The solution as is supplied, used for method chaining.
     */
    public static SolutionObject validateOutputs(SolutionObject sol, int[][] travelTime, int[] releaseDates,
            int[] dueDates, int[][] servicePairs, int[] serviceTimes, @Nullable SolutionObject currentSolution) {
        // convert single vehicle version to multi vehicle version for checking
        // of inputs
        final int n = travelTime.length;
        final int[][] vehicleTravelTimes = new int[1][n];
        // copy first row
        for (int i = 0; i < n; i++) {
            vehicleTravelTimes[0][i] = travelTime[0][i];
        }
        final Set<Integer> locationSet = newHashSet(
                ContiguousSet.create(Range.closedOpen(1, n - 1), DiscreteDomain.integers()));
        for (int i = 0; i < servicePairs.length; i++) {
            locationSet.remove(servicePairs[i][0]);
            locationSet.remove(servicePairs[i][1]);
        }

        final int[][] inventories = new int[locationSet.size()][2];
        final Iterator<Integer> locationSetIterator = locationSet.iterator();
        for (int i = 0; i < locationSet.size(); i++) {
            inventories[i][0] = 0;
            inventories[i][1] = locationSetIterator.next();
        }

        final int[] remainingServiceTimes = new int[] { 0 };
        final int[] currentDestinations = new int[] { 0 };

        @Nullable
        final SolutionObject[] currentSolutions = currentSolution == null ? null
                : new SolutionObject[] { currentSolution };

        // check inputs again since we just modified them
        validateInputs(travelTime, releaseDates, dueDates, servicePairs, serviceTimes, vehicleTravelTimes,
                inventories, remainingServiceTimes, currentDestinations, currentSolutions);

        final SolutionObject[] sols = new SolutionObject[] { sol };
        validateOutputs(sols, travelTime, releaseDates, dueDates, servicePairs, serviceTimes, vehicleTravelTimes,
                inventories, remainingServiceTimes, currentDestinations);
        return sol;
    }

    /**
     * Validates the {@link SolutionObject}s that are produced by a
     * {@link MultiVehicleArraysSolver}. If any of the {@link SolutionObject}s is
     * infeasible, an {@link IllegalArgumentException} is thrown.
     * @param sols The {@link SolutionObject}s that are validated.
     * @param travelTime Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param releaseDates Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param dueDates Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param servicePairs Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param serviceTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param vehicleTravelTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param inventories Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param remainingServiceTimes Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @param currentDestinations Parameter as specified by
     *          {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     *          .
     * @return The solution as is supplied, used for method chaining.
     */
    public static SolutionObject[] validateOutputs(SolutionObject[] sols, int[][] travelTime, int[] releaseDates,
            int[] dueDates, int[][] servicePairs, int[] serviceTimes, int[][] vehicleTravelTimes,
            int[][] inventories, int[] remainingServiceTimes, int[] currentDestinations) {

        final int n = travelTime.length;

        final ImmutableSet.Builder<Integer> routeSetBuilder = ImmutableSet.builder();
        int visitedLocations = 0;
        for (final SolutionObject sol : sols) {
            routeSetBuilder.addAll(toSet(sol.route));
            visitedLocations += sol.route.length - 2;
        }
        final Set<Integer> routeSet = routeSetBuilder.build();
        final Set<Integer> locationSet = ContiguousSet.create(Range.closedOpen(0, travelTime.length),
                DiscreteDomain.integers());

        checkArgument(visitedLocations == n - 2,
                "The number of visits in routes should equal the number of locations, "
                        + "expected: %s, observed: %s.",
                n - 2, visitedLocations);

        // checks duplicates and missing locations
        checkArgument(routeSet.size() == n,
                "Every location should appear exactly once in one route. Missing " + "location: %s.",
                Sets.difference(locationSet, routeSet));
        // checks for completeness of tour
        checkArgument(routeSet.equals(locationSet),
                "Not all locations are serviced, there is probably a non-existing "
                        + "location in the route. Set difference: %s.",
                Sets.difference(routeSet, locationSet));

        final ImmutableMultimap.Builder<Integer, Integer> inventoryBuilder = ImmutableMultimap.builder();
        for (int i = 0; i < inventories.length; i++) {
            inventoryBuilder.put(inventories[i][0], inventories[i][1]);
        }
        final Multimap<Integer, Integer> inventoryMap = inventoryBuilder.build();

        for (int v = 0; v < sols.length; v++) {
            final SolutionObject sol = sols[v];

            /*
             * CHECK SERVICE SEQUENCE
             */
            checkArgument(sol.route[0] == 0,
                    "The route should always start with the vehicle start location: 0, " + "actual:%s.",
                    sol.route[0]);
            checkArgument(sol.route[sol.route.length - 1] == n - 1,
                    "The route should always finish with the depot.");

            if (currentDestinations[v] != 0) {
                checkArgument(sol.route[1] == currentDestinations[v],
                        "Vehicle %s has a current destination %s, as such this must be the "
                                + "first point to visit (at index 1). The route: %s.",
                        v, currentDestinations[v], Arrays.toString(sol.route));
            }

            final Set<Integer> locs = ImmutableSet.copyOf(Ints.asList(sol.route));
            final Collection<Integer> inventory = inventoryMap.get(v);
            for (final Integer i : inventory) {
                checkArgument(locs.contains(i), "Every location in the inventory of a vehicle should occur in its "
                        + "route, route for vehicle %s does not contain location %s.", v, i);
            }

            // check service pairs ordering, pickups should be visited before
            // their corresponding delivery location
            final Map<Integer, Integer> pairs = newHashMap();
            for (int i = 0; i < servicePairs.length; i++) {
                pairs.put(servicePairs[i][0], servicePairs[i][1]);
            }
            final Set<Integer> seen = newHashSet();
            final Set<Integer> set = newHashSet(Ints.asList(sol.route));
            for (int i = 1; i < sol.route.length - 1; i++) {
                if (pairs.containsKey(sol.route[i])) {
                    checkArgument(!seen.contains(pairs.get(sol.route[i])),
                            "Pickups should be visited before their corresponding deliveries."
                                    + " Location %s should be visited after location %s.",
                            pairs.get(sol.route[i]), sol.route[i]);

                    checkArgument(set.contains(pairs.get(sol.route[i])),
                            "Vehicle %s: this route should contain both the pickup and "
                                    + "delivery location, found %s, didn't find %s.",
                            v, sol.route[i], pairs.get(sol.route[i]));
                }
                seen.add(sol.route[i]);
            }

            /*
             * CHECK ARRIVAL TIMES
             */
            checkArgument(sol.arrivalTimes.length == sol.route.length,
                    "Number of arrival times should equal number of locations.");
            checkArgument(sol.arrivalTimes[0] == 0, "The first arrival time should be 0, was %s.",
                    sol.arrivalTimes[0]);

            // check feasibility
            final int[] minArrivalTimes = ArraysSolvers.computeArrivalTimes(sol.route, travelTime,
                    remainingServiceTimes[v], vehicleTravelTimes[v], serviceTimes, releaseDates);
            for (int i = 1; i < sol.route.length; i++) {
                checkArgument(sol.arrivalTimes[i] >= minArrivalTimes[i],
                        "Vehicle %s, route index %s, arrivalTime (%s) needs to be greater "
                                + "or equal to minArrivalTime (%s).",
                        v, i, sol.arrivalTimes[i], minArrivalTimes[i]);
            }

            /*
             * CHECK OBJECTIVE VALUE
             */

            // sum travel time
            final int totalTravelTime = ArraysSolvers.computeTotalTravelTime(sol.route, travelTime,
                    vehicleTravelTimes[v]);

            // sum tardiness
            final int tardiness = ArraysSolvers.computeRouteTardiness(sol.route, sol.arrivalTimes, serviceTimes,
                    dueDates, remainingServiceTimes[v]);

            checkArgument(sol.objectiveValue == totalTravelTime + tardiness,
                    "Vehicle %s: incorrect objective value (%s), it should be travel "
                            + "time + tardiness = %s + %s = %s.",
                    v, sol.objectiveValue, totalTravelTime, tardiness, totalTravelTime + tardiness);

        }

        return sols;
    }

    static Set<Integer> toSet(int[] arr) {
        final Set<Integer> set = newHashSet();
        for (int i = 0; i < arr.length; i++) {
            set.add(arr[i]);
        }
        return set;
    }

    private static class SingleValidator implements SingleVehicleArraysSolver {
        private final SingleVehicleArraysSolver delegateSolver;

        SingleValidator(SingleVehicleArraysSolver delegate) {
            delegateSolver = delegate;
        }

        @Override
        public SolutionObject solve(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
                int[] serviceTimes, @Nullable SolutionObject currentSolution) {
            // first check inputs
            validateInputs(travelTime, releaseDates, dueDates, servicePairs, serviceTimes);
            // execute solver
            final SolutionObject output = delegateSolver.solve(travelTime, releaseDates, dueDates, servicePairs,
                    serviceTimes, currentSolution);
            // check outputs
            return validateOutputs(output, travelTime, releaseDates, dueDates, servicePairs, serviceTimes,
                    currentSolution);
        }
    }

    private static class MultiValidator implements MultiVehicleArraysSolver {
        private final MultiVehicleArraysSolver delegateSolver;

        MultiValidator(MultiVehicleArraysSolver delegate) {
            delegateSolver = delegate;
        }

        @Override
        public SolutionObject[] solve(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
                int[] serviceTimes, int[][] vehicleTravelTimes, int[][] inventories, int[] remainingServiceTimes,
                int[] currentDestinations, @Nullable SolutionObject[] currentSolutions) {
            validateInputs(travelTime, releaseDates, dueDates, servicePairs, serviceTimes, vehicleTravelTimes,
                    inventories, remainingServiceTimes, currentDestinations, currentSolutions);
            final SolutionObject[] output = delegateSolver.solve(travelTime, releaseDates, dueDates, servicePairs,
                    serviceTimes, vehicleTravelTimes, inventories, remainingServiceTimes, currentDestinations,
                    currentSolutions);
            return validateOutputs(output, travelTime, releaseDates, dueDates, servicePairs, serviceTimes,
                    vehicleTravelTimes, inventories, remainingServiceTimes, currentDestinations);
        }
    }
}