@Override
  public List<GraphPath> plan(
      String fromPlace,
      String toPlace,
      List<String> intermediates,
      Date targetTime,
      TraverseOptions options) {

    if (options.getModes().contains(TraverseMode.TRANSIT)) {
      throw new UnsupportedOperationException("TSP is not supported for transit trips");
    }

    ArrayList<String> notFound = new ArrayList<String>();
    Vertex fromVertex = getVertexForPlace(fromPlace, options);
    if (fromVertex == null) {
      notFound.add("from");
    }
    Vertex toVertex = getVertexForPlace(toPlace, options);
    if (toVertex == null) {
      notFound.add("to");
    }
    ArrayList<Vertex> intermediateVertices = new ArrayList<Vertex>();

    int i = 0;
    for (String intermediate : intermediates) {
      Vertex vertex = getVertexForPlace(intermediate, options);
      if (vertex == null) {
        notFound.add("intermediate." + i);
      } else {
        intermediateVertices.add(vertex);
      }
      i += 1;
    }

    if (notFound.size() > 0) {
      throw new VertexNotFoundException(notFound);
    }

    if (_graphService.getCalendarService() != null)
      options.setCalendarService(_graphService.getCalendarService());

    options.setTransferTable(_graphService.getGraph().getTransferTable());
    GraphPath path =
        _routingService.route(
            fromVertex,
            toVertex,
            intermediateVertices,
            (int) (targetTime.getTime() / 1000),
            options);

    return Arrays.asList(path);
  }
  /** Build the weight table, parallelized according to the number of processors */
  public void buildTable() {
    ArrayList<TransitStop> stopVertices;

    LOG.debug("Number of vertices: " + g.getVertices().size());
    stopVertices = new ArrayList<TransitStop>();
    for (Vertex gv : g.getVertices())
      if (gv instanceof TransitStop) stopVertices.add((TransitStop) gv);
    int nStops = stopVertices.size();

    stopIndices = new IdentityHashMap<Vertex, Integer>(nStops);
    for (int i = 0; i < nStops; i++) stopIndices.put(stopVertices.get(i), i);
    LOG.debug("Number of stops: " + nStops);

    table = new float[nStops][nStops];
    for (float[] row : table) Arrays.fill(row, Float.POSITIVE_INFINITY);

    LOG.debug("Performing search at each transit stop.");

    int nThreads = Runtime.getRuntime().availableProcessors();
    LOG.debug("number of threads: " + nThreads);
    ArrayBlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<Runnable>(nStops);
    ThreadPoolExecutor threadPool =
        new ThreadPoolExecutor(nThreads, nThreads, 10, TimeUnit.SECONDS, taskQueue);
    GenericObjectPool heapPool =
        new GenericObjectPool(new PoolableBinHeapFactory<State>(g.getVertices().size()), nThreads);

    // make one heap and recycle it
    TraverseOptions options = new TraverseOptions();
    options.speed = maxWalkSpeed;
    final double MAX_WEIGHT = 60 * 60 * options.walkReluctance;
    final double OPTIMISTIC_BOARD_COST = options.boardCost;

    // create a task for each transit stop in the graph
    ArrayList<Callable<Void>> tasks = new ArrayList<Callable<Void>>();
    for (TransitStop origin : stopVertices) {
      SPTComputer task =
          new SPTComputer(heapPool, options, MAX_WEIGHT, OPTIMISTIC_BOARD_COST, origin);
      tasks.add(task);
    }
    try {
      // invoke all of tasks.
      threadPool.invokeAll(tasks);
      threadPool.shutdown();
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    }
    floyd();
  }
  /**
   * Get polygons covering the components of the graph. The largest component (in terms of number of
   * nodes) will not overlap any other components (it will have holes); the others may overlap each
   * other.
   *
   * @param modes
   * @return
   */
  @Secured({"ROLE_USER"})
  @GET
  @Path("/polygons")
  @Produces({MediaType.APPLICATION_JSON})
  public GraphComponentPolygons getComponentPolygons(
      @DefaultValue("TRANSIT,WALK") @QueryParam("modes") TraverseModeSet modes,
      @QueryParam(RequestInf.DATE) String date,
      @QueryParam(RequestInf.TIME) String time,
      @DefaultValue("") @QueryParam(RequestInf.BANNED_ROUTES) String bannedRoutes) {

    TraverseOptions options = new TraverseOptions(modes);
    options.bannedRoutes = new HashSet<RouteSpec>();
    if (bannedRoutes.length() > 0) {
      for (String element : bannedRoutes.split(",")) {
        String[] routeSpec = element.split("_", 2);
        if (routeSpec.length != 2) {
          throw new IllegalArgumentException("AgencyId or routeId not set in bannedRoutes list");
        }
        options.bannedRoutes.add(new RouteSpec(routeSpec[0], routeSpec[1]));
      }
    }

    long dateTime = DateUtils.toDate(date, time).getTime();
    if (cachedPolygons == null || dateTime != cachedDateTime || !options.equals(cachedOptions)) {
      cachedOptions = options;
      cachedDateTime = dateTime;
      Graph graph = graphService.getGraph();
      if (graphService.getCalendarService() != null) {
        options.setCalendarService(graphService.getCalendarService());
      }
      options.setServiceDays(dateTime, graph.getAgencyIds());
      cachedPolygons = AnalysisUtils.getComponentPolygons(graph, options, dateTime);
    }

    GraphComponentPolygons out = new GraphComponentPolygons();
    out.components = new ArrayList<GraphComponent>();

    for (Geometry geometry : cachedPolygons) {
      GraphComponent component = new GraphComponent();
      component.polygon = geometry;
      out.components.add(component);
    }

    return out;
  }
  @Override
  public List<GraphPath> plan(
      String fromPlace,
      String toPlace,
      Date targetTime,
      TraverseOptions options,
      int nItineraries) {

    ArrayList<String> notFound = new ArrayList<String>();
    Vertex fromVertex = getVertexForPlace(fromPlace, options);
    if (fromVertex == null) {
      notFound.add("from");
    }
    Vertex toVertex = getVertexForPlace(toPlace, options);
    if (toVertex == null) {
      notFound.add("to");
    }

    if (notFound.size() > 0) {
      throw new VertexNotFoundException(notFound);
    }

    Vertex origin = null;
    Vertex target = null;

    if (options.isArriveBy()) {
      origin = toVertex;
      target = fromVertex;
    } else {
      origin = fromVertex;
      target = toVertex;
    }

    State state = new State((int) (targetTime.getTime() / 1000), origin, options);

    return plan(state, target, nItineraries);
  }
示例#5
0
 private TraverseResult tryWalkBike(State s0, TraverseOptions options, boolean back) {
   if (options.getModes().contains(TraverseMode.BICYCLE)) {
     return doTraverse(s0, options.getWalkingOptions(), back);
   }
   return null;
 }
  @Override
  public List<GraphPath> plan(State origin, Vertex target, int nItineraries) {

    Date targetTime = new Date(origin.getTime() * 1000);
    TraverseOptions options = origin.getOptions();

    if (_graphService.getCalendarService() != null)
      options.setCalendarService(_graphService.getCalendarService());
    options.setTransferTable(_graphService.getGraph().getTransferTable());
    options.setServiceDays(targetTime.getTime() / 1000);
    if (options.getModes().getTransit()
        && !_graphService.getGraph().transitFeedCovers(targetTime)) {
      // user wants a path through the transit network,
      // but the date provided is outside those covered by the transit feed.
      throw new TransitTimesException();
    }
    // decide which A* heuristic to use
    options.remainingWeightHeuristic =
        _remainingWeightHeuristicFactory.getInstanceForSearch(options, target);
    LOG.debug("Applied A* heuristic: {}", options.remainingWeightHeuristic);

    // If transit is not to be used, disable walk limit and only search for one itinerary.
    if (!options.getModes().getTransit()) {
      nItineraries = 1;
      options.setMaxWalkDistance(Double.MAX_VALUE);
    }

    ArrayList<GraphPath> paths = new ArrayList<GraphPath>();

    // The list of options specifying various modes, banned routes, etc to try for multiple
    // itineraries
    Queue<TraverseOptions> optionQueue = new LinkedList<TraverseOptions>();
    optionQueue.add(options);

    /* if the user wants to travel by transit, create a bus-only set of options */
    if (options.getModes().getTrainish() && options.getModes().contains(TraverseMode.BUS)) {
      TraverseOptions busOnly = options.clone();
      busOnly.setModes(options.getModes().clone());
      busOnly.getModes().setTrainish(false);
      // Moved inside block to avoid double insertion in list ? (AMB)
      // optionQueue.add(busOnly);
    }

    double maxWeight = Double.MAX_VALUE;
    long maxTime = options.isArriveBy() ? 0 : Long.MAX_VALUE;
    while (paths.size() < nItineraries) {
      options = optionQueue.poll();
      if (options == null) {
        break;
      }
      StateEditor editor = new StateEditor(origin, null);
      editor.setTraverseOptions(options);
      origin = editor.makeState();

      // options.worstTime = maxTime;
      // options.maxWeight = maxWeight;
      long searchBeginTime = System.currentTimeMillis();
      LOG.debug("BEGIN SEARCH");
      List<GraphPath> somePaths = _routingService.route(origin, target);
      LOG.debug("END SEARCH {} msec", System.currentTimeMillis() - searchBeginTime);
      if (maxWeight == Double.MAX_VALUE) {
        /*
         * the worst trip we are willing to accept is at most twice as bad or twice as long.
         */
        if (somePaths.isEmpty()) {
          // if there is no first path, there won't be any other paths
          return null;
        }
        GraphPath path = somePaths.get(0);
        long duration = path.getDuration();
        LOG.debug("Setting max time and weight for subsequent searches.");
        LOG.debug("First path start time:  {}", path.getStartTime());
        maxTime =
            path.getStartTime() + MAX_TIME_FACTOR * (options.isArriveBy() ? -duration : duration);
        LOG.debug("First path duration:  {}", duration);
        LOG.debug("Max time set to:  {}", maxTime);
        maxWeight = path.getWeight() * MAX_WEIGHT_FACTOR;
        LOG.debug("Max weight set to:  {}", maxWeight);
      }
      if (somePaths.isEmpty()) {
        LOG.debug("NO PATHS FOUND");
        continue;
      }
      for (GraphPath path : somePaths) {
        if (!paths.contains(path)) {
          // DEBUG
          // path.dump();
          paths.add(path);
          // now, create a list of options, one with each trip in this journey banned.

          LOG.debug("New trips: {}", path.getTrips());
          TraverseOptions newOptions = options.clone();
          for (AgencyAndId trip : path.getTrips()) {
            newOptions.bannedTrips.add(trip);
          }

          if (!optionQueue.contains(newOptions)) {
            optionQueue.add(newOptions);
          }
          /*
           * // now, create a list of options, one with each route in this trip banned. //
           * the HashSet banned is not strictly necessary as the optionsQueue will //
           * already remove duplicate options, but it might be slightly faster as //
           * hashing TraverseOptions is slow. LOG.debug("New routespecs: {}",
           * path.getRouteSpecs()); for (RouteSpec spec : path.getRouteSpecs()) {
           * TraverseOptions newOptions = options.clone();
           * newOptions.bannedRoutes.add(spec); if (!optionQueue.contains(newOptions)) {
           * optionQueue.add(newOptions); } }
           */
        }
      }
      LOG.debug("{} / {} itineraries", paths.size(), nItineraries);
    }
    if (paths.size() == 0) {
      return null;
    }
    // We order the list of returned paths by the time of arrival or departure (not path duration)
    Collections.sort(paths, new PathComparator(origin.getOptions().isArriveBy()));
    return paths;
  }
示例#7
0
  public void testTriangle() {
    Coordinate c1 = new Coordinate(-122.575033, 45.456773);
    Coordinate c2 = new Coordinate(-122.576668, 45.451426);

    Vertex v1 = new Vertex("v1", c1, null);
    Vertex v2 = new Vertex("v2", c2, null);

    GeometryFactory factory = new GeometryFactory();
    LineString geometry = factory.createLineString(new Coordinate[] {c1, c2});

    double length = 650.0;

    PlainStreetEdge testStreet =
        new PlainStreetEdge(
            v1, v2, geometry, "Test Lane", length, StreetTraversalPermission.ALL, false);
    testStreet.setBicycleSafetyEffectiveLength(length * 0.74); // a safe street

    Coordinate[] profile =
        new Coordinate[] {
          new Coordinate(0, 0), // slope = 0.1
          new Coordinate(length / 2, length / 20.0),
          new Coordinate(length, 0) // slope = -0.1
        };
    PackedCoordinateSequence elev = new PackedCoordinateSequence.Double(profile);
    testStreet.setElevationProfile(elev);

    double trueLength = ElevationUtils.getLengthMultiplierFromElevation(elev) * length;
    testStreet.setSlopeSpeedEffectiveLength(trueLength); // normalize length

    SlopeCosts costs = ElevationUtils.getSlopeCosts(elev, "test");

    TraverseOptions options = new TraverseOptions(TraverseMode.BICYCLE);
    options.optimizeFor = OptimizeType.TRIANGLE;
    options.speed = 6.0;
    options.walkReluctance = 1;

    options.setTriangleSafetyFactor(0);
    options.setTriangleSlopeFactor(0);
    options.setTriangleTimeFactor(1);
    State startState = new State(v1, options);

    State result = testStreet.traverse(startState);
    double timeWeight = result.getWeight();
    double expectedSpeedWeight = trueLength / options.speed;
    assertEquals(expectedSpeedWeight, timeWeight);

    options.setTriangleSafetyFactor(0);
    options.setTriangleSlopeFactor(1);
    options.setTriangleTimeFactor(0);
    startState = new State(v1, options);
    result = testStreet.traverse(startState);
    double slopeWeight = result.getWeight();
    assertTrue(length * 1.5 / options.speed < slopeWeight);
    assertTrue(length * 1.5 * 10 / options.speed > slopeWeight);

    options.setTriangleSafetyFactor(1);
    options.setTriangleSlopeFactor(0);
    options.setTriangleTimeFactor(0);
    startState = new State(v1, options);
    result = testStreet.traverse(startState);
    double safetyWeight = result.getWeight();
    double slopeSafety = costs.slopeSafetyCost;
    double expectedSafetyWeight = (trueLength * 0.74 + slopeSafety) / options.speed;
    assertTrue(expectedSafetyWeight - safetyWeight < 0.00001);

    final double ONE_THIRD = 1 / 3.0;
    options.setTriangleSafetyFactor(ONE_THIRD);
    options.setTriangleSlopeFactor(ONE_THIRD);
    options.setTriangleTimeFactor(ONE_THIRD);
    startState = new State(v1, options);
    result = testStreet.traverse(startState);
    double averageWeight = result.getWeight();
    assertTrue(
        Math.abs(
                safetyWeight * ONE_THIRD
                    + slopeWeight * ONE_THIRD
                    + timeWeight * ONE_THIRD
                    - averageWeight)
            < 0.00000001);
  }
  @Override
  public List<GraphPath> plan(State origin, Vertex target, int nItineraries) {

    TraverseOptions options = origin.getOptions();

    if (_graphService.getCalendarService() != null)
      options.setCalendarService(_graphService.getCalendarService());
    options.setTransferTable(_graphService.getGraph().getTransferTable());

    options.setServiceDays(origin.getTime(), _graphService.getGraph().getAgencyIds());
    if (options.getModes().getTransit()
        && !_graphService.getGraph().transitFeedCovers(new Date(origin.getTime() * 1000))) {
      // user wants a path through the transit network,
      // but the date provided is outside those covered by the transit feed.
      throw new TransitTimesException();
    }

    // always use the bidirectional heuristic because the others are not precise enough
    RemainingWeightHeuristic heuristic =
        new BidirectionalRemainingWeightHeuristic(_graphService.getGraph());

    // the states that will eventually be turned into paths and returned
    List<State> returnStates = new LinkedList<State>();

    // Populate any extra edges
    final ExtraEdgesStrategy extraEdgesStrategy = options.extraEdgesStrategy;
    OverlayGraph extraEdges = new OverlayGraph();
    extraEdgesStrategy.addEdgesFor(extraEdges, origin.getVertex());
    extraEdgesStrategy.addEdgesFor(extraEdges, target);

    BinHeap<State> pq = new BinHeap<State>();
    //        List<State> boundingStates = new ArrayList<State>();

    // initialize heuristic outside loop so table can be reused
    heuristic.computeInitialWeight(origin, target);

    // increase maxWalk repeatedly in case hard limiting is in use
    WALK:
    for (double maxWalk = options.getMaxWalkDistance();
        maxWalk < 100000 && returnStates.isEmpty();
        maxWalk *= 2) {
      LOG.debug("try search with max walk {}", maxWalk);
      // increase maxWalk if settings make trip impossible
      if (maxWalk
          < Math.min(
              origin.getVertex().distance(target),
              origin.getVertex().getDistanceToNearestTransitStop()
                  + target.getDistanceToNearestTransitStop())) continue WALK;
      options.setMaxWalkDistance(maxWalk);
      // reinitialize states for each retry
      HashMap<Vertex, List<State>> states = new HashMap<Vertex, List<State>>();
      pq.reset();
      pq.insert(origin, 0);
      long startTime = System.currentTimeMillis();
      long endTime = startTime + (int) (_timeouts[0] * 1000);
      LOG.debug("starttime {} endtime {}", startTime, endTime);
      QUEUE:
      while (!pq.empty()) {

        if (System.currentTimeMillis() > endTime) {
          LOG.debug("timeout at {} msec", System.currentTimeMillis() - startTime);
          if (returnStates.isEmpty()) continue WALK;
          else {
            storeMemory();
            break WALK;
          }
        }

        State su = pq.extract_min();

        //                for (State bs : boundingStates) {
        //                    if (eDominates(bs, su)) {
        //                        continue QUEUE;
        //                    }
        //                }

        Vertex u = su.getVertex();

        if (traverseVisitor != null) {
          traverseVisitor.visitVertex(su);
        }

        if (u.equals(target)) {
          //                    boundingStates.add(su);
          returnStates.add(su);
          if (!options.getModes().getTransit()) break QUEUE;
          // options should contain max itineraries
          if (returnStates.size() >= _maxPaths) break QUEUE;
          if (returnStates.size() < _timeouts.length) {
            endTime = startTime + (int) (_timeouts[returnStates.size()] * 1000);
            LOG.debug(
                "{} path, set timeout to {}",
                returnStates.size(),
                _timeouts[returnStates.size()] * 1000);
          }
          continue QUEUE;
        }

        for (Edge e : u.getEdges(extraEdges, null, options.isArriveBy())) {
          STATE:
          for (State new_sv = e.traverse(su); new_sv != null; new_sv = new_sv.getNextResult()) {
            if (traverseVisitor != null) {
              traverseVisitor.visitEdge(e, new_sv);
            }

            double h = heuristic.computeForwardWeight(new_sv, target);
            //                    for (State bs : boundingStates) {
            //                        if (eDominates(bs, new_sv)) {
            //                            continue STATE;
            //                        }
            //                    }
            Vertex v = new_sv.getVertex();
            List<State> old_states = states.get(v);
            if (old_states == null) {
              old_states = new LinkedList<State>();
              states.put(v, old_states);
            } else {
              for (State old_sv : old_states) {
                if (eDominates(old_sv, new_sv)) {
                  continue STATE;
                }
              }
              Iterator<State> iter = old_states.iterator();
              while (iter.hasNext()) {
                State old_sv = iter.next();
                if (eDominates(new_sv, old_sv)) {
                  iter.remove();
                }
              }
            }
            if (traverseVisitor != null) traverseVisitor.visitEnqueue(new_sv);

            old_states.add(new_sv);
            pq.insert(new_sv, new_sv.getWeight() + h);
          }
        }
      }
    }
    storeMemory();

    // Make the states into paths and return them
    List<GraphPath> paths = new LinkedList<GraphPath>();
    for (State s : returnStates) {
      LOG.debug(s.toStringVerbose());
      paths.add(new GraphPath(s, true));
    }
    // sort by arrival time, though paths are already in order of increasing difficulty
    // Collections.sort(paths, new PathComparator(origin.getOptions().isArriveBy()));
    return paths;
  }
  public void testHalfEdges() {
    // the shortest half-edge from the start vertex takes you down, but the shortest total path
    // is up and over

    TraverseOptions options = new TraverseOptions();

    HashSet<Edge> turns = new HashSet<Edge>(graph.getOutgoing(left));
    turns.addAll(graph.getOutgoing(leftBack));

    StreetLocation start =
        StreetLocation.createStreetLocation(
            "start",
            "start",
            cast(turns, StreetEdge.class),
            new LinearLocation(0, 0.4).getCoordinate(left.getGeometry()));

    HashSet<Edge> endTurns = new HashSet<Edge>(graph.getOutgoing(right));
    endTurns.addAll(graph.getOutgoing(rightBack));

    StreetLocation end =
        StreetLocation.createStreetLocation(
            "end",
            "end",
            cast(endTurns, StreetEdge.class),
            new LinearLocation(0, 0.8).getCoordinate(right.getGeometry()));

    assertTrue(start.getX() < end.getX());
    assertTrue(start.getY() < end.getY());

    List<DirectEdge> extra = end.getExtra();

    assertEquals(12, extra.size());

    GregorianCalendar startTime = new GregorianCalendar(2009, 11, 1, 12, 34, 25);

    ShortestPathTree spt1 =
        AStar.getShortestPathTree(graph, brOut, end, TestUtils.toSeconds(startTime), options);

    GraphPath pathBr = spt1.getPath(end, false);
    assertNotNull("There must be a path from br to end", pathBr);

    ShortestPathTree spt2 =
        AStar.getShortestPathTree(graph, trOut, end, TestUtils.toSeconds(startTime), options);

    GraphPath pathTr = spt2.getPath(end, false);
    assertNotNull("There must be a path from tr to end", pathTr);
    assertTrue(
        "path from bottom to end must be longer than path from top to end",
        pathBr.getWeight() > pathTr.getWeight());

    ShortestPathTree spt =
        AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options);

    GraphPath path = spt.getPath(end, false);
    assertNotNull("There must be a path from start to end", path);

    // the bottom is not part of the shortest path
    for (State s : path.states) {
      assertNotSame(s.getVertex(), graph.getVertex("bottom"));
      assertNotSame(s.getVertex(), graph.getVertex("bottomBack"));
    }

    startTime = new GregorianCalendar(2009, 11, 1, 12, 34, 25);

    options.setArriveBy(true);
    spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options);

    path = spt.getPath(start, false);
    assertNotNull("There must be a path from start to end (looking back)", path);

    // the bottom edge is not part of the shortest path
    for (State s : path.states) {
      assertNotSame(s.getVertex(), graph.getVertex("bottom"));
      assertNotSame(s.getVertex(), graph.getVertex("bottomBack"));
    }

    /* Now, the right edge is not bikeable.  But the user can walk their bike.  So here are some tests
     * that prove (a) that walking bikes works, but that (b) it is not preferred to riding a tiny bit longer.
     */

    options = new TraverseOptions(new TraverseModeSet(TraverseMode.BICYCLE));
    start =
        StreetLocation.createStreetLocation(
            "start1",
            "start1",
            cast(turns, StreetEdge.class),
            new LinearLocation(0, 0.95).getCoordinate(top.getGeometry()));
    end =
        StreetLocation.createStreetLocation(
            "end1",
            "end1",
            cast(turns, StreetEdge.class),
            new LinearLocation(0, 0.95).getCoordinate(bottom.getGeometry()));
    spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options);

    path = spt.getPath(start, false);
    assertNotNull("There must be a path from top to bottom along the right", path);

    // the left edge is not part of the shortest path (even though the bike must be walked along the
    // right)
    for (State s : path.states) {
      assertNotSame(s.getVertex(), graph.getVertex("left"));
      assertNotSame(s.getVertex(), graph.getVertex("leftBack"));
    }

    start =
        StreetLocation.createStreetLocation(
            "start2",
            "start2",
            cast(turns, StreetEdge.class),
            new LinearLocation(0, 0.55).getCoordinate(top.getGeometry()));
    end =
        StreetLocation.createStreetLocation(
            "end2",
            "end2",
            cast(turns, StreetEdge.class),
            new LinearLocation(0, 0.55).getCoordinate(bottom.getGeometry()));
    spt = AStar.getShortestPathTree(graph, start, end, TestUtils.toSeconds(startTime), options);

    path = spt.getPath(start, false);
    assertNotNull("There must be a path from top to bottom", path);

    // the right edge is not part of the shortest path, e
    for (State s : path.states) {
      assertNotSame(s.getVertex(), graph.getVertex("right"));
      assertNotSame(s.getVertex(), graph.getVertex("rightBack"));
    }
  }