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

rinde.sim.pdptw.central.SolverValidator Maven / Gradle / Ivy

The newest version!
/**
 * 
 */
package rinde.sim.pdptw.central;

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

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

import rinde.sim.pdptw.central.GlobalStateObject.VehicleStateObject;
import rinde.sim.pdptw.common.ParcelDTO;
import rinde.sim.util.SupplierRng;
import rinde.sim.util.SupplierRng.DefaultSupplierRng;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

/**
 * Provides methods for validating input to and output from {@link Solver}s.
 * Also provides {@link #wrap(Solver)} method which decorates any solver such
 * that both inputs and outputs are validated every time it is is called.
 * @author Rinde van Lon 
 */
public final class SolverValidator {

  private SolverValidator() {}

  /**
   * Decorates the original {@link Solver} 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 Solver} that will be used for the actual
   *          solving.
   * @return The wrapped solver.
   */
  public static Solver wrap(Solver delegate) {
    return new SolverValidator.Validator(delegate);
  }

  public static SupplierRng wrap(SupplierRng sup) {
    return new SolverValidatorSupplier(sup);
  }

  private static final class SolverValidatorSupplier extends
      DefaultSupplierRng {
    private final SupplierRng supplier;

    SolverValidatorSupplier(SupplierRng sup) {
      supplier = sup;
    }

    @Override
    public Solver get(long seed) {
      return SolverValidator.wrap(supplier.get(seed));
    }
  }

  /**
   * Validates the inputs for {@link Solver#solve(GlobalStateObject)} method. If
   * the input is not correct an {@link IllegalArgumentException} is thrown.
   * @param state The state to validate.
   * @return The state.
   */
  public static GlobalStateObject validateInputs(GlobalStateObject state) {
    checkArgument(state.time >= 0, "Time must be >= 0, is %s.", state.time);
    final Set inventoryParcels = newHashSet();

    final boolean routeIsPresent = state.vehicles.get(0).route.isPresent();
    final Set allParcels = newHashSet();
    for (final VehicleStateObject vs : state.vehicles) {
      checkArgument(
          vs.route.isPresent() == routeIsPresent,
          "Either a route should be present for all vehicles, or no route should be present for all vehicles.");
      if (vs.route.isPresent()) {
        for (final ParcelDTO p : vs.route.get()) {
          checkArgument(
              !allParcels.contains(p),
              "Found parcel which is already present in the route of another vehicle. Parcel %s.",
              p);
        }
        allParcels.addAll(vs.route.get());
      }
    }

    for (int i = 0; i < state.vehicles.size(); i++) {
      final VehicleStateObject vs = state.vehicles.get(i);

      checkArgument(vs.remainingServiceTime >= 0,
          "Remaining service time must be >= 0, is %s.",
          vs.remainingServiceTime);
      checkArgument(vs.speed > 0, "Speed must be positive, is %s.", vs.speed);
      final Set intersection = Sets.intersection(
          state.availableParcels, vs.contents);
      checkArgument(
          intersection.isEmpty(),
          "Parcels can not be available AND in the inventory of a vehicle, found: %s.",
          intersection);
      final Set inventoryIntersection = Sets.intersection(
          inventoryParcels, vs.contents);
      checkArgument(
          inventoryIntersection.isEmpty(),
          "Parcels can not be in the inventory of two vehicles at the same time, found: %s.",
          inventoryIntersection);
      inventoryParcels.addAll(vs.contents);

      // if the destination parcel is not available, it must be in the
      // cargo of the vehicle
      if (vs.destination != null) {
        final boolean isAvailable = state.availableParcels
            .contains(vs.destination);
        final boolean isInCargo = vs.contents.contains(vs.destination);
        checkArgument(
            isAvailable != isInCargo,
            "Destination must be either available (%s) or in the current vehicle's cargo (%s), but not both (i.e. XOR). Destination: %s, vehicle: %s (out of %s), remaining service time: %s.",
            isAvailable, isInCargo, vs.destination, i, state.vehicles.size(),
            vs.remainingServiceTime);
      }

      if (vs.route.isPresent()) {
        checkRoute(vs, i);
      }
    }
    return state;
  }

  /**
   * Validate the route of a vehicle.
   * @param vs The vehicle to check.
   * @param i The index of the vehicle, only used to generate nice error
   *          messages.
   * @throws IllegalArgumentException if the route is not correct.
   */
  public static void checkRoute(VehicleStateObject vs, int i) {
    checkArgument(vs.route.isPresent());
    checkArgument(
        vs.route.get().containsAll(vs.contents),
        "Vehicle %s's route doesn't contain all locations it has in cargo. Route: %s, cargo: %s.",
        i, vs.route.get(), vs.contents);
    if (vs.destination != null) {
      checkArgument(!vs.route.get().isEmpty()
          && vs.route.get().get(0) == vs.destination,
          "First location in route must equal destination (%s), route is: %s.",
          vs.destination, vs.route.get());
    }

    for (final ParcelDTO dp : vs.route.get()) {
      final int freq = Collections.frequency(vs.route.get(), dp);
      if (vs.contents.contains(dp)) {
        checkArgument(
            freq == 1,
            "A parcel already in cargo should occur once in the route, found %s instance(s). Parcel: %s, route: %s.",
            freq, dp, vs.route.get());
      } else {
        checkArgument(
            freq == 2,
            "A parcel that is still available should occur twice in the route, found %s instance(s). Parcel: %s, route: %s.",
            freq, dp, vs.route.get(), vs.remainingServiceTime);
      }
    }
  }

  /**
   * Validates the routes that are produced by a {@link Solver}. If one of the
   * routes is infeasible an {@link IllegalArgumentException} is thrown.
   * @param routes The routes that are validated.
   * @param state Parameter as specified by
   *          {@link Solver#solve(GlobalStateObject)}.
   * @return The routes.
   */
  public static ImmutableList> validateOutputs(
      ImmutableList> routes, GlobalStateObject state) {

    checkArgument(
        routes.size() == state.vehicles.size(),
        "There must be exactly one route for every vehicle, found %s routes with %s vehicles.",
        routes.size(), state.vehicles.size());

    final Set inputParcels = newHashSet(state.availableParcels);
    final Set outputParcels = newHashSet();
    for (int i = 0; i < routes.size(); i++) {
      final List route = routes.get(i);
      final Set routeSet = ImmutableSet.copyOf(route);
      checkArgument(routeSet.containsAll(state.vehicles.get(i).contents),
          "The route of vehicle %s doesn't visit all parcels in its cargo.", i);
      inputParcels.addAll(state.vehicles.get(i).contents);

      if (state.vehicles.get(i).destination != null) {
        checkArgument(
            route.get(0) == state.vehicles.get(i).destination,
            "The route of vehicle %s should start with its current destination: %s.",
            i, state.vehicles.get(i).destination);
      }

      for (final ParcelDTO p : route) {
        checkArgument(!outputParcels.contains(p),
            "Found a parcel which is already in another route: %s.", p);
        final int frequency = Collections.frequency(route, p);
        if (state.availableParcels.contains(p)) {
          // if the parcel is available, it needs to occur twice in
          // the route (once for pickup, once for delivery).
          checkArgument(
              frequency == 2,
              "Route %s: a parcel that is picked up needs to be delivered as well, so it should occur twice in the route, found %s occurence(s) of parcel %s.",
              i, frequency, p);
        } else {
          checkArgument(
              state.vehicles.get(i).contents.contains(p),
              "The parcel in this route is not available, which means it should be in the contents of this vehicle. Parcel: %s.",
              p);
          checkArgument(
              frequency == 1,
              "A parcel that is already in cargo should occur once in the route, found %s occurences of parcel %s.",
              frequency, p);
        }
      }
      outputParcels.addAll(route);
    }
    checkArgument(
        inputParcels.equals(outputParcels),
        "The number of distinct parcels in the routes should equal the number of parcels in the input, parcels that should be added in routes: %s, parcels that should be removed from routes: %s.",
        Sets.difference(inputParcels, outputParcels),
        Sets.difference(outputParcels, inputParcels));
    return routes;
  }

  private static class Validator implements Solver {
    private final Solver delegateSolver;

    Validator(Solver delegate) {
      delegateSolver = delegate;
    }

    @Override
    public ImmutableList> solve(GlobalStateObject state) {
      return validateOutputs(delegateSolver.solve(validateInputs(state)), state);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy