org.neo4j.gds.degree.DegreeCentrality Maven / Gradle / Ivy
/*
* 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.degree;
import org.apache.commons.lang3.mutable.MutableDouble;
import org.neo4j.gds.Algorithm;
import org.neo4j.gds.Orientation;
import org.neo4j.gds.api.Graph;
import org.neo4j.gds.api.properties.relationships.RelationshipIterator;
import org.neo4j.gds.api.properties.relationships.RelationshipWithPropertyConsumer;
import org.neo4j.gds.collections.ha.HugeDoubleArray;
import org.neo4j.gds.collections.haa.HugeAtomicDoubleArray;
import org.neo4j.gds.core.concurrency.Concurrency;
import org.neo4j.gds.core.concurrency.RunWithConcurrency;
import org.neo4j.gds.core.utils.paged.ParallelDoublePageCreator;
import org.neo4j.gds.core.utils.partition.Partition;
import org.neo4j.gds.core.utils.partition.PartitionUtils;
import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.function.LongToIntFunction;
import static org.neo4j.gds.utils.StringFormatting.formatWithLocale;
public class DegreeCentrality extends Algorithm {
private static final double DEFAULT_WEIGHT = 0D;
private final Graph graph;
private final ExecutorService executor;
private final Concurrency concurrency;
private final Orientation orientation;
private final boolean hasRelationshipWeightProperty;
private final int minBatchSize;
public DegreeCentrality(
Graph graph,
ExecutorService executor,
Concurrency concurrency,
Orientation orientation,
boolean hasRelationshipWeightProperty,
int minBatchSize,
ProgressTracker progressTracker
) {
super(progressTracker);
this.graph = graph;
this.executor = executor;
this.concurrency = concurrency;
this.orientation = orientation;
this.hasRelationshipWeightProperty = hasRelationshipWeightProperty;
this.minBatchSize = minBatchSize;
}
@Override
public DegreeCentralityResult compute() {
progressTracker.beginSubTask();
var result = hasRelationshipWeightProperty
? computeWeighted()
: computeUnweighted();
progressTracker.endSubTask();
return new DegreeCentralityResult(graph.nodeCount(), result);
}
private DegreeFunction computeUnweighted() {
switch (orientation) {
case NATURAL:
progressTracker.logProgress(graph.nodeCount());
return graph::degree;
case REVERSE:
return computeDegreeAtomic((partition, degrees) -> new ReverseDegreeTask(
graph.concurrentCopy(),
partition,
progressTracker,
(sourceNodeId, targetNodeId, weight) -> {
degrees.getAndAdd(targetNodeId, 1);
return true;
}
)
);
case UNDIRECTED:
return computeDegreeAtomic((partition, degrees) -> new UndirectedDegreeTask(
graph.concurrentCopy(),
partition,
degrees,
progressTracker
)
);
default:
throw new IllegalArgumentException(formatWithLocale(
"Orientation %s is not supported",
orientation
));
}
}
private DegreeFunction computeWeighted() {
switch (orientation) {
case NATURAL:
return computeDegree((partition, degrees) -> new NaturalWeightedDegreeTask(
graph.concurrentCopy(),
degrees,
partition,
progressTracker
));
case REVERSE:
return computeDegreeAtomic((partition, degrees) -> new ReverseDegreeTask(
graph.concurrentCopy(),
partition,
progressTracker,
(sourceNodeId, targetNodeId, weight) -> {
if (weight > 0.0D) {
degrees.getAndAdd(targetNodeId, weight);
}
return true;
}
)
);
case UNDIRECTED:
return computeDegreeAtomic((partition, degrees) -> new UndirectedWeightedDegreeTask(
graph.concurrentCopy(),
partition,
degrees,
progressTracker
));
default:
throw new IllegalArgumentException(formatWithLocale(
"Orientation %s is not supported",
orientation
));
}
}
@FunctionalInterface
interface TaskFunction {
Runnable apply(Partition partition, HugeDoubleArray array);
}
@FunctionalInterface
interface TaskFunctionAtomic {
Runnable apply(Partition partition, HugeAtomicDoubleArray array);
}
private DegreeFunction computeDegree(TaskFunction taskFunction) {
var degrees = HugeDoubleArray.newArray(graph.nodeCount());
var tasks = PartitionUtils.degreePartition(
graph,
concurrency,
partition -> taskFunction.apply(partition, degrees),
Optional.of(minBatchSize)
);
RunWithConcurrency.builder()
.concurrency(concurrency)
.tasks(tasks)
.executor(executor)
.run();
return degrees::get;
}
private DegreeFunction computeDegreeAtomic(TaskFunctionAtomic taskFunction) {
var degrees = HugeAtomicDoubleArray.of(graph.nodeCount(), ParallelDoublePageCreator.passThrough(concurrency));
var tasks = PartitionUtils.degreePartition(
graph,
concurrency,
partition -> taskFunction.apply(partition, degrees),
Optional.of(minBatchSize)
);
RunWithConcurrency.builder()
.concurrency(concurrency)
.tasks(tasks)
.executor(executor)
.run();
return degrees::get;
}
private static class NaturalWeightedDegreeTask implements Runnable {
private final HugeDoubleArray result;
private final RelationshipIterator relationshipIterator;
private final Partition partition;
private final ProgressTracker progressTracker;
NaturalWeightedDegreeTask(
RelationshipIterator relationshipIterator,
HugeDoubleArray result,
Partition partition,
ProgressTracker progressTracker
) {
this.relationshipIterator = relationshipIterator;
this.result = result;
this.partition = partition;
this.progressTracker = progressTracker;
}
@Override
public void run() {
var nodeWeight = new MutableDouble();
partition.consume(nodeId -> {
nodeWeight.setValue(0);
relationshipIterator.forEachRelationship(
nodeId,
DEFAULT_WEIGHT,
(sourceNodeId, targetNodeId, weight) -> {
if (weight > 0.0D) {
nodeWeight.add(weight);
}
return true;
}
);
result.set(nodeId, nodeWeight.doubleValue());
});
progressTracker.logProgress(partition.nodeCount());
}
}
private static class ReverseDegreeTask implements Runnable {
private final Graph graph;
private final Partition partition;
private final ProgressTracker progressTracker;
private final RelationshipWithPropertyConsumer consumer;
ReverseDegreeTask(
Graph graph,
Partition partition,
ProgressTracker progressTracker,
RelationshipWithPropertyConsumer consumer
) {
this.graph = graph;
this.partition = partition;
this.progressTracker = progressTracker;
this.consumer = consumer;
}
@Override
public void run() {
partition.consume(node -> graph.forEachRelationship(node, DEFAULT_WEIGHT, consumer));
progressTracker.logProgress(partition.nodeCount());
}
}
private static class UndirectedDegreeTask implements Runnable {
private final Graph graph;
private final Partition partition;
private final HugeAtomicDoubleArray degrees;
private final ProgressTracker progressTracker;
UndirectedDegreeTask(
Graph graph,
Partition partition,
HugeAtomicDoubleArray degrees,
ProgressTracker progressTracker
) {
this.graph = graph;
this.partition = partition;
this.degrees = degrees;
this.progressTracker = progressTracker;
}
@Override
public void run() {
LongToIntFunction degreeFn = graph::degree;
partition.consume(node -> {
// outgoing
degrees.getAndAdd(node, degreeFn.applyAsInt(node));
// incoming
graph.forEachRelationship(node, (sourceNodeId, targetNodeId) -> {
degrees.getAndAdd(targetNodeId, 1);
return true;
});
});
progressTracker.logProgress(partition.nodeCount());
}
}
private static class UndirectedWeightedDegreeTask implements Runnable {
private final Graph graph;
private final Partition partition;
private final HugeAtomicDoubleArray degrees;
private final ProgressTracker progressTracker;
UndirectedWeightedDegreeTask(
Graph graph,
Partition partition,
HugeAtomicDoubleArray degrees,
ProgressTracker progressTracker
) {
this.graph = graph;
this.partition = partition;
this.degrees = degrees;
this.progressTracker = progressTracker;
}
@Override
public void run() {
var nodeWeight = new MutableDouble();
partition.consume(node -> {
nodeWeight.setValue(0);
graph.forEachRelationship(node, DEFAULT_WEIGHT, ((sourceNodeId, targetNodeId, weight) -> {
if (weight > 0.0D) {
// outgoing
nodeWeight.add(weight);
// incoming
degrees.getAndAdd(targetNodeId, weight);
}
return true;
}));
degrees.getAndAdd(node, nodeWeight.doubleValue());
});
progressTracker.logProgress(partition.nodeCount());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy