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

org.neo4j.gds.steiner.InverseRerouter Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.gds.steiner;

import com.carrotsearch.hppc.BitSet;
import org.apache.commons.lang3.mutable.MutableDouble;
import org.neo4j.gds.api.Graph;
import org.neo4j.gds.collections.ha.HugeDoubleArray;
import org.neo4j.gds.collections.ha.HugeLongArray;
import org.neo4j.gds.core.concurrency.Concurrency;
import org.neo4j.gds.mem.MemoryEstimation;
import org.neo4j.gds.mem.MemoryEstimations;
import org.neo4j.gds.core.utils.paged.HugeLongArrayQueue;
import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
import org.neo4j.gds.core.utils.queue.HugeLongPriorityQueue;

import java.util.concurrent.atomic.DoubleAdder;
import java.util.concurrent.atomic.LongAdder;

import static org.neo4j.gds.steiner.ShortestPathsSteinerAlgorithm.PRUNED;
import static org.neo4j.gds.steiner.ShortestPathsSteinerAlgorithm.ROOT_NODE;

public class InverseRerouter extends ReroutingAlgorithm {

    private final BitSet isTerminal;
    private final HugeLongArray examinationQueue;
    private final LongAdder indexQueue;
    private final ProgressTracker progressTracker;

    InverseRerouter(
        Graph graph,
        long sourceId,
        BitSet isTerminal,
        HugeLongArray examinationQueue,
        LongAdder indexQueue,
        Concurrency concurrency,
        ProgressTracker progressTracker
    ) {
        super(graph, sourceId, concurrency, progressTracker);
        this.isTerminal = isTerminal;
        this.examinationQueue = examinationQueue;
        this.indexQueue = indexQueue;
        this.progressTracker = progressTracker;
    }

    static MemoryEstimation estimation() {

        var memoryEstimationBuilder = MemoryEstimations.builder(SimpleRerouter.class)
            .add(LinkCutTree.estimation())
            .add(ReroutingChildrenManager.estimation())
            .add("priority queue", HugeLongPriorityQueue.memoryEstimation())
            .perNode("current segment", HugeLongArray::memoryEstimation)
            .perNode("queue", HugeLongArrayQueue::memoryEstimation)
            .perNode("pruning array", HugeLongArray::memoryEstimation)
            .perNode("best alternative", HugeLongArray::memoryEstimation)
            .perNode("best alternative parent cost ", HugeDoubleArray::memoryEstimation);

        return memoryEstimationBuilder.build();
    }

    @Override
    public void reroute(
        HugeLongArray parent,
        HugeDoubleArray parentCost,
        DoubleAdder totalCost,
        LongAdder effectiveNodeCount
    ) {
        progressTracker.beginSubTask("Reroute");

        LinkCutTree linkCutTree = createLinkCutTree(parent);
        ReroutingChildrenManager childrenManager = new ReroutingChildrenManager(
            graph.nodeCount(),
            isTerminal,
            sourceId
        );

        initializeChildrenManager(childrenManager, parent);
        HugeLongPriorityQueue priorityQueue = HugeLongPriorityQueue.max(graph.nodeCount());
        HugeLongArray currentSegmentArray = HugeLongArray.newArray(graph.nodeCount());
        HugeLongArrayQueue actualExaminationQueue = HugeLongArrayQueue.newQueue(graph.nodeCount());
        HugeLongArray pruningArray = HugeLongArray.newArray(graph.nodeCount());
        HugeLongArray bestAlternative = HugeLongArray.newArray(graph.nodeCount());
        HugeDoubleArray bestAlternativeParentCost = HugeDoubleArray.newArray(graph.nodeCount());

        long currentSegmentIndex = 0;
        long endIndex = indexQueue.longValue();
        //we start traversing paths to terminals in the order they were found
        for (long indexId = 0; indexId < endIndex; ++indexId) {
            long element = this.examinationQueue.get(indexId);

            if (element != PRUNED) { //PRUNED signals end of a path
                currentSegmentArray.set(currentSegmentIndex++, element);
                continue;
            }

            while (currentSegmentIndex > 0) {
                long node = currentSegmentArray.get(--currentSegmentIndex);
                actualExaminationQueue.add(node); //transfer path to the examination queue (FIFO)
                //if the node in the path cannot be pruned (because it's part of other paths), we prune what we can so far
                //at the end prune
                boolean shouldOptimizeSegment = currentSegmentIndex == 0 || !childrenManager.prunable(node);

                if (shouldOptimizeSegment) {
                    optimizeSegment(
                        childrenManager,
                        actualExaminationQueue,
                        priorityQueue,
                        pruningArray,
                        bestAlternative,
                        bestAlternativeParentCost,
                        linkCutTree,
                        parent,
                        parentCost,
                        totalCost,
                        effectiveNodeCount
                    );
                }
            }

        }
        progressTracker.endSubTask("Reroute");
    }
    private void initializeChildrenManager(ReroutingChildrenManager childrenManager, HugeLongArray parent) {
        graph.forEachNode(nodeId -> {
            var parentId = parent.get(nodeId);
            if (parentId != PRUNED && parentId != ROOT_NODE) {
                childrenManager.link(nodeId, parentId);
            }
            return true;
        });
    }

    private void optimizeSegment(
        ReroutingChildrenManager childrenManager,
        HugeLongArrayQueue examinationQueue,
        HugeLongPriorityQueue priorityQueue,
        HugeLongArray pruningArray,
        HugeLongArray bestAlternative,
        HugeDoubleArray bestAlternativeParentCost,
        LinkCutTree linkCutTree,
        HugeLongArray parent,
        HugeDoubleArray parentCost,
        DoubleAdder totalCost,
        LongAdder effectiveNodeCount

    ) {
        long stopPruningNode = -1;
        double pruningGain = 0;

        long curr = examinationQueue.peek();

        //the parent of the path could have had many children (which made it not-runed)
        //but these children might have been removed, which could perhaps make it prunable now
        //so when we include the  cost of pruning a node from  the current path, we might be able to gain some more!
        while (childrenManager.prunable(parent.get(curr))) { //source is always reachable and never prunable
            var nextCurr = parent.get(curr);
            var nextCost = parentCost.get(nextCurr);
            pruningGain += nextCost;
            curr = nextCurr;
        }

        while (!examinationQueue.isEmpty()) {
            long nodeId = examinationQueue.remove();

            var parentId = parent.get(nodeId);

            if (stopPruningNode == -1) {
                stopPruningNode = parent.get(curr);
            }

            pruningGain += parentCost.get(nodeId);  //if we cut nodeId, we get rid of extra parentCost(nodeId) cost
            pruningArray.set(nodeId, 0);
            bestAlternative.set(nodeId, PRUNED);

            double finalPruningGain = pruningGain;
            //find the best node we can make it as parent
            double gain = processNodeInverseIndexedGraph(
                parent,
                bestAlternative,
                bestAlternativeParentCost,
                linkCutTree,
                nodeId,
                parentId,
                finalPruningGain
            );

            if (bestAlternative.get(nodeId) != PRUNED) { //add to the priority Queue
                priorityQueue.add(nodeId, gain);
            }
            progressTracker.logProgress();
        }
        double prunedSoFar = 0;
        while (!priorityQueue.isEmpty()) {
            long node = priorityQueue.top();
            if (node != PRUNED) { //node is still alive
                //we prune from top to bottom: prunedSoFar will include gain from nodes already pruned
                double adjustedCost = priorityQueue.cost(node) - prunedSoFar;
                if (adjustedCost > 0) { //maybe it's not worth pruning anymore
                    //if still worthy, we must check we are not creating a cycle
                    boolean canReroute = checkIfRerouteIsValid(
                        linkCutTree,
                        bestAlternative.get(node),
                        node,
                        parent.get(node)
                    );
                    if (canReroute) {
                        //if everything is alright, do pruning
                        prunedSoFar += rerouteWithPruning(
                            childrenManager,
                            bestAlternative,
                            bestAlternativeParentCost,
                            linkCutTree,
                            parent,
                            parentCost,
                            totalCost,
                            stopPruningNode,
                            node,
                            effectiveNodeCount
                        );

                        stopPruningNode = node; //node becomes the new stop, everything above it has been pruned in the current segment

                    } else {
                        //not ok, restore tree
                        linkCutTree.link(parent.get(node), node);
                    }
                }
            }
            priorityQueue.pop();
        }

    }

    private double processNodeInverseIndexedGraph(
        HugeLongArray parent,
        HugeLongArray bestAlternative,
        HugeDoubleArray bestAlternativeParentCost,
        LinkCutTree linkCutTree,
        long nodeId,
        long parentId,
        double finalPruningGain
    ) {
        MutableDouble bestGain = new MutableDouble(-1);
        graph.forEachInverseRelationship(nodeId, 1.0, (s, t, w) -> {
            if (parent.get(nodeId) == t) {
                return true;
            }

            //  t must still be alive, in addition if we prune, we should be able to get a benefit from it
            //benefit is:  new edge cost - all edges pruned
            if (parent.get(t) != PRUNED && (finalPruningGain - w) > bestGain.doubleValue()) {
                //now we must check that t is not a descendant of s.
                boolean canReconnect = checkIfRerouteIsValid(linkCutTree, t, nodeId, parentId);
                if (canReconnect) {
                    bestAlternative.set(nodeId, t);
                    bestGain.setValue(finalPruningGain - w);
                    bestAlternativeParentCost.set(
                        nodeId,
                        w
                    ); //this can be deduced via subtraction later on, but let's keep it
                }
                //we always restore the tree to its original state
                linkCutTree.link(parentId, nodeId);

            }
                return true;
            }
        );
        return bestGain.doubleValue();
    }

    private double rerouteWithPruning(
        ReroutingChildrenManager childrenManager,
        HugeLongArray bestAlternative,
        HugeDoubleArray bestAlternativeParentCost,
        LinkCutTree linkCutTree,
        HugeLongArray parent,
        HugeDoubleArray parentCost,
        DoubleAdder totalCost,
        long stopPruningNode,
        long node,
        LongAdder effectiveNodeCount
    ) {
        double pruned = 0;
        long current = node;
        //start pruning until you hit the stop
        while (stopPruningNode != current) {

            long nextCurrent = parent.get(current);
            childrenManager.cut(current);  //with pruning we remove some useless nodes
            parent.set(current, PRUNED);
            double nodePruneCost = parentCost.get(current);
            pruned += nodePruneCost;
            totalCost.add(-nodePruneCost); //and their associaed cost
            parentCost.set(current, PRUNED);
            current = nextCurrent;
            effectiveNodeCount.decrement(); //this also cuts the node we are going to reroute....
        }
        effectiveNodeCount.increment(); //...hence we need do a +1 to recover it
        linkCutTree.link(bestAlternative.get(node), node); //now we link node to its new parent
        childrenManager.link(node, bestAlternative.get(node));
        parentCost.set(node, bestAlternativeParentCost.get(node));  //and adjust its cost
        parent.set(node, bestAlternative.get(node));
        totalCost.add(parentCost.get(node)); //and the overall cost

        return pruned;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy