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

com.graphhopper.routing.ch.CHPreparationGraph Maven / Gradle / Ivy

/*
 *  Licensed to GraphHopper GmbH under one or more contributor
 *  license agreements. See the NOTICE file distributed with this work for
 *  additional information regarding copyright ownership.
 *
 *  GraphHopper GmbH licenses this file to you 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.graphhopper.routing.ch;

import com.carrotsearch.hppc.*;
import com.carrotsearch.hppc.sorting.IndirectComparator;
import com.carrotsearch.hppc.sorting.IndirectSort;
import com.graphhopper.routing.ev.DecimalEncodedValue;
import com.graphhopper.routing.ev.TurnCost;
import com.graphhopper.routing.util.AllEdgesIterator;
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
import com.graphhopper.storage.TurnCostStorage;
import com.graphhopper.util.BitUtil;
import com.graphhopper.util.EdgeIterator;
import com.graphhopper.util.GHUtility;

import static com.graphhopper.util.ArrayUtil.zero;

/**
 * Graph data structure used for CH preparation. It allows caching weights, and edges that are not needed anymore
 * (those adjacent to contracted nodes) can be removed (see {@link #disconnect}.
 */
public class CHPreparationGraph {
    private final int nodes;
    private final int edges;
    private final boolean edgeBased;
    private final TurnCostFunction turnCostFunction;
    // each edge/shortcut between nodes a/b is represented as a single object and we maintain two linked lists of such
    // objects for every node (one for outgoing edges and one for incoming edges).
    private PrepareEdge[] prepareEdgesOut;
    private PrepareEdge[] prepareEdgesIn;
    // todo: maybe we can get rid of this
    private int[] degrees;
    private IntSet neighborSet;
    private OrigGraph origGraph;
    private OrigGraph.Builder origGraphBuilder;
    private int nextShortcutId;
    private boolean ready;

    public static CHPreparationGraph nodeBased(int nodes, int edges) {
        return new CHPreparationGraph(nodes, edges, false, (in, via, out) -> 0);
    }

    public static CHPreparationGraph edgeBased(int nodes, int edges, TurnCostFunction turnCostFunction) {
        return new CHPreparationGraph(nodes, edges, true, turnCostFunction);
    }

    /**
     * @param nodes (fixed) number of nodes of the graph
     * @param edges the maximum number of (non-shortcut) edges in this graph. edges-1 is the maximum edge id that may
     *              be used.
     */
    private CHPreparationGraph(int nodes, int edges, boolean edgeBased, TurnCostFunction turnCostFunction) {
        this.turnCostFunction = turnCostFunction;
        this.nodes = nodes;
        this.edges = edges;
        this.edgeBased = edgeBased;
        prepareEdgesOut = new PrepareEdge[nodes];
        prepareEdgesIn = new PrepareEdge[nodes];
        degrees = new int[nodes];
        origGraphBuilder = edgeBased ? new OrigGraph.Builder() : null;
        neighborSet = new IntHashSet();
        nextShortcutId = edges;
    }

    public static void buildFromGraph(CHPreparationGraph prepareGraph, Graph graph, Weighting weighting) {
        if (graph.getNodes() != prepareGraph.getNodes())
            throw new IllegalArgumentException("Cannot initialize from given graph. The number of nodes does not match: " +
                    graph.getNodes() + " vs. " + prepareGraph.getNodes());
        if (graph.getEdges() != prepareGraph.getOriginalEdges())
            throw new IllegalArgumentException("Cannot initialize from given graph. The number of edges does not match: " +
                    graph.getEdges() + " vs. " + prepareGraph.getOriginalEdges());
        AllEdgesIterator iter = graph.getAllEdges();
        while (iter.next()) {
            double weightFwd = weighting.calcEdgeWeightWithAccess(iter, false);
            double weightBwd = weighting.calcEdgeWeightWithAccess(iter, true);
            prepareGraph.addEdge(iter.getBaseNode(), iter.getAdjNode(), iter.getEdge(), weightFwd, weightBwd);
        }
        prepareGraph.prepareForContraction();
    }

    /**
     * Builds a turn cost function for a given graph('s turn cost storage) and a weighting.
     * The trivial implementation would be simply returning {@link Weighting#calcTurnWeight}. However, it turned out
     * that reading all turn costs for the current encoder and then storing them in separate arrays upfront speeds up
     * edge-based CH preparation by about 25%. See #2084
     */
    public static TurnCostFunction buildTurnCostFunctionFromTurnCostStorage(Graph graph, Weighting weighting) {
        FlagEncoder encoder = weighting.getFlagEncoder();
        String key = TurnCost.key(encoder.toString());
        if (!encoder.hasEncodedValue(key))
            return (inEdge, viaNode, outEdge) -> 0;

        DecimalEncodedValue turnCostEnc = encoder.getDecimalEncodedValue(key);
        TurnCostStorage turnCostStorage = graph.getTurnCostStorage();
        // we maintain a list of inEdge/outEdge/turn-cost triples (we use two arrays for this) that is sorted by nodes
        LongArrayList turnCostEdgePairs = new LongArrayList();
        DoubleArrayList turnCosts = new DoubleArrayList();
        // for each node we store the index of the first turn cost entry/triple in the list
        final int[] turnCostNodes = new int[graph.getNodes() + 1];
        TurnCostStorage.TurnRelationIterator tcIter = turnCostStorage.getAllTurnRelations();
        int lastNode = -1;
        while (tcIter.next()) {
            int viaNode = tcIter.getViaNode();
            if (viaNode < lastNode)
                throw new IllegalStateException();
            long edgePair = BitUtil.LITTLE.combineIntsToLong(tcIter.getFromEdge(), tcIter.getToEdge());
            // note that as long as we only use OSM turn restrictions all the turn costs are infinite anyway
            double turnCost = tcIter.getCost(turnCostEnc);
            int index = turnCostEdgePairs.size();
            turnCostEdgePairs.add(edgePair);
            turnCosts.add(turnCost);
            if (viaNode != lastNode) {
                for (int i = lastNode + 1; i <= viaNode; i++) {
                    turnCostNodes[i] = index;
                }
            }
            lastNode = viaNode;
        }
        for (int i = lastNode + 1; i <= turnCostNodes.length - 1; i++) {
            turnCostNodes[i] = turnCostEdgePairs.size();
        }
        turnCostNodes[turnCostNodes.length - 1] = turnCostEdgePairs.size();

        // currently the u-turn costs are the same for all junctions, so for now we just get them for one of them
        double uTurnCosts = weighting.calcTurnWeight(1, 0, 1);
        return (inEdge, viaNode, outEdge) -> {
            if (!EdgeIterator.Edge.isValid(inEdge) || !EdgeIterator.Edge.isValid(outEdge))
                return 0;
            else if (inEdge == outEdge)
                return uTurnCosts;
            // traverse all turn cost entries we have for this viaNode and return the turn costs if we find a match
            for (int i = turnCostNodes[viaNode]; i < turnCostNodes[viaNode + 1]; i++) {
                long l = turnCostEdgePairs.get(i);
                if (inEdge == BitUtil.LITTLE.getIntLow(l) && outEdge == BitUtil.LITTLE.getIntHigh(l))
                    return turnCosts.get(i);
            }
            return 0;
        };
    }

    public int getNodes() {
        return nodes;
    }

    public int getOriginalEdges() {
        return edges;
    }

    public int getDegree(int node) {
        return degrees[node];
    }

    public void addEdge(int from, int to, int edge, double weightFwd, double weightBwd) {
        checkNotReady();
        boolean fwd = Double.isFinite(weightFwd);
        boolean bwd = Double.isFinite(weightBwd);
        if (!fwd && !bwd)
            return;
        PrepareBaseEdge prepareEdge = new PrepareBaseEdge(edge, from, to, (float) weightFwd, (float) weightBwd);
        if (fwd) {
            addOutEdge(from, prepareEdge);
            addInEdge(to, prepareEdge);
        }
        if (bwd && from != to) {
            addOutEdge(to, prepareEdge);
            addInEdge(from, prepareEdge);
        }
        if (edgeBased)
            origGraphBuilder.addEdge(from, to, edge, fwd, bwd);
    }

    public int addShortcut(int from, int to, int origEdgeKeyFirst, int origEdgeKeyLast, int skipped1,
                           int skipped2, double weight, int origEdgeCount) {
        checkReady();
        PrepareEdge prepareEdge = edgeBased
                ? new EdgeBasedPrepareShortcut(nextShortcutId, from, to, origEdgeKeyFirst, origEdgeKeyLast, weight, skipped1, skipped2, origEdgeCount)
                : new PrepareShortcut(nextShortcutId, from, to, weight, skipped1, skipped2, origEdgeCount);
        addOutEdge(from, prepareEdge);
        if (from != to)
            addInEdge(to, prepareEdge);
        return nextShortcutId++;
    }

    public void prepareForContraction() {
        checkNotReady();
        origGraph = edgeBased ? origGraphBuilder.build() : null;
        origGraphBuilder = null;
        ready = true;
    }

    public PrepareGraphEdgeExplorer createOutEdgeExplorer() {
        checkReady();
        return new PrepareGraphEdgeExplorerImpl(prepareEdgesOut, false);
    }

    public PrepareGraphEdgeExplorer createInEdgeExplorer() {
        checkReady();
        return new PrepareGraphEdgeExplorerImpl(prepareEdgesIn, true);
    }

    public PrepareGraphOrigEdgeExplorer createOutOrigEdgeExplorer() {
        checkReady();
        if (!edgeBased)
            throw new IllegalStateException("orig out explorer is not available for node-based graph");
        return origGraph.createOutOrigEdgeExplorer();
    }

    public PrepareGraphOrigEdgeExplorer createInOrigEdgeExplorer() {
        checkReady();
        if (!edgeBased)
            throw new IllegalStateException("orig in explorer is not available for node-based graph");
        return origGraph.createInOrigEdgeExplorer();
    }

    public double getTurnWeight(int inEdge, int viaNode, int outEdge) {
        return turnCostFunction.getTurnWeight(inEdge, viaNode, outEdge);
    }

    public IntContainer disconnect(int node) {
        checkReady();
        // we use this neighbor set to guarantee a deterministic order of the returned
        // node ids
        neighborSet.clear();
        IntArrayList neighbors = new IntArrayList(getDegree(node));
        PrepareEdge currOut = prepareEdgesOut[node];
        while (currOut != null) {
            int adjNode = currOut.getNodeB();
            if (adjNode == node)
                adjNode = currOut.getNodeA();
            if (adjNode == node) {
                // this is a loop
                currOut = currOut.getNextOut(node);
                continue;
            }
            removeInEdge(adjNode, currOut);
            if (neighborSet.add(adjNode))
                neighbors.add(adjNode);
            currOut = currOut.getNextOut(node);
        }
        PrepareEdge currIn = prepareEdgesIn[node];
        while (currIn != null) {
            int adjNode = currIn.getNodeB();
            if (adjNode == node)
                adjNode = currIn.getNodeA();
            if (adjNode == node) {
                // this is a loop
                currIn = currIn.getNextIn(node);
                continue;
            }
            removeOutEdge(adjNode, currIn);
            if (neighborSet.add(adjNode))
                neighbors.add(adjNode);
            currIn = currIn.getNextIn(node);
        }
        prepareEdgesOut[node] = null;
        prepareEdgesIn[node] = null;
        degrees[node] = 0;
        return neighbors;
    }

    private void removeOutEdge(int node, PrepareEdge prepareEdge) {
        PrepareEdge prevOut = null;
        PrepareEdge currOut = prepareEdgesOut[node];
        while (currOut != null) {
            if (currOut == prepareEdge) {
                if (prevOut == null) {
                    prepareEdgesOut[node] = currOut.getNextOut(node);
                } else {
                    prevOut.setNextOut(node, currOut.getNextOut(node));
                }
                degrees[node]--;
            } else {
                prevOut = currOut;
            }
            currOut = currOut.getNextOut(node);
        }
    }

    private void removeInEdge(int node, PrepareEdge prepareEdge) {
        PrepareEdge prevIn = null;
        PrepareEdge currIn = prepareEdgesIn[node];
        while (currIn != null) {
            if (currIn == prepareEdge) {
                if (prevIn == null) {
                    prepareEdgesIn[node] = currIn.getNextIn(node);
                } else {
                    prevIn.setNextIn(node, currIn.getNextIn(node));
                }
                degrees[node]--;
            } else {
                prevIn = currIn;
            }
            currIn = currIn.getNextIn(node);
        }
    }

    public void close() {
        checkReady();
        prepareEdgesOut = null;
        prepareEdgesIn = null;
        degrees = null;
        neighborSet = null;
        if (edgeBased)
            origGraph = null;
    }

    private void addOutEdge(int node, PrepareEdge prepareEdge) {
        prepareEdge.setNextOut(node, prepareEdgesOut[node]);
        prepareEdgesOut[node] = prepareEdge;
        degrees[node]++;
    }

    private void addInEdge(int node, PrepareEdge prepareEdge) {
        prepareEdge.setNextIn(node, prepareEdgesIn[node]);
        prepareEdgesIn[node] = prepareEdge;
        degrees[node]++;
    }

    private void checkReady() {
        if (!ready)
            throw new IllegalStateException("You need to call prepareForContraction() before calling this method");
    }

    private void checkNotReady() {
        if (ready)
            throw new IllegalStateException("You cannot call this method after calling prepareForContraction()");
    }

    @FunctionalInterface
    public interface TurnCostFunction {
        double getTurnWeight(int inEdge, int viaNode, int outEdge);
    }

    private static class PrepareGraphEdgeExplorerImpl implements PrepareGraphEdgeExplorer, PrepareGraphEdgeIterator {
        private final PrepareEdge[] prepareEdges;
        private final boolean reverse;
        private int node = -1;
        private PrepareEdge currEdge;
        private PrepareEdge nextEdge;

        PrepareGraphEdgeExplorerImpl(PrepareEdge[] prepareEdges, boolean reverse) {
            this.prepareEdges = prepareEdges;
            this.reverse = reverse;
        }

        @Override
        public PrepareGraphEdgeIterator setBaseNode(int node) {
            this.node = node;
            currEdge = null;
            nextEdge = prepareEdges[node];
            return this;
        }

        @Override
        public boolean next() {
            currEdge = nextEdge;
            if (currEdge == null)
                return false;
            nextEdge = reverse ? currEdge.getNextIn(node) : currEdge.getNextOut(node);
            return true;
        }

        @Override
        public int getBaseNode() {
            return node;
        }

        @Override
        public int getAdjNode() {
            return nodeAisBase() ? currEdge.getNodeB() : currEdge.getNodeA();
        }

        @Override
        public int getPrepareEdge() {
            return currEdge.getPrepareEdge();
        }

        @Override
        public boolean isShortcut() {
            return currEdge.isShortcut();
        }

        @Override
        public int getOrigEdgeKeyFirst() {
            return nodeAisBase() ? currEdge.getOrigEdgeKeyFirstAB() : currEdge.getOrigEdgeKeyFirstBA();
        }

        @Override
        public int getOrigEdgeKeyLast() {
            return nodeAisBase() ? currEdge.getOrigEdgeKeyLastAB() : currEdge.getOrigEdgeKeyLastBA();
        }

        @Override
        public int getSkipped1() {
            return currEdge.getSkipped1();
        }

        @Override
        public int getSkipped2() {
            return currEdge.getSkipped2();
        }

        @Override
        public double getWeight() {
            if (nodeAisBase()) {
                return reverse ? currEdge.getWeightBA() : currEdge.getWeightAB();
            } else {
                return reverse ? currEdge.getWeightAB() : currEdge.getWeightBA();
            }
        }

        @Override
        public int getOrigEdgeCount() {
            return currEdge.getOrigEdgeCount();
        }

        @Override
        public void setSkippedEdges(int skipped1, int skipped2) {
            currEdge.setSkipped1(skipped1);
            currEdge.setSkipped2(skipped2);
        }

        @Override
        public void setWeight(double weight) {
            assert Double.isFinite(weight);
            currEdge.setWeight(weight);
        }

        @Override
        public void setOrigEdgeCount(int origEdgeCount) {
            currEdge.setOrigEdgeCount(origEdgeCount);
        }

        @Override
        public String toString() {
            return currEdge == null ? "not_started" : getBaseNode() + "-" + getAdjNode();
        }

        private boolean nodeAisBase() {
            // in some cases we need to determine which direction of the (bidirectional) edge we want
            return currEdge.getNodeA() == node;
        }
    }

    interface PrepareEdge {
        boolean isShortcut();

        int getPrepareEdge();

        int getNodeA();

        int getNodeB();

        double getWeightAB();

        double getWeightBA();

        int getOrigEdgeKeyFirstAB();

        int getOrigEdgeKeyFirstBA();

        int getOrigEdgeKeyLastAB();

        int getOrigEdgeKeyLastBA();

        int getSkipped1();

        int getSkipped2();

        int getOrigEdgeCount();

        void setSkipped1(int skipped1);

        void setSkipped2(int skipped2);

        void setWeight(double weight);

        void setOrigEdgeCount(int origEdgeCount);

        PrepareEdge getNextOut(int base);

        void setNextOut(int base, PrepareEdge prepareEdge);

        PrepareEdge getNextIn(int base);

        void setNextIn(int base, PrepareEdge prepareEdge);

    }

    public static class PrepareBaseEdge implements PrepareEdge {
        private final int prepareEdge;
        private final int nodeA;
        private final int nodeB;
        private final float weightAB;
        private final float weightBA;
        private PrepareEdge nextOutA;
        private PrepareEdge nextOutB;
        private PrepareEdge nextInA;
        private PrepareEdge nextInB;

        public PrepareBaseEdge(int prepareEdge, int nodeA, int nodeB, float weightAB, float weightBA) {
            this.prepareEdge = prepareEdge;
            this.nodeA = nodeA;
            this.nodeB = nodeB;
            this.weightAB = weightAB;
            this.weightBA = weightBA;
        }

        @Override
        public boolean isShortcut() {
            return false;
        }

        @Override
        public int getPrepareEdge() {
            return prepareEdge;
        }

        @Override
        public int getNodeA() {
            return nodeA;
        }

        @Override
        public int getNodeB() {
            return nodeB;
        }

        @Override
        public double getWeightAB() {
            return weightAB;
        }

        @Override
        public double getWeightBA() {
            return weightBA;
        }

        @Override
        public int getOrigEdgeKeyFirstAB() {
            int key = prepareEdge << 1;
            return nodeA > nodeB ? key + 1 : key;
        }

        @Override
        public int getOrigEdgeKeyFirstBA() {
            int key = prepareEdge << 1;
            return nodeB > nodeA ? key + 1 : key;
        }

        @Override
        public int getOrigEdgeKeyLastAB() {
            return getOrigEdgeKeyFirstAB();
        }

        @Override
        public int getOrigEdgeKeyLastBA() {
            return getOrigEdgeKeyFirstBA();
        }

        @Override
        public int getSkipped1() {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getSkipped2() {
            throw new UnsupportedOperationException();
        }

        @Override
        public int getOrigEdgeCount() {
            return 1;
        }

        @Override
        public void setSkipped1(int skipped1) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setSkipped2(int skipped2) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setWeight(double weight) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void setOrigEdgeCount(int origEdgeCount) {
            throw new UnsupportedOperationException();
        }

        @Override
        public PrepareEdge getNextOut(int base) {
            if (base == nodeA)
                return nextOutA;
            else if (base == nodeB)
                return nextOutB;
            else
                throw new IllegalStateException("Cannot get next out edge as the given base " + base + " is not adjacent to the current edge");
        }

        @Override
        public void setNextOut(int base, PrepareEdge prepareEdge) {
            if (base == nodeA)
                nextOutA = prepareEdge;
            else if (base == nodeB)
                nextOutB = prepareEdge;
            else
                throw new IllegalStateException("Cannot set next out edge as the given base " + base + " is not adjacent to the current edge");
        }

        @Override
        public PrepareEdge getNextIn(int base) {
            if (base == nodeA)
                return nextInA;
            else if (base == nodeB)
                return nextInB;
            else
                throw new IllegalStateException("Cannot get next in edge as the given base " + base + " is not adjacent to the current edge");
        }

        @Override
        public void setNextIn(int base, PrepareEdge prepareEdge) {
            if (base == nodeA)
                nextInA = prepareEdge;
            else if (base == nodeB)
                nextInB = prepareEdge;
            else
                throw new IllegalStateException("Cannot set next in edge as the given base " + base + " is not adjacent to the current edge");
        }

        @Override
        public String toString() {
            return nodeA + "-" + nodeB + " (" + prepareEdge + ") " + weightAB + " " + weightBA;
        }
    }

    private static class PrepareShortcut implements PrepareEdge {
        private final int prepareEdge;
        private final int from;
        private final int to;
        private double weight;
        private int skipped1;
        private int skipped2;
        private int origEdgeCount;
        private PrepareEdge nextOut;
        private PrepareEdge nextIn;

        private PrepareShortcut(int prepareEdge, int from, int to, double weight, int skipped1, int skipped2, int origEdgeCount) {
            this.prepareEdge = prepareEdge;
            this.from = from;
            this.to = to;
            assert Double.isFinite(weight);
            this.weight = weight;
            this.skipped1 = skipped1;
            this.skipped2 = skipped2;
            this.origEdgeCount = origEdgeCount;
        }

        @Override
        public boolean isShortcut() {
            return true;
        }

        @Override
        public int getPrepareEdge() {
            return prepareEdge;
        }

        @Override
        public int getNodeA() {
            return from;
        }

        @Override
        public int getNodeB() {
            return to;
        }

        @Override
        public double getWeightAB() {
            return weight;
        }

        @Override
        public double getWeightBA() {
            return weight;
        }

        @Override
        public int getOrigEdgeKeyFirstAB() {
            throw new IllegalStateException("Not supported for node-based shortcuts");
        }

        @Override
        public int getOrigEdgeKeyFirstBA() {
            throw new IllegalStateException("Not supported for node-based shortcuts");
        }

        @Override
        public int getOrigEdgeKeyLastAB() {
            throw new IllegalStateException("Not supported for node-based shortcuts");
        }

        @Override
        public int getOrigEdgeKeyLastBA() {
            throw new IllegalStateException("Not supported for node-based shortcuts");
        }

        @Override
        public int getSkipped1() {
            return skipped1;
        }

        @Override
        public int getSkipped2() {
            return skipped2;
        }

        @Override
        public int getOrigEdgeCount() {
            return origEdgeCount;
        }

        @Override
        public void setSkipped1(int skipped1) {
            this.skipped1 = skipped1;
        }

        @Override
        public void setSkipped2(int skipped2) {
            this.skipped2 = skipped2;
        }

        @Override
        public void setWeight(double weight) {
            this.weight = weight;
        }

        @Override
        public void setOrigEdgeCount(int origEdgeCount) {
            this.origEdgeCount = origEdgeCount;
        }

        @Override
        public PrepareEdge getNextOut(int base) {
            return nextOut;
        }

        @Override
        public void setNextOut(int base, PrepareEdge prepareEdge) {
            this.nextOut = prepareEdge;
        }

        @Override
        public PrepareEdge getNextIn(int base) {
            return nextIn;
        }

        @Override
        public void setNextIn(int base, PrepareEdge prepareEdge) {
            this.nextIn = prepareEdge;
        }

        @Override
        public String toString() {
            return from + "-" + to + " " + weight;
        }
    }

    private static class EdgeBasedPrepareShortcut extends PrepareShortcut {
        // we use this subclass to save some memory for node-based where these are not needed
        private final int origEdgeKeyFirst;
        private final int origEdgeKeyLast;

        public EdgeBasedPrepareShortcut(int prepareEdge, int from, int to, int origEdgeKeyFirst, int origEdgeKeyLast,
                                        double weight, int skipped1, int skipped2, int origEdgeCount) {
            super(prepareEdge, from, to, weight, skipped1, skipped2, origEdgeCount);
            this.origEdgeKeyFirst = origEdgeKeyFirst;
            this.origEdgeKeyLast = origEdgeKeyLast;
        }

        @Override
        public int getOrigEdgeKeyFirstAB() {
            return origEdgeKeyFirst;
        }

        @Override
        public int getOrigEdgeKeyFirstBA() {
            return origEdgeKeyFirst;
        }

        @Override
        public int getOrigEdgeKeyLastAB() {
            return origEdgeKeyLast;
        }

        @Override
        public int getOrigEdgeKeyLastBA() {
            return origEdgeKeyLast;
        }

        @Override
        public String toString() {
            return getNodeA() + "-" + getNodeB() + " (" + origEdgeKeyFirst + ", " + origEdgeKeyLast + ") " + getWeightAB();
        }
    }

    /**
     * This helper graph can be used to quickly obtain the edge-keys of the edges of a node. It is only used for
     * edge-based CH. In principle we could use base graph for this, but it turned out it is faster to use this
     * graph (because it does not need to read all the edge flags to determine the access flags).
     */
    private static class OrigGraph {
        // we store a list of 'edges' in the format: adjNode|edgeId|accessFlags, we use two ints for each edge
        private final IntArrayList adjNodes;
        private final IntArrayList edgesAndFlags;
        // for each node we store the index at which the edges for this node begin in the above edge list
        private final IntArrayList firstEdgesByNode;

        private OrigGraph(IntArrayList firstEdgesByNode, IntArrayList adjNodes, IntArrayList edgesAndFlags) {
            this.firstEdgesByNode = firstEdgesByNode;
            this.adjNodes = adjNodes;
            this.edgesAndFlags = edgesAndFlags;
        }

        PrepareGraphOrigEdgeExplorer createOutOrigEdgeExplorer() {
            return new OrigEdgeIteratorImpl(this, false);
        }

        PrepareGraphOrigEdgeExplorer createInOrigEdgeExplorer() {
            return new OrigEdgeIteratorImpl(this, true);
        }

        static class Builder {
            private final IntArrayList fromNodes = new IntArrayList();
            private final IntArrayList toNodes = new IntArrayList();
            private final IntArrayList edgesAndFlags = new IntArrayList();
            private int maxFrom = -1;
            private int maxTo = -1;

            void addEdge(int from, int to, int edge, boolean fwd, boolean bwd) {
                fromNodes.add(from);
                toNodes.add(to);
                edgesAndFlags.add(getEdgeWithFlags(edge, fwd, bwd));
                maxFrom = Math.max(maxFrom, from);
                maxTo = Math.max(maxTo, to);

                fromNodes.add(to);
                toNodes.add(from);
                edgesAndFlags.add(getEdgeWithFlags(edge, bwd, fwd));
                maxFrom = Math.max(maxFrom, to);
                maxTo = Math.max(maxTo, from);
            }

            OrigGraph build() {
                int[] sortOrder = IndirectSort.mergesort(0, fromNodes.elementsCount, new IndirectComparator.AscendingIntComparator(fromNodes.buffer));
                sortAndTrim(fromNodes, sortOrder);
                sortAndTrim(toNodes, sortOrder);
                sortAndTrim(edgesAndFlags, sortOrder);
                return new OrigGraph(buildFirstEdgesByNode(), toNodes, edgesAndFlags);
            }

            private int getEdgeWithFlags(int edge, boolean fwd, boolean bwd) {
                // we use only 30 bits for the edge Id and store two access flags along with the same int
                if (edge >= Integer.MAX_VALUE >> 2)
                    throw new IllegalArgumentException("Maximum edge ID exceeded: " + Integer.MAX_VALUE);
                edge <<= 1;
                if (fwd)
                    edge++;
                edge <<= 1;
                if (bwd)
                    edge++;
                return edge;
            }

            private IntArrayList buildFirstEdgesByNode() {
                // it is assumed the edges have been sorted already
                final int numFroms = maxFrom + 1;
                final int numEdges = fromNodes.size();

                IntArrayList firstEdgesByNode = zero(numFroms + 1);
                if (numFroms == 0) {
                    firstEdgesByNode.set(0, numEdges);
                    return firstEdgesByNode;
                }
                int edgeIndex = 0;
                for (int from = 0; from < numFroms; from++) {
                    while (edgeIndex < numEdges && fromNodes.get(edgeIndex) < from) {
                        edgeIndex++;
                    }
                    firstEdgesByNode.set(from, edgeIndex);
                }
                firstEdgesByNode.set(numFroms, numEdges);
                return firstEdgesByNode;
            }

        }
    }

    private static class OrigEdgeIteratorImpl implements PrepareGraphOrigEdgeExplorer, PrepareGraphOrigEdgeIterator {
        private final OrigGraph graph;
        private final boolean reverse;
        private int node;
        private int endEdge;
        private int index;

        public OrigEdgeIteratorImpl(OrigGraph graph, boolean reverse) {
            this.graph = graph;
            this.reverse = reverse;
        }

        @Override
        public PrepareGraphOrigEdgeIterator setBaseNode(int node) {
            this.node = node;
            index = graph.firstEdgesByNode.get(node) - 1;
            endEdge = graph.firstEdgesByNode.get(node + 1);
            return this;
        }

        @Override
        public boolean next() {
            while (true) {
                index++;
                if (index >= endEdge)
                    return false;
                if (hasAccess())
                    return true;
            }
        }

        @Override
        public int getBaseNode() {
            return node;
        }

        @Override
        public int getAdjNode() {
            return graph.adjNodes.get(index);
        }

        @Override
        public int getOrigEdgeKeyFirst() {
            int e = graph.edgesAndFlags.get(index);
            return GHUtility.createEdgeKey(node, getAdjNode(), e >> 2, false);
        }

        @Override
        public int getOrigEdgeKeyLast() {
            return getOrigEdgeKeyFirst();
        }

        private boolean hasAccess() {
            int e = graph.edgesAndFlags.get(index);
            if (reverse) {
                return (e & 0b01) == 0b01;
            } else {
                return (e & 0b10) == 0b10;
            }
        }
    }

    private static void sortAndTrim(IntArrayList arr, int[] sortOrder) {
        arr.buffer = applySortOrder(sortOrder, arr.buffer);
        arr.elementsCount = arr.buffer.length;
    }

    private static int[] applySortOrder(int[] sortOrder, int[] arr) {
        if (sortOrder.length > arr.length) {
            throw new IllegalArgumentException("sort order must not be shorter than array");
        }
        int[] result = new int[sortOrder.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = arr[sortOrder[i]];
        }
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy