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

Java tutorial

Introduction

Here is the source code for com.github.rinde.rinsim.central.arrays.ArraysSolvers.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.base.Preconditions.checkState;
import static com.google.common.collect.Sets.newHashSet;

import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;
import javax.measure.Measure;
import javax.measure.converter.UnitConverter;
import javax.measure.quantity.Duration;
import javax.measure.quantity.Length;
import javax.measure.quantity.Velocity;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;

import com.github.rinde.rinsim.central.GlobalStateObject;
import com.github.rinde.rinsim.central.GlobalStateObject.VehicleStateObject;
import com.github.rinde.rinsim.core.model.pdp.Parcel;
import com.github.rinde.rinsim.core.model.road.RoadModels;
import com.github.rinde.rinsim.geom.Point;
import com.github.rinde.rinsim.util.TimeWindow;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.math.DoubleMath;

/**
 * @author Rinde van Lon
 *
 */
public final class ArraysSolvers {

    // TODO create a Converter class which uses the Units as constructor
    // parameters. All methods can then easily access them, making most of the
    // code much cleaner.

    private ArraysSolvers() {
    }

    /**
     * Converts the list of points on a plane into a travel time matrix. For
     * distance between two points the Euclidean distance is used, i.e. no
     * obstacles or graph structure are considered. See
     * {@link #toTravelTimeMatrix(List, Unit, Measure, Unit, RoundingMode)} for
     * more options.
     * @param points The set of points which will be converted to a travel time
     *          matrix.
     * @param speed the speed in m/s.
     * @param rm The rounding mode, see {@link RoundingMode}.
     * @return A <code>n x n</code> travel time matrix, where <code>n</code> is
     *         the size of the <code>points</code> list.
     */
    public static int[][] toTravelTimeMatrix(List<Point> points, double speed, RoundingMode rm) {
        return toTravelTimeMatrix(points, SI.METER, Measure.valueOf(speed, SI.METERS_PER_SECOND), SI.SECOND, rm);
    }

    /**
     * Converts the list of points on a plane into a travel time matrix. For
     * distance between two points the euclidean distance is used, i.e. no
     * obstacles or graph structure are considered.
     * @param points The set of points which will be converted to a travel time
     *          matrix.
     * @param distUnit The {@link Unit} that is used for distances (
     *          {@link Length}) between the specified points.
     * @param speed The travel speed specified as a {@link Measure} which includes
     *          its {@link Unit}.
     * @param outputTimeUnit The output time {@link Unit} to which all times are
     *          converted, e.g. if {@link SI#SECOND} is specified the travel times
     *          will be in seconds.
     * @param rm When computing the travel times they often need to be rounded.
     *          The rounding mode indicates how numbers are rounded, see
     *          {@link RoundingMode} for the available options.
     * @return A <code>n x n</code> travel time matrix, where <code>n</code> is
     *         the size of the <code>points</code> list.
     */
    public static int[][] toTravelTimeMatrix(List<Point> points, Unit<Length> distUnit,
            Measure<Double, Velocity> speed, Unit<Duration> outputTimeUnit, RoundingMode rm) {
        checkArgument(points.size() >= 2);
        final int[][] matrix = new int[points.size()][points.size()];
        for (int i = 0; i < points.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (i != j) {
                    // compute distance
                    final Measure<Double, Length> dist = Measure
                            .valueOf(Point.distance(points.get(i), points.get(j)), distUnit);
                    // calculate duration in desired unit
                    final double duration = RoadModels.computeTravelTime(speed, dist, outputTimeUnit);
                    // round duration
                    final int tt = DoubleMath.roundToInt(duration, rm);
                    matrix[i][j] = tt;
                    matrix[j][i] = tt;
                }
            }
        }
        return matrix;
    }

    /**
     * Converts the {@link GlobalStateObject} into an {@link ArraysObject} using
     * the specified output time unit.
     * @param state The state to convert.
     * @param outputTimeUnit The {@link Unit} to use as time in the resulting
     *          object.
     * @return An {@link ArraysObject} using the specified output time unit.
     */
    public static ArraysObject toSingleVehicleArrays(GlobalStateObject state, Unit<Duration> outputTimeUnit) {

        final UnitConverter timeConverter = state.getTimeUnit().getConverterTo(outputTimeUnit);

        final VehicleStateObject v = state.getVehicles().iterator().next();

        // we check all vehicles in case this method is used in other contexts
        final ImmutableSet.Builder<Parcel> cargoBuilder = ImmutableSet.builder();
        for (final VehicleStateObject vs : state.getVehicles()) {
            cargoBuilder.addAll(vs.getContents());
        }
        final Set<Parcel> inCargo = cargoBuilder.build();

        // there are always two locations: the current vehicle location and
        // the depot
        final int numLocations = 2 + state.getAvailableParcels().size() * 2 + inCargo.size();

        final int[] releaseDates = new int[numLocations];
        final int[] dueDates = new int[numLocations];
        final int[][] servicePairs = new int[state.getAvailableParcels().size()][2];
        final int[] serviceTimes = new int[numLocations];

        // we need to create two mappings:
        // Parcel -> pickup index / deliver index
        // index -> Parcel
        final ImmutableMap.Builder<Parcel, ParcelIndexObj> parcel2indexBuilder = ImmutableMap.builder();
        final ImmutableMap.Builder<Integer, ParcelIndexObj> index2parcelBuilder = ImmutableMap.builder();

        // we wrap the points in PointWrapper to avoid problems with (possibly)
        // duplicates in the points
        final ImmutableList.Builder<Point> points = ImmutableList.builder();
        points.add(v.getLocation());

        int index = 1;
        int spIndex = 0;
        for (final Parcel p : state.getAvailableParcels()) {
            serviceTimes[index] = DoubleMath.roundToInt(timeConverter.convert(p.getPickupDuration()),
                    RoundingMode.CEILING);
            // add pickup location and time window
            points.add(p.getPickupLocation());
            final int deliveryIndex = index + state.getAvailableParcels().size();
            final ParcelIndexObj pio = new ParcelIndexObj(p, index, deliveryIndex);
            parcel2indexBuilder.put(p, pio);
            index2parcelBuilder.put(index, pio);
            index2parcelBuilder.put(deliveryIndex, pio);

            final int[] tw = convertTW(p.getPickupTimeWindow(), state.getTime(), timeConverter);
            releaseDates[index] = tw[0];
            dueDates[index] = tw[1];
            checkState(releaseDates[index] <= dueDates[index]);

            // link the pair with its delivery location (see next loop)
            servicePairs[spIndex++] = new int[] { index, deliveryIndex };

            index++;
        }
        checkState(spIndex == state.getAvailableParcels().size(), "%s %s", state.getAvailableParcels().size(),
                spIndex);

        final List<Parcel> deliveries = new ImmutableList.Builder<Parcel>().addAll(state.getAvailableParcels())
                .addAll(inCargo).build();
        for (final Parcel p : deliveries) {
            serviceTimes[index] = DoubleMath.roundToInt(timeConverter.convert(p.getDeliveryDuration()),
                    RoundingMode.CEILING);

            points.add(p.getDeliveryLocation());
            if (inCargo.contains(p)) {
                final ParcelIndexObj pio = new ParcelIndexObj(p, -1, index);
                parcel2indexBuilder.put(p, pio);
                index2parcelBuilder.put(index, pio);
            }

            final int[] tw = convertTW(p.getDeliveryTimeWindow(), state.getTime(), timeConverter);
            releaseDates[index] = tw[0];
            dueDates[index] = tw[1];
            checkState(releaseDates[index] <= dueDates[index]);

            index++;
        }
        checkState(index == numLocations - 1);

        // the start position of the truck points to the depot location
        points.add(v.getDto().getStartPosition());

        // end of the day
        dueDates[index] = fixTWend(v.getDto().getAvailabilityTimeWindow().end(), state.getTime(), timeConverter);

        releaseDates[index] = Math.min(0, dueDates[index]);

        final Measure<Double, Velocity> speed = Measure.valueOf(v.getDto().getSpeed(), state.getSpeedUnit());

        final ImmutableList<Point> pointList = points.build();
        final ImmutableMap<Parcel, ParcelIndexObj> parcel2indexMap = parcel2indexBuilder.build();
        final ImmutableMap<Integer, ParcelIndexObj> index2parcelMap = index2parcelBuilder.build();

        final int[][] travelTime = ArraysSolvers.toTravelTimeMatrix(pointList, state.getDistUnit(), speed,
                outputTimeUnit, RoundingMode.CEILING);

        @Nullable
        SolutionObject[] sol = null;
        if (v.getRoute().isPresent() && state.getVehicles().size() == 1) {
            // the assumption is that if the current route of one vehicle is known,
            // the routes of all vehicles should be known.
            sol = toCurrentSolutions(state, parcel2indexMap, travelTime, releaseDates, dueDates, serviceTimes,
                    new int[][] { travelTime[0] }, new int[] { 0 });
        }
        return new ArraysObject(travelTime, releaseDates, dueDates, servicePairs, serviceTimes, sol, pointList,
                parcel2indexMap, index2parcelMap);
    }

    @Nullable
    static SolutionObject[] toCurrentSolutions(GlobalStateObject state, Map<Parcel, ParcelIndexObj> mapping,
            int[][] travelTime, int[] releaseDates, int[] dueDates, int[] serviceTimes, int[][] vehicleTravelTimes,
            int[] remainingServiceTimes) {
        final SolutionObject[] sols = new SolutionObject[state.getVehicles().size()];
        for (int i = 0; i < state.getVehicles().size(); i++) {
            sols[i] = convertRouteToSolutionObject(state, state.getVehicles().get(i), mapping, travelTime,
                    releaseDates, dueDates, serviceTimes, vehicleTravelTimes[i], remainingServiceTimes[i]);
        }
        return sols;
    }

    static SolutionObject convertRouteToSolutionObject(GlobalStateObject state, VehicleStateObject vso,
            Map<Parcel, ParcelIndexObj> mapping, int[][] travelTime, int[] releaseDates, int[] dueDates,
            int[] serviceTimes, int[] vehicleTravelTimes, int remainingServiceTime) {
        final int[] route = new int[vso.getRoute().get().size() + 2];

        final Set<Parcel> seen = newHashSet();
        for (int i = 0; i < vso.getRoute().get().size(); i++) {
            final Parcel dto = vso.getRoute().get().get(i);

            if (vso.getContents().contains(dto) || seen.contains(dto)) {
                // it is in cargo
                route[i + 1] = mapping.get(dto).deliveryIndex;
            } else {
                checkArgument(state.getAvailableParcels().contains(dto),
                        "This parcel should be available but is not: %s.", dto);
                // it is available
                route[i + 1] = mapping.get(dto).pickupIndex;
            }
            // TODO add error msg
            checkArgument(route[i + 1] > 0);
            seen.add(dto);
        }
        route[route.length - 1] = travelTime.length - 1;
        final int[] arrivalTimes = computeArrivalTimes(route, travelTime, remainingServiceTime, vehicleTravelTimes,
                serviceTimes, releaseDates);

        final int tardiness = computeRouteTardiness(route, arrivalTimes, serviceTimes, dueDates,
                remainingServiceTime);
        final int tt = computeTotalTravelTime(route, travelTime, vehicleTravelTimes);
        return new SolutionObject(route, arrivalTimes, tt + tardiness);
    }

    static int[] computeArrivalTimes(int[] route, int[][] travelTime, int remainingServiceTime,
            int[] vehicleTravelTimes, int[] serviceTimes, int[] releaseDates) {
        final int[] arrivalTimes = new int[route.length];

        checkArgument(route.length >= 2);
        checkArgument(route[0] == 0);

        arrivalTimes[0] = 0;

        for (int j = 1; j < route.length; j++) {
            final int prev = route[j - 1];
            final int cur = route[j];

            // we compute the travel time. If it is the first step in the
            // route, we use the time from vehicle location to the next
            // location in the route.
            final int tt = j == 1 ? vehicleTravelTimes[cur] : travelTime[prev][cur];

            if (j == 1 && remainingServiceTime > 0) {
                checkArgument(tt == 0, "%s", tt);
            }

            // service time is different in case we were already halfway with the
            // servicing (as defined by remainingServiceTime)
            final int st = j == 2 && remainingServiceTime > 0 ? remainingServiceTime : serviceTimes[prev];
            // we compute the first possible arrival time for the vehicle to
            // arrive at location i, given that it first visited location
            // i-1
            final int earliestArrivalTime = arrivalTimes[j - 1] + st + tt;

            // we also have to take into account the time window
            final int minArrivalTime = Math.max(earliestArrivalTime, releaseDates[cur]);
            arrivalTimes[j] = minArrivalTime;
        }
        return arrivalTimes;
    }

    /**
     * Converts the specified {@link GlobalStateObject} into an
     * {@link MVArraysObject} using the specified time unit.
     * @param state The state to convert.
     * @param outputTimeUnit The unit to use for time.
     * @return A {@link MVArraysObject} using the specified output time unit.
     */
    public static MVArraysObject toMultiVehicleArrays(GlobalStateObject state, Unit<Duration> outputTimeUnit) {
        final ArraysObject singleVehicleArrays = toSingleVehicleArrays(state, outputTimeUnit);
        checkArgument(!state.getVehicles().isEmpty(), "We need at least one vehicle");

        final int[][] vehicleTravelTimes = toVehicleTravelTimes(state, singleVehicleArrays, outputTimeUnit);
        final int[][] inventories = toInventoriesArray(state, singleVehicleArrays);
        final int[] remainingServiceTimes = toRemainingServiceTimes(state, outputTimeUnit);

        final int[] currentDestinations = toVehicleDestinations(state, singleVehicleArrays);

        @Nullable
        SolutionObject[] sols = null;
        if (state.getVehicles().iterator().next().getRoute().isPresent()) {
            // the assumption is that if the current route of one vehicle is known,
            // the routes of all vehicles should be known.
            sols = toCurrentSolutions(state, singleVehicleArrays.parcel2index, singleVehicleArrays.travelTime,
                    singleVehicleArrays.releaseDates, singleVehicleArrays.dueDates,
                    singleVehicleArrays.serviceTimes, vehicleTravelTimes, remainingServiceTimes);
        }

        return new MVArraysObject(singleVehicleArrays, sols, vehicleTravelTimes, inventories, remainingServiceTimes,
                currentDestinations);
    }

    /**
     * Converts a {@link SolutionObject} into a list of {@link Parcel}s.
     * @param sol The solution to convert.
     * @param index2parcel Mapping of indices to {@link Parcel}s.
     * @return A list containing the route as specified by the
     *         {@link SolutionObject}.
     */
    public static ImmutableList<Parcel> convertSolutionObject(SolutionObject sol,
            Map<Integer, ParcelIndexObj> index2parcel) {
        final ImmutableList.Builder<Parcel> builder = ImmutableList.builder();
        // ignore first (current pos) and last (depot)
        for (int i = 1; i < sol.route.length - 1; i++) {
            builder.add(index2parcel.get(sol.route[i]).dto);
        }
        return builder.build();
    }

    static int[] toVehicleDestinations(GlobalStateObject state, ArraysObject sva) {
        final int v = state.getVehicles().size();
        final UnmodifiableIterator<VehicleStateObject> iterator = state.getVehicles().iterator();

        final int[] destinations = new int[v];
        for (int i = 0; i < v; i++) {
            final VehicleStateObject cur = iterator.next();
            if (cur.getDestination().isPresent()) {
                final Parcel dest = cur.getDestination().get();
                checkArgument(sva.parcel2index.containsKey(dest));
                final boolean isInCargo = cur.getContents().contains(dest);
                final ParcelIndexObj pio = sva.parcel2index.get(dest);
                final int index = isInCargo ? pio.deliveryIndex : pio.pickupIndex;
                destinations[i] = index;
            } else {
                destinations[i] = 0;
            }
            checkArgument(destinations[i] >= 0, "Invalid destination.", cur.getDestination());
        }
        return destinations;
    }

    static int[][] toVehicleTravelTimes(GlobalStateObject state, ArraysObject sva, Unit<Duration> outputTimeUnit) {
        final int v = state.getVehicles().size();
        final int n = sva.travelTime.length;
        // compute vehicle travel times
        final int[][] vehicleTravelTimes = new int[v][n];

        final UnmodifiableIterator<VehicleStateObject> iterator = state.getVehicles().iterator();

        for (int i = 0; i < v; i++) {
            final VehicleStateObject cur = iterator.next();
            final Measure<Double, Velocity> speed = Measure.valueOf(cur.getDto().getSpeed(), state.getSpeedUnit());

            if (cur.getDestination().isPresent()) {
                final Parcel dest = cur.getDestination().get();
                // only add travel time for current dest
                for (int j = 1; j < n; j++) {
                    vehicleTravelTimes[i][j] = Integer.MAX_VALUE;
                }
                final boolean isInCargo = cur.getContents().contains(dest);
                final ParcelIndexObj pio = sva.parcel2index.get(dest);
                final int index = isInCargo ? pio.deliveryIndex : pio.pickupIndex;

                checkArgument(index > 0);
                vehicleTravelTimes[i][index] = computeRoundedTravelTime(speed,
                        Measure.valueOf(Point.distance(cur.getLocation(), sva.location2index.get(index)),
                                state.getDistUnit()),
                        outputTimeUnit);

            } else {
                // add travel time for every location
                for (int j = 1; j < n; j++) {
                    vehicleTravelTimes[i][j] = computeRoundedTravelTime(speed,
                            Measure.valueOf(Point.distance(cur.getLocation(), sva.location2index.get(j)),
                                    state.getDistUnit()),
                            outputTimeUnit);
                }
            }
        }
        return vehicleTravelTimes;
    }

    static int computeRoundedTravelTime(Measure<Double, Velocity> speed, Measure<Double, Length> dist,
            Unit<Duration> outputTimeUnit) {
        return DoubleMath.roundToInt(RoadModels.computeTravelTime(speed, dist, outputTimeUnit),
                RoundingMode.CEILING);
    }

    static int[][] toInventoriesArray(GlobalStateObject state, ArraysObject sva) {
        final UnmodifiableIterator<VehicleStateObject> iterator = state.getVehicles().iterator();

        final ImmutableList.Builder<ImmutableList<Integer>> invPairBuilder = ImmutableList.builder();
        for (int i = 0; i < state.getVehicles().size(); i++) {
            final VehicleStateObject cur = iterator.next();
            for (final Parcel dp : cur.getContents()) {
                invPairBuilder.add(ImmutableList.of(i, sva.parcel2index.get(dp).deliveryIndex));
            }
        }
        final ImmutableList<ImmutableList<Integer>> inventoryPairs = invPairBuilder.build();

        final int[][] inventories = new int[inventoryPairs.size()][2];
        for (int i = 0; i < inventoryPairs.size(); i++) {
            inventories[i][0] = inventoryPairs.get(i).get(0);
            inventories[i][1] = inventoryPairs.get(i).get(1);
        }
        return inventories;
    }

    static int[] toRemainingServiceTimes(GlobalStateObject state, Unit<Duration> outputTimeUnit) {
        final UnmodifiableIterator<VehicleStateObject> iterator = state.getVehicles().iterator();
        final int[] remainingServiceTimes = new int[state.getVehicles().size()];
        for (int i = 0; i < state.getVehicles().size(); i++) {
            remainingServiceTimes[i] = DoubleMath
                    .roundToInt(Measure.valueOf(iterator.next().getRemainingServiceTime(), state.getTimeUnit())
                            .doubleValue(outputTimeUnit), RoundingMode.CEILING);
        }
        return remainingServiceTimes;
    }

    /**
     * Computes the total travel time of the specified route.
     * @param route The route.
     * @param travelTime The travel time matrix.
     * @param vehicleTravelTimes The vehicle travel time for the vehicle that is
     *          driving the specified route.
     * @return The travel time of the specified route.
     */
    public static int computeTotalTravelTime(int[] route, int[][] travelTime, int[] vehicleTravelTimes) {
        int totalTravelTime = 0;
        for (int i = 1; i < route.length; i++) {
            if (i == 1) {
                totalTravelTime += vehicleTravelTimes[route[i]];
            } else {
                totalTravelTime += travelTime[route[i - 1]][route[i]];
            }
        }
        return totalTravelTime;
    }

    /**
     * Computes the total tardiness of the specified route with the specified
     * arrivalTimes.
     * @param route The route with length &ge; 2.
     * @param arrivalTimes The arrival times at every index of the route.
     * @param serviceTimes The full serviceTimes array containing all locations,
     *          using the original indices.
     * @param dueDates The full dueDates array containing all locations, using the
     *          original indices.
     * @param remainingServiceTime The remaining service time for the position at
     *          index 1, if any, 0 otherwise.
     * @return The sum tardiness.
     */
    public static int computeRouteTardiness(int[] route, int[] arrivalTimes, int[] serviceTimes, int[] dueDates,
            int remainingServiceTime) {
        int tardiness = 0;
        // start at index 1 since there can be no tardiness at start location
        for (int i = 1; i < route.length; i++) {
            int st;
            if (i == 1 && remainingServiceTime > 0) {
                st = remainingServiceTime;
            } else {
                st = serviceTimes[route[i]];
            }
            final int lateness = arrivalTimes[i] + st - dueDates[route[i]];
            if (lateness > 0) {
                tardiness += lateness;
            }
        }
        return tardiness;
    }

    /**
     * Sums the objective values of all provided {@link SolutionObject}s.
     * @param sols The {@link SolutionObject}s.
     * @return The sum objective value.
     */
    public static int computeTotalObjectiveValue(SolutionObject[] sols) {
        int obj = 0;
        for (final SolutionObject sol : sols) {
            obj += sol.objectiveValue;
        }
        return obj;
    }

    /**
     * Sums the objective values of all provided {@link SolutionObject}s. The
     * input values are treated as instances of the <code>inputUnit</code> and are
     * converted to the <code>outputUnit</code>.
     * @param sols The {@link SolutionObject}s.
     * @param inputUnit The time unit of the input values.
     * @param outputUnit The time unit to convert the values to.
     * @return The sum objective value in the <code>outputUnit</code>.
     */
    public static int computeTotalObjectiveValue(SolutionObject[] sols, Unit<Duration> inputUnit,
            Unit<Duration> outputUnit) {
        return Measure.valueOf(computeTotalObjectiveValue(sols), inputUnit).intValue(outputUnit);
    }

    static int[] convertTW(TimeWindow tw, long time, UnitConverter timeConverter) {
        final int releaseDate = fixTWstart(tw.begin(), time, timeConverter);
        final int dueDate = fixTWend(tw.end(), time, timeConverter);
        if (releaseDate > dueDate) {
            // if this happens, we know this is the result of rounding behavior:
            // release is rounded up, due is rounded down. We also know that the
            // difference is only 1. Therefore we flip the values.
            checkArgument(Math.abs(dueDate - releaseDate) == 1);
            return new int[] { dueDate, releaseDate };
        }
        return new int[] { releaseDate, dueDate };
    }

    static int fixTWstart(long start, long time, UnitConverter timeConverter) {
        return DoubleMath.roundToInt(timeConverter.convert(start - time), RoundingMode.CEILING);
    }

    static int fixTWend(long end, long time, UnitConverter timeConverter) {
        return DoubleMath.roundToInt(timeConverter.convert(end - time), RoundingMode.FLOOR);
    }

    /**
     * Object which specifies the parameters of
     * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
     * . Also includes additional information which is required to interpret the
     * resulting {@link SolutionObject}.
     * @author Rinde van Lon
     */
    public static class ArraysObject {
        /**
         * See
         * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
         * .
         */
        public final int[][] travelTime;

        /**
         * See
         * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
         * .
         */
        public final int[] releaseDates;

        /**
         * See
         * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
         * .
         */
        public final int[] dueDates;

        /**
         * See
         * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
         * .
         */
        public final int[][] servicePairs;

        /**
         * See
         * {@link SingleVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], SolutionObject)}
         * .
         */
        public final int[] serviceTimes;

        /**
         * See
         * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
         * .
         */
        @Nullable
        public final SolutionObject[] currentSolutions;

        /**
         * A bidirectional mapping between locations and their index.
         */
        public final ImmutableList<Point> location2index;

        /**
         * A mapping between parcels and their locations.
         */
        public final ImmutableMap<Parcel, ParcelIndexObj> parcel2index;

        /**
         * A mapping between indices and parcels/locations.
         */
        public final ImmutableMap<Integer, ParcelIndexObj> index2parcel;

        ArraysObject(int[][] pTravelTime, int[] pReleaseDates, int[] pDueDates, int[][] pServicePairs,
                int[] pServiceTimes, @Nullable SolutionObject[] pCurrentSolutions, ImmutableList<Point> pLocations,
                ImmutableMap<Parcel, ParcelIndexObj> pParcel2index,
                ImmutableMap<Integer, ParcelIndexObj> pIndex2parcel) {
            travelTime = pTravelTime;
            releaseDates = pReleaseDates;
            dueDates = pDueDates;
            servicePairs = pServicePairs;
            serviceTimes = pServiceTimes;
            currentSolutions = pCurrentSolutions;
            location2index = pLocations;
            parcel2index = pParcel2index;
            index2parcel = pIndex2parcel;
        }

        ArraysObject(int[][] pTravelTime, int[] pReleaseDates, int[] pDueDates, int[][] pServicePairs,
                int[] pServiceTimes, @Nullable SolutionObject[] pCurrentSolutions) {
            this(pTravelTime, pReleaseDates, pDueDates, pServicePairs, pServiceTimes, pCurrentSolutions,
                    ImmutableList.<Point>of(), ImmutableMap.<Parcel, ParcelIndexObj>of(),
                    ImmutableMap.<Integer, ParcelIndexObj>of());
        }
    }

    /**
     * Object which specifies the parameters of
     * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
     * . Also includes additional information which is required to interpret the
     * resulting {@link SolutionObject}.
     * @author Rinde van Lon
     */
    public static class MVArraysObject extends ArraysObject {
        /**
         * See
         * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
         * .
         */
        public final int[][] vehicleTravelTimes;

        /**
         * See
         * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
         * .
         */
        public final int[][] inventories;

        /**
         * See
         * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
         * .
         */
        public final int[] remainingServiceTimes;

        /**
         * See
         * {@link MultiVehicleArraysSolver#solve(int[][], int[], int[], int[][], int[], int[][], int[][], int[], int[], SolutionObject[])}
         * .
         */
        public final int[] currentDestinations;

        MVArraysObject(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
                int[] serviceTimes, @Nullable SolutionObject[] currentSolutions, ImmutableList<Point> locations,
                ImmutableMap<Parcel, ParcelIndexObj> parcel2index,
                ImmutableMap<Integer, ParcelIndexObj> index2parcel, int[][] pVehicleTravelTimes,
                int[][] pInventories, int[] pRemainingServiceTimes, int[] pCurrentDestinations) {
            super(travelTime, releaseDates, dueDates, servicePairs, serviceTimes, currentSolutions, locations,
                    parcel2index, index2parcel);
            vehicleTravelTimes = Arrays.copyOf(pVehicleTravelTimes, pVehicleTravelTimes.length);
            inventories = Arrays.copyOf(pInventories, pInventories.length);
            remainingServiceTimes = Arrays.copyOf(pRemainingServiceTimes, pRemainingServiceTimes.length);
            currentDestinations = Arrays.copyOf(pCurrentDestinations, pCurrentDestinations.length);
        }

        MVArraysObject(ArraysObject ao, @Nullable SolutionObject[] currentSolutions, int[][] pVehicleTravelTimes,
                int[][] pInventories, int[] pRemainingServiceTimes, int[] pCurrentDestinations) {
            this(ao.travelTime, ao.releaseDates, ao.dueDates, ao.servicePairs, ao.serviceTimes, currentSolutions,
                    ao.location2index, ao.parcel2index, ao.index2parcel, pVehicleTravelTimes, pInventories,
                    pRemainingServiceTimes, pCurrentDestinations);
        }

        MVArraysObject(int[][] travelTime, int[] releaseDates, int[] dueDates, int[][] servicePairs,
                int[] serviceTimes, int[][] pVehicleTravelTimes, int[][] pInventories, int[] pRemainingServiceTimes,
                int[] pCurrentDestinations, @Nullable SolutionObject[] curSolutions) {
            super(travelTime, releaseDates, dueDates, servicePairs, serviceTimes, curSolutions);
            vehicleTravelTimes = Arrays.copyOf(pVehicleTravelTimes, pVehicleTravelTimes.length);
            inventories = Arrays.copyOf(pInventories, pInventories.length);
            remainingServiceTimes = Arrays.copyOf(pRemainingServiceTimes, pRemainingServiceTimes.length);
            currentDestinations = Arrays.copyOf(pCurrentDestinations, pCurrentDestinations.length);
        }
    }

    static class ParcelIndexObj {
        final Parcel dto;
        final int pickupIndex;
        final int deliveryIndex;

        ParcelIndexObj(Parcel pDto, int pPickupIndex, int pDeliveryIndex) {
            dto = pDto;
            pickupIndex = pPickupIndex;
            deliveryIndex = pDeliveryIndex;
        }
    }
}