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

com.github.rinde.rinsim.scenario.measure.Metrics Maven / Gradle / Ivy

There is a newer version: 4.4.6
Show newest version
/*
 * 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.scenario.measure;

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

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.annotation.Nullable;

import org.apache.commons.math3.stat.descriptive.StatisticalSummary;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

import com.github.rinde.rinsim.core.model.pdp.IParcel;
import com.github.rinde.rinsim.core.model.pdp.ParcelDTO;
import com.github.rinde.rinsim.geom.Point;
import com.github.rinde.rinsim.pdptw.common.AddParcelEvent;
import com.github.rinde.rinsim.pdptw.common.AddVehicleEvent;
import com.github.rinde.rinsim.scenario.Scenario;
import com.github.rinde.rinsim.scenario.TimedEvent;
import com.github.rinde.rinsim.scenario.generator.ScenarioGenerator;
import com.github.rinde.rinsim.scenario.generator.ScenarioGenerator.TravelTimes;
import com.github.rinde.rinsim.util.TimeWindow;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSortedMultiset;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Multiset;

/**
 * @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.
   */
  static ImmutableList measureLoad(Scenario s) {
    return measureLoad(s, 1);
  }

  static ImmutableList measureLoad(Scenario s, int numVehicles) {
    final TravelTimes tt = ScenarioGenerator.createTravelTimes(s);
    final ImmutableList.Builder loadParts = ImmutableList.builder();
    for (final TimedEvent te : s.getEvents()) {
      if (te instanceof AddParcelEvent) {
        loadParts.addAll(measureLoad((AddParcelEvent) te, tt));
      }
    }
    return sum(0, loadParts.build(), numVehicles);
  }

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

  static ImmutableList measureLoad(AddParcelEvent event,
      TravelTimes tt) {
    checkArgument(
      event.getParcelDTO().getPickupTimeWindow().begin() <= event.getParcelDTO()
        .getDeliveryTimeWindow().begin(),
      "Delivery TW begin may not be before pickup TW begin.");
    checkArgument(
      event.getParcelDTO().getPickupTimeWindow().end() <= event.getParcelDTO()
        .getDeliveryTimeWindow().end(),
      "Delivery TW end may not be before pickup TW end.");

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

    final long expectedTravelTime = tt.getShortestTravelTime(
      event.getParcelDTO().getPickupLocation(),
      event.getParcelDTO().getDeliveryLocation());
    // first possible departure time from pickup location
    final long travelLb = pickupLb + event.getParcelDTO().getPickupDuration();
    // latest possible arrival time at delivery location
    final long travelUb = Math.max(
      event.getParcelDTO().getDeliveryTimeWindow().end(),
      travelLb + expectedTravelTime);

    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.getParcelDTO().getDeliveryTimeWindow().begin(),
      pickupLb + event.getParcelDTO().getPickupDuration()
        + expectedTravelTime);
    // delivery upper bound: the latest possible time the delivery can end
    final long deliveryUb = Math.max(
      event.getParcelDTO().getDeliveryTimeWindow().end(),
      deliveryLb) + event.getParcelDTO().getDeliveryDuration();
    final double deliveryLoad = event.getParcelDTO().getDeliveryDuration()
      / (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.getEvents()) {
      if (te instanceof AddVehicleEvent) {
        if (vehicleSpeed == -1d) {
          vehicleSpeed = ((AddVehicleEvent) te).getVehicleDTO().getSpeed();
        } else {
          checkArgument(
            vehicleSpeed == ((AddVehicleEvent) te).getVehicleDTO().getSpeed(),
            "All vehicles are expected to have the same speed.");
        }
      }
    }
    checkArgument(vehicleSpeed > 0, "There are no vehicles in the scenario.");
    return vehicleSpeed;
  }

  /**
   * Computes the number of occurrences of each event type in the specified
   * {@link Scenario}.
   * @param s The scenario to check.
   * @return A {@link ImmutableMultiset} of event types.
   */
  public static ImmutableMultiset> getEventTypeCounts(Scenario s) {
    final Multiset> set = LinkedHashMultiset.create();
    for (final TimedEvent te : s.getEvents()) {
      set.add(te.getClass());
    }
    final List> toMove = new ArrayList<>();
    for (final Class c : set.elementSet()) {
      if (!Modifier.isPublic(c.getModifiers())
        && TimedEvent.class.isAssignableFrom(c.getSuperclass())
        && !set.contains(c.getSuperclass())) {
        toMove.add(c);
      }
    }
    for (final Class c : toMove) {
      set.add(c.getSuperclass(), set.count(c));
      set.remove(c, set.count(c));
    }
    return ImmutableMultiset.copyOf(set);
  }

  /**
   * Checks the time window strictness of the specified {@link Scenario}. A time
   * window is strict if:
   * 
    *
  • dtw.begin ≥ ptw.begin + pd + travelTime
  • *
  • ptw.end + pd + travelTime ≤ dtw.end
  • *
* Where: *
    *
  • dtw is {@link ParcelDTO#getDeliveryTimeWindow()}
  • *
  • ptw is {@link ParcelDTO#getPickupTimeWindow()}
  • *
  • pd is {@link ParcelDTO#getPickupDuration()}
  • *
  • travelTime is the time to travel the shortest path from * {@link ParcelDTO#getPickupLocation()} to * {@link ParcelDTO#getDeliveryLocation()}
  • *
* * @param s The scenario to check. */ public static void checkTimeWindowStrictness(Scenario s) { final TravelTimes tt = ScenarioGenerator.createTravelTimes(s); for (final TimedEvent te : s.getEvents()) { if (te instanceof AddParcelEvent) { checkParcelTWStrictness((AddParcelEvent) te, tt); } } } /* * Checks whether the TWs are not unnecessarily big. */ static void checkParcelTWStrictness(AddParcelEvent event, TravelTimes travelTimes) { final long firstDepartureTime = event.getParcelDTO() .getPickupTimeWindow().begin() + event.getParcelDTO().getPickupDuration(); final long latestDepartureTime = event.getParcelDTO() .getPickupTimeWindow().end() + event.getParcelDTO().getPickupDuration(); final double travelTime = travelTimes.getShortestTravelTime( event.getParcelDTO().getPickupLocation(), event.getParcelDTO().getDeliveryLocation()); checkArgument( event.getParcelDTO().getDeliveryTimeWindow().begin() >= firstDepartureTime + travelTime, "The begin of the delivery time window (%s) is too early, " + "should be >= %s.", event.getParcelDTO().getDeliveryTimeWindow(), firstDepartureTime + travelTime); checkArgument( latestDepartureTime + travelTime <= event.getParcelDTO() .getDeliveryTimeWindow().end(), "The end of the pickup time window %s is too late, or end of delivery " + "is too early.", event.getParcelDTO().getPickupTimeWindow().end()); } /** * Collects all event arrival times of the specified {@link Scenario}. * @param s The scenario. * @return {@link ImmutableList} containing event arrival times. */ public static ImmutableList getArrivalTimes(Scenario s) { final ImmutableList.Builder builder = ImmutableList.builder(); for (final TimedEvent te : s.getEvents()) { if (te instanceof AddParcelEvent) { builder.add(te.getTime()); } } 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(); } static long pickupUrgency(AddParcelEvent event) { return pickupUrgency(event.getParcelDTO()); } static long pickupUrgency(IParcel dto) { return dto.getPickupTimeWindow().end() - dto.getOrderAnnounceTime(); } enum Urgency implements Function { PICKUP { @Override @Nullable public Long apply(@Nullable AddParcelEvent input) { return pickupUrgency(verifyNotNull(input)); } } } static StatisticalSummary toStatisticalSummary( Iterable values) { final SummaryStatistics ss = new SummaryStatistics(); for (final Number n : values) { ss.addValue(n.doubleValue()); } return ss.getSummary(); } /** * Computes a {@link StatisticalSummary} object for all urgency values of a * {@link Scenario}. * @param s The scenario to measure. * @return A statistical summary of the urgency values in the specified * scenario. */ public static StatisticalSummary measureUrgency(Scenario s) { final List urgencyValues = FluentIterable.from(s.getEvents()) .filter(AddParcelEvent.class) .transform(Urgency.PICKUP) .toList(); return toStatisticalSummary(urgencyValues); } /** * Measures the degree of dynamism of the specified scenario. * @param s The scenario to measure. * @return A double in range [0,1]. */ public static double measureDynamism(Scenario s) { return measureDynamism(s, s.getTimeWindow().end()); } /** * Measures the degree of dynamism of the specified scenario using the * specified length of day. * @param s The scenario to measure. * @param lengthOfDay The length of the day. * @return A double in range [0,1]. */ public static double measureDynamism(Scenario s, long lengthOfDay) { return measureDynamism(convert(getOrderArrivalTimes(s)), lengthOfDay); } // Best version as of March 6th, 2014 /** * Computes degree of dynamism of the specified arrival times and length of * day. * @param arrivalTimes A list of event arrival times. * @param lengthOfDay The length of the day. * @return A number in range [0, 1]. */ 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; } /** * 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.getEvents()) { if (se instanceof AddParcelEvent) { builder.add(((AddParcelEvent) se).getParcelDTO().getPickupLocation()); builder.add(((AddParcelEvent) se).getParcelDTO().getDeliveryLocation()); } } return builder.build(); } static ImmutableList getOrderArrivalTimes(Scenario s) { final ImmutableList.Builder builder = ImmutableList.builder(); for (final TimedEvent se : s.getEvents()) { if (se instanceof AddParcelEvent) { builder .add(((AddParcelEvent) se).getParcelDTO().getOrderAnnounceTime()); } } return builder.build(); } static ImmutableList convert(List in) { final ImmutableList.Builder builder = ImmutableList.builder(); for (final Long l : in) { builder.add(new Double(l)); } 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 = TimeWindow.create(st, end); load = value; } boolean isBeforeEnd(long i) { return tw.isBeforeEnd(i); } 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 MoreObjects.toStringHelper("LoadPart").add("begin", tw.begin()) .add("end", tw.end()).add("load", load).toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy