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

rinde.sim.pdptw.generator.Metrics Maven / Gradle / Ivy

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

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newLinkedHashSet;

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

import rinde.sim.core.graph.Point;
import rinde.sim.core.model.pdp.PDPScenarioEvent;
import rinde.sim.pdptw.common.AddParcelEvent;
import rinde.sim.pdptw.common.AddVehicleEvent;
import rinde.sim.pdptw.common.DynamicPDPTWScenario;
import rinde.sim.scenario.Scenario;
import rinde.sim.scenario.TimedEvent;
import rinde.sim.util.TimeWindow;

import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.math.DoubleMath;

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

  private Metrics() {}

  /**
   * Computes the absolute load at every time instance of the specified
   * scenario. Load is a measure of expected vehicle utilization.
   * @param s The {@link Scenario} to measure.
   * @return A list of load values. The value at index i indicates
   *         the load at time i. All values are always
   *          >= 0. All time instances not included in the list are
   *         assumed to have load 0.
   */
  public static ImmutableList measureLoad(Scenario s) {
    return measureLoad(s, 1);
  }

  static ImmutableList measureLoad(Scenario s, int numVehicles) {

    // FIXME should be possible to set the granularity of time. e.g. compute
    // load for every 1 second/minute/5minutes/hour/etc

    final double vehicleSpeed = getVehicleSpeed(s);
    final ImmutableList.Builder loadParts = ImmutableList.builder();
    for (final TimedEvent te : s.asList()) {
      if (te instanceof AddParcelEvent) {
        loadParts.addAll(measureLoad((AddParcelEvent) te, vehicleSpeed));
      }
    }
    return sum(0, loadParts.build(), numVehicles);
  }

  public static ImmutableList measureRelativeLoad(Scenario s) {
    final int numVehicles = getEventTypeCounts(s).count(
        PDPScenarioEvent.ADD_VEHICLE);
    return measureLoad(s, numVehicles);
  }

  public static ImmutableList measureLoad(AddParcelEvent event,
      double vehicleSpeed) {
    checkArgument(vehicleSpeed > 0d);
    checkArgument(
        event.parcelDTO.pickupTimeWindow.begin <= event.parcelDTO.deliveryTimeWindow.begin,
        "Delivery TW begin may not be before pickup TW begin.");
    checkArgument(
        event.parcelDTO.pickupTimeWindow.end <= event.parcelDTO.deliveryTimeWindow.end,
        "Delivery TW end may not be before pickup TW end.");

    // pickup lower bound,
    final long pickupLb = event.parcelDTO.pickupTimeWindow.begin;
    // pickup upper bound
    final long pickupUb = event.parcelDTO.pickupTimeWindow.end
        + event.parcelDTO.pickupDuration;
    final double pickupLoad = event.parcelDTO.pickupDuration
        / (double) (pickupUb - pickupLb);
    final LoadPart pickupPart = new LoadPart(pickupLb, pickupUb, pickupLoad);

    final long expectedTravelTime = travelTime(event.parcelDTO.pickupLocation,
        event.parcelDTO.destinationLocation, vehicleSpeed);
    // first possible departure time from pickup location
    final long travelLb = pickupLb + event.parcelDTO.pickupDuration;
    // latest possible arrival time at delivery location
    final long travelUb = Math.max(event.parcelDTO.deliveryTimeWindow.end,
        travelLb + expectedTravelTime);

    // checkArgument(travelUb - travelLb >= expectedTravelTime,
    // "The time windows should allow traveling from pickup to delivery.");

    final double travelLoad = expectedTravelTime
        / (double) (travelUb - travelLb);
    final LoadPart travelPart = new LoadPart(travelLb, travelUb, travelLoad);

    // delivery lower bound: the first possible time the delivery can start,
    // normally uses the start of the delivery TW, in case this is not
    // feasible we correct for the duration and travel time.
    final long deliveryLb = Math.max(event.parcelDTO.deliveryTimeWindow.begin,
        pickupLb + event.parcelDTO.pickupDuration + expectedTravelTime);
    // delivery upper bound: the latest possible time the delivery can end
    final long deliveryUb = Math.max(event.parcelDTO.deliveryTimeWindow.end,
        deliveryLb) + event.parcelDTO.deliveryDuration;
    final double deliveryLoad = event.parcelDTO.deliveryDuration
        / (double) (deliveryUb - deliveryLb);
    final LoadPart deliveryPart = new LoadPart(deliveryLb, deliveryUb,
        deliveryLoad);

    return ImmutableList.of(pickupPart, travelPart, deliveryPart);
  }

  static ImmutableList sum(long st, List parts, int num) {
    checkArgument(num >= 1);
    final ImmutableList.Builder builder = ImmutableList.builder();
    long i = st;
    final Set partSet = newLinkedHashSet(parts);
    while (!partSet.isEmpty()) {
      double currentLoadVal = 0d;
      final List toRemove = newArrayList();
      for (final LoadPart lp : partSet) {
        if (lp.isIn(i)) {
          currentLoadVal += lp.get(i);
        }
        if (!lp.isBeforeEnd(i)) {
          toRemove.add(lp);
        }
      }
      partSet.removeAll(toRemove);

      if (!partSet.isEmpty()) {
        if (num > 1) {
          currentLoadVal /= num;
        }
        builder.add(currentLoadVal);
        i++;
      }
    }
    return builder.build();
  }

  /**
   * Checks whether the vehicles defined for the specified scenario have the
   * same speed. If the speed is the same it is returned, otherwise an exception
   * is thrown.
   * @param s The {@link Scenario} to get the speed from.
   * @return The vehicle speed if all vehicles have the same speed.
   * @throws IllegalArgumentException if either: not all vehicles have the same
   *           speed, or there are no vehicles.
   */
  public static double getVehicleSpeed(Scenario s) {
    double vehicleSpeed = -1d;
    for (final TimedEvent te : s.asList()) {
      if (te instanceof AddVehicleEvent) {
        if (vehicleSpeed == -1d) {
          vehicleSpeed = ((AddVehicleEvent) te).vehicleDTO.speed;
        } else {
          checkArgument(
              vehicleSpeed == ((AddVehicleEvent) te).vehicleDTO.speed,
              "All vehicles are expected to have the same speed.");
        }
      }
    }
    checkArgument(vehicleSpeed > 0, "There are no vehicles in the scenario.");
    return vehicleSpeed;
  }

  public static ImmutableMultiset> getEventTypeCounts(Scenario s) {
    final ImmutableMultiset.Builder> occurences = ImmutableMultiset
        .builder();
    for (final TimedEvent te : s.asList()) {
      occurences.add(te.getEventType());
    }
    return occurences.build();
  }

  public static void checkTimeWindowStrictness(Scenario s) {
    final double speed = getVehicleSpeed(s);
    for (final TimedEvent te : s.asList()) {
      if (te instanceof AddParcelEvent) {
        Metrics.checkParcelTWStrictness((AddParcelEvent) te, speed);
      }
    }
  }

  /**
   * Checks whether the TWs are not unnecessarily big.
   * @param event
   * @param vehicleSpeed
   */
  public static void checkParcelTWStrictness(AddParcelEvent event,
      double vehicleSpeed) {
    final long firstDepartureTime = event.parcelDTO.pickupTimeWindow.begin
        + event.parcelDTO.pickupDuration;
    final long latestDepartureTime = event.parcelDTO.pickupTimeWindow.end
        + event.parcelDTO.pickupDuration;

    final long travelTime = travelTime(event.parcelDTO.pickupLocation,
        event.parcelDTO.destinationLocation, vehicleSpeed);

    checkArgument(
        event.parcelDTO.deliveryTimeWindow.begin >= firstDepartureTime
            + travelTime, "The begin of the delivery time window is too early.");
    checkArgument(
        latestDepartureTime + travelTime <= event.parcelDTO.deliveryTimeWindow.end,
        "The end of the pickup time window %s is too late.",
        event.parcelDTO.pickupTimeWindow.end);
  }

  public static ImmutableList getArrivalTimes(DynamicPDPTWScenario s) {
    final ImmutableList.Builder builder = ImmutableList.builder();
    for (final TimedEvent te : s.asList()) {
      if (te instanceof AddParcelEvent) {
        builder.add(te.time);
      }
    }
    return builder.build();
  }

  /**
   * Computes a histogram for the inputs. The result is a multiset with an entry
   * for each bin in ascending order, the count of each entry indicates the size
   * of the bin. A bin is indicated by its leftmost value, for example if the
   * binSize is 2 and the result contains
   * 4 with count 3 this means that there are
   * 3 values in range 2 ≤ x < 4.
   * @param input The values to compute the histogram of.
   * @param binSize The size of the bins.
   * @return An {@link ImmutableSortedMultiset} representing the histogram.
   */
  public static ImmutableSortedMultiset computeHistogram(
      Iterable input, double binSize) {
    final ImmutableSortedMultiset.Builder builder = ImmutableSortedMultiset
        .naturalOrder();
    for (final double d : input) {
      checkArgument(!Double.isInfinite(d) && !Double.isNaN(d),
          "Only finite numbers are accepted, found %s.", d);
      builder.add(Math.floor(d / binSize) * binSize);
    }
    return builder.build();
  }

  public static double measureUrgency(DynamicPDPTWScenario s) {

    long reactionTime = 0;
    int count = 0;
    for (final TimedEvent ev : s.asList()) {
      if (ev instanceof AddParcelEvent) {
        final AddParcelEvent ape = (AddParcelEvent) ev;
        reactionTime += ape.parcelDTO.pickupTimeWindow.end - ape.time;
        count++;
      }
    }
    return reactionTime / (double) count;
  }

  public static double measureDynamism(DynamicPDPTWScenario s) {
    return measureDynamism(convert(getOrderArrivalTimes(s)),
        s.getTimeWindow().end);
  }

  static ImmutableList convert(List in) {
    final ImmutableList.Builder builder = ImmutableList.builder();
    for (final Long l : in) {
      builder.add(new Double(l));
    }
    return builder.build();
  }

  // Best version as of March 6th, 2014
  public static double measureDynamism(Iterable arrivalTimes,
      double lengthOfDay) {
    final List times = newArrayList(arrivalTimes);
    checkArgument(times.size() >= 2,
        "At least two arrival times are required, found %s time(s).",
        times.size());
    for (final double time : times) {
      checkArgument(time >= 0 && time < lengthOfDay,
          "all specified times should be >= 0 and < %s. Found %s.",
          lengthOfDay, time);
    }
    Collections.sort(times);

    final int numEvents = times.size();

    // this is the expected interarrival time
    final double expectedInterArrivalTime = lengthOfDay
        / numEvents;

    // deviation to expectedInterArrivalTime
    double sumDeviation = 0;
    double maxDeviation = (numEvents - 1) * expectedInterArrivalTime;
    double prevDeviation = 0;
    for (int i = 0; i < numEvents - 1; i++) {
      // compute interarrival time
      final double delta = times.get(i + 1) - times.get(i);
      if (delta < expectedInterArrivalTime) {
        final double diff = expectedInterArrivalTime - delta;
        final double scaledPrev = (diff / expectedInterArrivalTime)
            * prevDeviation;
        final double cur = diff + scaledPrev;
        sumDeviation += cur;
        maxDeviation += scaledPrev;
        prevDeviation = cur;
      } else {
        prevDeviation = 0;
      }
    }
    return 1d - (sumDeviation / maxDeviation);
  }

  // FIXME move this method to common? and make sure everybody uses the same
  // definition?
  // speed = km/h
  // points in km
  // return time in minutes
  public static long travelTime(Point p1, Point p2, double speed) {
    return DoubleMath.roundToLong((Point.distance(p1, p2) / speed) * 60d,
        RoundingMode.CEILING);
  }

  /**
   * Returns an {@link ImmutableList} containing all service points of
   * {@link Scenario}. The scenario must contain {@link AddParcelEvent}s.
   * @param s The scenario to extract the points from.
   * @return A list containing all service points in order of occurrence in the
   *         scenario event list.
   */
  public static ImmutableList getServicePoints(Scenario s) {
    final ImmutableList.Builder builder = ImmutableList.builder();
    for (final TimedEvent se : s.asList()) {
      if (se instanceof AddParcelEvent) {
        builder.add(((AddParcelEvent) se).parcelDTO.pickupLocation);
        builder.add(((AddParcelEvent) se).parcelDTO.destinationLocation);
      }
    }
    return builder.build();
  }

  public static ImmutableList getOrderArrivalTimes(Scenario s) {
    final ImmutableList.Builder builder = ImmutableList.builder();
    for (final TimedEvent se : s.asList()) {
      if (se instanceof AddParcelEvent) {
        builder.add(((AddParcelEvent) se).parcelDTO.orderArrivalTime);
      }
    }
    return builder.build();
  }

  // to use for parts of the timeline to avoid excessively long list with
  // mostly 0s.
  static class LoadPart {
    private final double load;
    private final TimeWindow tw;

    LoadPart(long st, long end, double value) {
      tw = new TimeWindow(st, end);
      load = value;
    }

    public boolean isBeforeEnd(long i) {
      return tw.isBeforeEnd(i);
    }

    public boolean isIn(long i) {
      return tw.isIn(i);
    }

    long begin() {
      return tw.begin;
    }

    long end() {
      return tw.end;
    }

    long length() {
      return tw.length();
    }

    double get(long i) {
      if (tw.isIn(i)) {
        return load;
      }
      return 0d;
    }

    @Override
    public String toString() {
      return Objects.toStringHelper("LoadPart").add("begin", tw.begin)
          .add("end", tw.end).add("load", load).toString();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy