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

rinde.sim.core.graph.Graphs Maven / Gradle / Ivy

There is a newer version: 4.4.6
Show newest version
/**
 * 
 */
package rinde.sim.core.graph;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.Nullable;

import org.apache.commons.math3.random.RandomGenerator;

import com.google.common.base.Function;

/**
 * Utility class containing many methods for working with graphs.
 * @author Rinde van Lon 
 * @author Bartosz Michalik  - change in the
 *         graphs model
 */
public final class Graphs {

  private Graphs() {}

  /**
   * Create a path of connections on the specified {@link Graph} using the
   * specified {@link Point}s. If the points A, B, C are specified,
   * the two connections: A -> B and B -> C will be
   * added to the graph.
   * @param graph The graph to which the connections will be added.
   * @param path Points that will be treated as a path.
   * @param  The type of connection data.
   */
  public static  void addPath(Graph graph,
      Point... path) {
    for (int i = 1; i < path.length; i++) {
      graph.addConnection(path[i - 1], path[i]);
    }
  }

  /**
   * Create a path of bi-directional connections on the specified {@link Graph}
   * using the specified {@link Point}s. If the points A, B, C are
   * specified, the four connections: A -> B, B -> A,
   * B -> C and C -> B will be added to the graph.
   * @param graph The graph to which the connections will be added.
   * @param path Points that will be treated as a path.
   * @param  The type of connection data.
   */
  public static  void addBiPath(Graph graph,
      Point... path) {
    addPath(graph, path);
    final List list = Arrays.asList(path);
    Collections.reverse(list);
    addPath(graph, list.toArray(new Point[path.length]));
  }

  /**
   * Returns an unmodifiable view on the specified {@link Graph}.
   * @param graph A graph.
   * @param  The type of connection data.
   * @return An unmodifiable view on the graph.
   */
  public static  Graph unmodifiableGraph(
      Graph graph) {
    return new UnmodifiableGraph(graph);
  }

  /**
   * Returns an unmodifiable view on the specified {@link Connection}.
   * @param conn A connection.
   * @param  The type of connection data.
   * @return An unmodifiable view on the connection.
   */
  public static  Connection unmodifiableConnection(
      Connection conn) {
    return new UnmodifiableConnection(conn);
  }

  /**
   * Returns an unmodifiable view on the specified {@link ConnectionData}.
   * @param connData Connection data.
   * @param  The type of connection data.
   * @return An unmodifiable view on the connection data.
   */
  @SuppressWarnings("unchecked")
  @Nullable
  public static  E unmodifiableConnectionData(
      @Nullable E connData) {
    if (connData instanceof MultiAttributeData) {
      return (E) new UnmodifiableMultiAttributeEdgeData(
          (MultiAttributeData) connData);
    }
    return connData;
  }

  /**
   * Basic equals method.
   * @param g1 A graph.
   * @param g2 Another graph.
   * @param  The type of connection data.
   * @return true if the provided graphs are equal,
   *         false otherwise.
   */
  @SuppressWarnings("null")
  public static  boolean equals(
      Graph g1, Graph g2) {
    if (g1.getNumberOfNodes() != g2.getNumberOfNodes()) {
      return false;
    }
    if (g1.getNumberOfConnections() != g2.getNumberOfConnections()) {
      return false;
    }
    for (final Connection g1conn : g1.getConnections()) {
      if (!g2.hasConnection(g1conn.from, g1conn.to)) {
        return false;
      }
      final E g2connEdgeData = g2.connectionData(g1conn.from, g1conn.to);

      final boolean null1 = g1conn.getData() == null;
      final boolean null2 = g2connEdgeData == null;
      final int nullCount = (null1 ? 1 : 0) + (null2 ? 1 : 0);
      if ((nullCount == 0 && !g1conn.getData().equals(g2connEdgeData))
          || nullCount == 1) {
        return false;
      }
    }
    return true;

  }

  /**
   * Computes the shortest path based on the Euclidean distance.
   * @param graph The {@link Graph} on which the shortest path is searched.
   * @param from The start point of the path.
   * @param to The destination of the path.
   * @param  The type of connection data.
   * @return The shortest path that exists between from and
   *         to.
   */
  public static  List shortestPathEuclideanDistance(
      Graph graph, final Point from, final Point to) {
    return Graphs.shortestPath(graph, from, to, new Graphs.EuclidianDistance());
  }

  /**
   * A standard implementation of the A* algorithm.
   * 
   * @param graph The {@link Graph} which contains from and
   *          to.
   * @param from The start position
   * @param to The end position
   * @param h The {@link Heuristic} used in the A* implementation.
   * @param  The type of connection data.
   * @return The shortest path from from to to if it
   *         exists, otherwise a {@link PathNotFoundException} is thrown.
   * @throws PathNotFoundException if a path does not exist between
   *           from and to.
   * 
   * @author Rutger Claes
   * @author Rinde van Lon 
   */
  public static  List shortestPath(
      Graph graph, final Point from, final Point to, Graphs.Heuristic h) {
    if (!graph.containsNode(from)) {
      throw new IllegalArgumentException("from should be valid vertex. " + from);
    }

    // The set of nodes already evaluated.
    final Set closedSet = new LinkedHashSet();

    // Distance from start along optimal path.
    final Map gScore = new LinkedHashMap();
    gScore.put(from, 0d);

    // heuristic estimates
    final Map hScore = new LinkedHashMap();
    hScore.put(from, h.estimateCost(Point.distance(from, to)));

    // Estimated total distance from start to goal through y
    final SortedMap fScore = new TreeMap();
    fScore.put(h.estimateCost(Point.distance(from, to)), from);

    // The map of navigated nodes.
    final Map cameFrom = new LinkedHashMap();

    while (!fScore.isEmpty()) {
      final Point current = fScore.remove(fScore.firstKey());
      if (current.equals(to)) {
        final List result = new ArrayList();
        result.add(from);
        result.addAll(Graphs.reconstructPath(cameFrom, to));
        return result;
      }
      closedSet.add(current);
      for (final Point outgoingPoint : graph.getOutgoingConnections(current)) {
        if (closedSet.contains(outgoingPoint)) {
          continue;
        }

        // tentative_g_score := g_score[x] + dist_between(x,y)
        final double tgScore = gScore.get(current)
            + h.calculateCost(current, outgoingPoint);
        boolean tIsBetter = false;

        if (!fScore.values().contains(outgoingPoint)) {
          hScore.put(outgoingPoint,
              h.estimateCost(Point.distance(outgoingPoint, to)));
          tIsBetter = true;
        } else if (tgScore < gScore.get(outgoingPoint)) {
          tIsBetter = true;
        }

        if (tIsBetter) {
          cameFrom.put(outgoingPoint, current);
          gScore.put(outgoingPoint, tgScore);

          double fScoreValue = gScore.get(outgoingPoint)
              + hScore.get(outgoingPoint);
          while (fScore.containsKey(fScoreValue)) {
            fScoreValue = Double.longBitsToDouble(Double
                .doubleToLongBits(fScoreValue) + 1);
          }
          fScore.put(fScoreValue, outgoingPoint);
        }
      }
    }

    throw new PathNotFoundException("Cannot reach " + to + " from " + from);
  }

  /**
   * A method for finding the closest object to a point. If there is no object
   * null is returned instead.
   * @param pos The {@link Point} which is used as reference.
   * @param objects The {@link Collection} which is searched.
   * @param transformation A {@link Function} that transforms an object from
   *          objects into a {@link Point}, normally this means
   *          that the position of the object is retrieved.
   * @param  the type of object.
   * @return The closest object in objects to pos or
   *         null if no object exists.
   */
  @Nullable
  public static  T findClosestObject(Point pos, Collection objects,
      Function transformation) {
    double dist = Double.MAX_VALUE;
    T closest = null;
    for (final T obj : objects) {
      @Nullable
      final Point objPos = transformation.apply(obj);
      checkNotNull(objPos);
      final double currentDist = Point.distance(pos, objPos);
      if (currentDist < dist) {
        dist = currentDist;
        closest = obj;
      }
    }
    return closest;
  }

  /**
   * Searches the closest n objects to position pos in
   * collection objects using transformation.
   * @param pos The {@link Point} which is used as a reference point.
   * @param objects The list of objects which is searched.
   * @param transformation A function that transforms objects from
   *          objects to a point.
   * @param n The maximum number of objects to return where n must be >= 0.
   * @param  The type of object.
   * @return A list of objects that are closest to pos. The list is
   *         ordered such that the closest object appears first. An empty list
   *         is returned when objects is empty.
   */
  public static  List findClosestObjects(Point pos,
      Collection objects, Function transformation, int n) {
    checkArgument(n > 0, "n must be positive.");
    final List> objs = new ArrayList>();
    for (final T obj : objects) {
      @Nullable
      final Point objPos = transformation.apply(obj);
      checkNotNull(objPos);
      objs.add(new ObjectWithDistance(obj, Point.distance(pos, objPos)));
    }
    Collections.sort(objs);
    final List results = new ArrayList();
    for (final ObjectWithDistance o : objs.subList(0,
        Math.min(n, objs.size()))) {
      results.add(o.obj);
    }
    return results;
  }

  /**
   * Calculates the length of a path. The length is calculated by simply summing
   * the distances of every two neighboring positions.
   * @param path A list of {@link Point}s forming a path.
   * @return The total length of the path.
   */
  public static double pathLength(List path) {
    double dist = 0;
    for (int i = 1; i < path.size(); i++) {
      dist += Point.distance(path.get(i - 1), path.get(i));
    }
    return dist;
  }

  static List reconstructPath(final Map cameFrom,
      final Point end) {
    if (cameFrom.containsKey(end)) {
      final List path = reconstructPath(cameFrom, cameFrom.get(end));
      path.add(end);
      return path;
    }

    return new LinkedList();
  }

  /**
   * A heuristic can be used to direct the {@link #shortestPath} algorithm, it
   * determines the cost of traveling which should be minimized.
   * @author Rinde van Lon 
   */
  public interface Heuristic {
    /**
     * Can be used to estimate the cost of traveling a distance.
     * @param distance A distance.
     * @return The estimate of the cost.
     */
    double estimateCost(double distance);

    /**
     * Computes the cost of traveling over the connection as specified by the
     * provided points.
     * @param from Start point of a connection.
     * @param to End point of a connection.
     * @return The cost of traveling.
     */
    double calculateCost(Point from, Point to);
  }

  static class ObjectWithDistance implements
      Comparable> {
    final double dist;
    final T obj;

    ObjectWithDistance(T pObj, double pDist) {
      obj = pObj;
      dist = pDist;
    }

    @Override
    public int compareTo(ObjectWithDistance o) {
      return Double.compare(dist, o.dist);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(@Nullable Object o) {
      if (o == null) {
        return false;
      }
      if (o.getClass() == ObjectWithDistance.class) {
        return compareTo((ObjectWithDistance) o) == 0;
      }
      return false;
    }
  }

  private static class UnmodifiableMultiAttributeEdgeData extends
      MultiAttributeData {

    private final MultiAttributeData original;

    UnmodifiableMultiAttributeEdgeData(MultiAttributeData pOriginal) {
      super(-1);
      original = pOriginal;
    }

    @Override
    public double getLength() {
      return original.getLength();
    }

    @Override
    public double getMaxSpeed() {
      return original.getMaxSpeed();
    }

    @Override
    public Map getAttributes() {
      return original.getAttributes();
    }

    @Nullable
    @Override
    public  E get(String key, Class type) {
      return original.get(key, type);
    }

    @Override
    public double setMaxSpeed(double maxSpeed) {
      throw new UnsupportedOperationException();
    }

    @Override
    public  void put(String key, E value) {
      throw new UnsupportedOperationException();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      return original.equals(obj);
    }

    @Override
    public int hashCode() {
      return original.hashCode();
    }

  }

  private static final class UnmodifiableConnection
      extends Connection {
    private final Connection original;

    UnmodifiableConnection(Connection c) {
      super(c.from, c.to, null);
      original = c;
    }

    @Override
    public void setData(E data) {
      throw new UnsupportedOperationException();
    }

    @Override
    @Nullable
    public E getData() {
      final E d = original.getData();
      if (d == null) {
        return null;
      }
      return Graphs.unmodifiableConnectionData(d);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      return original.equals(obj);
    }

    @Override
    public int hashCode() {
      return original.hashCode();
    }

    @Override
    public String toString() {
      return original.toString();
    }
  }

  private static class UnmodifiableGraph implements
      Graph {
    final Graph delegate;

    UnmodifiableGraph(Graph pDelegate) {
      delegate = pDelegate;
    }

    @Override
    public boolean containsNode(Point node) {
      return delegate.containsNode(node);
    }

    @Override
    public Collection getOutgoingConnections(Point node) {
      return Collections.unmodifiableCollection(delegate
          .getOutgoingConnections(node));
    }

    @Override
    public Collection getIncomingConnections(Point node) {
      return Collections.unmodifiableCollection(delegate
          .getIncomingConnections(node));
    }

    @Override
    public boolean hasConnection(Point from, Point to) {
      return delegate.hasConnection(from, to);
    }

    @Override
    public int getNumberOfConnections() {
      return delegate.getNumberOfConnections();
    }

    @Override
    public List> getConnections() {
      final List> conn = delegate.getConnections();
      final List> unmodConn = new ArrayList>();
      for (final Connection c : conn) {
        unmodConn.add(unmodifiableConnection(c));
      }
      return Collections.unmodifiableList(unmodConn);
    }

    @Override
    public int getNumberOfNodes() {
      return delegate.getNumberOfNodes();
    }

    @Override
    public Set getNodes() {
      return Collections.unmodifiableSet(delegate.getNodes());
    }

    @Override
    public double connectionLength(Point from, Point to) {
      return delegate.connectionLength(from, to);
    }

    @Override
    public boolean isEmpty() {
      return delegate.isEmpty();
    }

    @Override
    public void addConnection(Point from, Point to) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void merge(Graph other) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void addConnections(Collection> connections) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void removeNode(Point node) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void removeConnection(Point from, Point to) {
      throw new UnsupportedOperationException();
    }

    @SuppressWarnings({ "unchecked" })
    @Override
    public boolean equals(@Nullable Object other) {
      return other instanceof Graph ? Graphs.equals(this, (Graph) other)
          : false;
    }

    @Override
    public int hashCode() {
      return delegate.hashCode();
    }

    @Nullable
    @Override
    public E connectionData(Point from, Point to) {
      return unmodifiableConnectionData(delegate.connectionData(from, to));
    }

    @Override
    public void addConnection(Point from, Point to, E edgeData) {
      throw new UnsupportedOperationException();
    }

    @Override
    public void addConnection(Connection connection) {
      throw new UnsupportedOperationException();
    }

    @Override
    public E setConnectionData(Point from, Point to, E edgeData) {
      throw new UnsupportedOperationException();
    }

    @Override
    public Point getRandomNode(RandomGenerator generator) {
      return delegate.getRandomNode(generator);
    }

    @Override
    public Connection getConnection(Point from, Point to) {
      return unmodifiableConnection(delegate.getConnection(from, to));
    }

  }

  static class EuclidianDistance implements Graphs.Heuristic {

    @Override
    public double calculateCost(final Point from, Point to) {
      return Point.distance(from, to);
    }

    @Override
    public double estimateCost(final double distance) {
      return distance;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy