blob: fd022bedd51d47b878e451fabc9cddb746cf0b75 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* 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 org.apache.hugegraph.traversal.algorithm;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.ws.rs.core.MultivaluedMap;
import org.apache.hugegraph.HugeGraph;
import org.apache.hugegraph.backend.id.Id;
import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
import org.apache.hugegraph.type.define.Directions;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.hugegraph.structure.HugeEdge;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.OrderLimitMap;
public class NeighborRankTraverser extends HugeTraverser {
public static final int MAX_TOP = 1000;
private final double alpha;
private final long capacity;
public NeighborRankTraverser(HugeGraph graph, double alpha, long capacity) {
super(graph);
checkCapacity(capacity);
this.alpha = alpha;
this.capacity = capacity;
}
public List<Map<Id, Double>> neighborRank(Id source, List<Step> steps) {
E.checkNotNull(source, "source vertex id");
this.checkVertexExist(source, "source vertex");
E.checkArgument(!steps.isEmpty(), "The steps can't be empty");
MultivaluedMap<Id, Node> sources = newMultivalueMap();
sources.add(source, new Node(source, null));
boolean sameLayerTransfer = true;
long access = 0;
// Results: ranks of each layer
List<Ranks> ranks = newList();
ranks.add(Ranks.of(source, 1.0));
for (Step step : steps) {
Ranks lastLayerRanks = ranks.get(ranks.size() - 1);
Map<Id, Double> sameLayerIncrRanks = newMap();
List<Adjacencies> adjacencies = newList();
MultivaluedMap<Id, Node> newVertices = newMultivalueMap();
// Traversal vertices of previous level
for (Map.Entry<Id, List<Node>> entry : sources.entrySet()) {
Id vertex = entry.getKey();
Iterator<Edge> edges = this.edgesOfVertex(vertex,
step.edgeStep);
Adjacencies adjacenciesV = new Adjacencies(vertex);
Set<Id> sameLayerNodesV = newIdSet();
Map<Integer, Set<Id>> prevLayerNodesV = newMap();
while (edges.hasNext()) {
HugeEdge edge = (HugeEdge) edges.next();
Id target = edge.id().otherVertexId();
// Determine whether it belongs to the same layer
if (this.belongToSameLayer(sources.keySet(), target,
sameLayerNodesV)) {
continue;
}
/*
* Determine whether it belongs to the previous layers,
* if it belongs to, update the weight, but don't pass
* any more
*/
if (this.belongToPrevLayers(ranks, target,
prevLayerNodesV)) {
continue;
}
for (Node n : entry.getValue()) {
// If have loop, skip target
if (n.contains(target)) {
continue;
}
Node newNode = new Node(target, n);
adjacenciesV.add(newNode);
// Add adjacent nodes to sources of next step
newVertices.add(target, newNode);
checkCapacity(this.capacity, ++access, "neighbor rank");
}
}
long degree = sameLayerNodesV.size() + prevLayerNodesV.size() +
adjacenciesV.nodes().size();
if (degree == 0L) {
continue;
}
adjacenciesV.degree(degree);
adjacencies.add(adjacenciesV);
double incr = lastLayerRanks.getOrDefault(vertex, 0.0) *
this.alpha / degree;
// Merge the increment of the same layer node
this.mergeSameLayerIncrRanks(sameLayerNodesV, incr,
sameLayerIncrRanks);
// Adding contributions to the previous layers
this.contributePrevLayers(ranks, incr, prevLayerNodesV);
}
Ranks newLayerRanks;
if (sameLayerTransfer) {
// First contribute to last layer, then pass to the new layer
this.contributeLastLayer(sameLayerIncrRanks, lastLayerRanks);
newLayerRanks = this.contributeNewLayer(adjacencies,
lastLayerRanks,
step.capacity);
} else {
// First pass to the new layer, then contribute to last layer
newLayerRanks = this.contributeNewLayer(adjacencies,
lastLayerRanks,
step.capacity);
this.contributeLastLayer(sameLayerIncrRanks, lastLayerRanks);
}
ranks.add(newLayerRanks);
// Re-init sources
sources = newVertices;
}
return this.topRanks(ranks, steps);
}
private boolean belongToSameLayer(Set<Id> sources, Id target,
Set<Id> sameLayerNodes) {
if (sources.contains(target)) {
sameLayerNodes.add(target);
return true;
} else {
return false;
}
}
private boolean belongToPrevLayers(List<Ranks> ranks, Id target,
Map<Integer, Set<Id>> prevLayerNodes) {
for (int i = ranks.size() - 2; i > 0; i--) {
Ranks prevLayerRanks = ranks.get(i);
if (prevLayerRanks.containsKey(target)) {
Set<Id> nodes = prevLayerNodes.computeIfAbsent(
i, HugeTraverser::newSet);
nodes.add(target);
return true;
}
}
return false;
}
private void mergeSameLayerIncrRanks(Set<Id> sameLayerNodesV, double incr,
Map<Id, Double> sameLayerIncrRanks) {
for (Id node : sameLayerNodesV) {
double oldRank = sameLayerIncrRanks.getOrDefault(node, 0.0);
sameLayerIncrRanks.put(node, oldRank + incr);
}
}
private void contributePrevLayers(List<Ranks> ranks, double incr,
Map<Integer, Set<Id>> prevLayerNodesV) {
for (Map.Entry<Integer, Set<Id>> e : prevLayerNodesV.entrySet()) {
Ranks prevLayerRanks = ranks.get(e.getKey());
for (Id node : e.getValue()) {
double oldRank = prevLayerRanks.get(node);
prevLayerRanks.put(node, oldRank + incr);
}
}
}
private void contributeLastLayer(Map<Id, Double> rankIncrs,
Ranks lastLayerRanks) {
for (Map.Entry<Id, Double> entry : rankIncrs.entrySet()) {
double originRank = lastLayerRanks.get(entry.getKey());
double incrRank = entry.getValue();
lastLayerRanks.put(entry.getKey(), originRank + incrRank);
}
}
private Ranks contributeNewLayer(List<Adjacencies> adjacencies,
Ranks lastLayerRanks, int capacity) {
Ranks newLayerRanks = new Ranks(capacity);
for (Adjacencies adjacenciesV : adjacencies) {
Id source = adjacenciesV.source();
long degree = adjacenciesV.degree();
for (Node node : adjacenciesV.nodes()) {
double rank = newLayerRanks.getOrDefault(node.id(), 0.0);
rank += (lastLayerRanks.get(source) * this.alpha / degree);
newLayerRanks.put(node.id(), rank);
}
}
return newLayerRanks;
}
private List<Map<Id, Double>> topRanks(List<Ranks> ranks,
List<Step> steps) {
assert ranks.size() > 0;
List<Map<Id, Double>> results = newList(ranks.size());
// The first layer is root node so skip i=0
results.add(ranks.get(0));
for (int i = 1; i < ranks.size(); i++) {
Step step = steps.get(i - 1);
Ranks origin = ranks.get(i);
if (origin.size() > step.top) {
results.add(origin.topN(step.top));
} else {
results.add(origin);
}
}
return results;
}
public static class Step {
private final EdgeStep edgeStep;
private final int top;
private final int capacity;
public Step(HugeGraph g, Directions direction, List<String> labels,
long degree, long skipDegree, int top, int capacity) {
E.checkArgument(top > 0 && top <= MAX_TOP,
"The top of each layer must be in (0, %s], but " +
"got %s", MAX_TOP, top);
E.checkArgument(capacity > 0,
"The capacity of each layer must be > 0, " +
"but got %s", capacity);
this.edgeStep = new EdgeStep(g, direction, labels, null,
degree, skipDegree);
this.top = top;
this.capacity = capacity;
}
}
private static class Adjacencies {
private final Id source;
private final List<Node> nodes;
private long degree;
public Adjacencies(Id source) {
this.source = source;
this.nodes = newList();
this.degree = -1L;
}
public Id source() {
return this.source;
}
public List<Node> nodes() {
return this.nodes;
}
public void add(Node node) {
this.nodes.add(node);
}
public long degree() {
E.checkArgument(this.degree > 0,
"The max degree must be > 0, but got %s",
this.degree);
return this.degree;
}
public void degree(long degree) {
this.degree = degree;
}
}
private static class Ranks extends OrderLimitMap<Id, Double> {
private static final long serialVersionUID = 2041529946017356029L;
public Ranks(int capacity) {
super(capacity);
}
public static Ranks of(Id key, Double value) {
Ranks ranks = new Ranks(1);
ranks.put(key, value);
return ranks;
}
}
}