Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modules/DistanceBackboneToolbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Distance Backbone Toolbox

This plugin allows you to compute the metric and ultrametric distance backbones defined in Simas et al 2021 [https://doi.org/10.1093/comnet/cnab021]. For larger networks, we recommend using the Python library distanceclosure [https://github.com/CASCI-lab/distanceclosure].

Those backbones are a parameter-free and algebraically-principles network sparsification methodologies which remove edges that break a generalized triangular inequality and, as a consequence, are redundant for shortest-path computations. Interestingly, the ultrametric backbone expands the concept of a minimum spanning tree to directed graphs and is itself a subgraph of the metric backbone for both directed and undirected networks.
74 changes: 74 additions & 0 deletions modules/DistanceBackboneToolbox/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>gephi-plugin-parent</artifactId>
<groupId>org.gephi</groupId>
<version>0.10.0</version>
</parent>

<groupId>edu.binghamton.casci</groupId>
<artifactId>distance-backbone-toolbox</artifactId>
<version>1.0.0</version>
<packaging>nbm</packaging>

<name>Distance Backbone Toolbox</name>

<organization>
<name>Complex Adaptive Systems and Computational Intelligence Lab</name>
<url>https://casci.binghamton.edu/casci.php</url>
</organization>

<dependencies>
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-openide-util-lookup</artifactId>
</dependency>
<dependency>
<groupId>org.gephi</groupId>
<artifactId>graph-api</artifactId>
</dependency>
<dependency>
<groupId>org.netbeans.api</groupId>
<artifactId>org-openide-util</artifactId>
</dependency>
<dependency>
<groupId>org.gephi</groupId>
<artifactId>statistics-api</artifactId>
<type>jar</type>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.netbeans.utilities</groupId>
<artifactId>nbm-maven-plugin</artifactId>
<configuration>
<licenseName>Apache 2.0</licenseName>
<author>Robert Palermo, Felipe Xavier Costa, Luis M. Rocha</author>
<authorEmail>[email protected]</authorEmail>
<authorUrl>https://github.com/CASCI-lab/distanceclosure</authorUrl>
<sourceCodeUrl>https://github.com/robertjosephpalermo/gephi-plugins.git</sourceCodeUrl>
<publicPackages>
<!-- Insert public packages -->
</publicPackages>
</configuration>
</plugin>
</plugins>
</build>

<!-- Snapshot Repositories (only needed if developing against a SNAPSHOT version) -->
<repositories>
<repository>
<id>oss-sonatype</id>
<name>oss-sonatype</name>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>


Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package org.robertpalermo.distancebackbonetoolbox;

import org.gephi.graph.api.*;
import java.util.*;


/*

Something important here to note is that the data that is being input into
Gephi (to have its backbone computed) must have the edge weight it is going to
use labeled in all lowercase letters as "weight".

*/


public class DistanceBackboneToolboxProcessor {
private final Graph graph;

public DistanceBackboneToolboxProcessor(GraphModel graphModel) {
this.graph = graphModel.getUndirectedGraph();
}

public void computeUltrametricBackbone() {
removeSelfLoops();
List<Node> sortedNodes = getNodesSortedByDegree();

for (Node u : sortedNodes) {
Map<Node, Double> distances = ultrametricDijkstra(u);

List<Edge> edges = new ArrayList<>();
for (Edge edge : graph.getEdges(u)) {
edges.add(edge);
}

for (Edge edge : edges) {
Node v = edge.getSource().equals(u) ? edge.getTarget() : edge.getSource();
double directWeight = edge.getWeight();

if (distances.containsKey(v) && distances.get(v) < directWeight) {
graph.removeEdge(edge);
}
}
}
}


public void computeMetricBackbone() {
removeSelfLoops();
List<Node> sortedNodes = getNodesSortedByDegree();

for (Node u : sortedNodes) {
Map<Node, Double> distances = metricDijkstra(u);
removeNonEssentialEdges(u, distances);
}
}


private void removeSelfLoops() {
List<Edge> toRemove = new ArrayList<>();
for (Edge edge : graph.getEdges()) {
if (edge.getSource().equals(edge.getTarget())) {
toRemove.add(edge);
}
}
for (Edge edge : toRemove) {
graph.removeEdge(edge);
}
}


private List<Node> getNodesSortedByDegree() {
List<Node> nodes = new ArrayList<>();
for (Node node : graph.getNodes()) {
nodes.add(node);
}
nodes.sort(Comparator.comparingDouble(graph::getDegree));
return nodes;
}


private void removeNonEssentialEdges(Node u, Map<Node, Double> distances) {
List<Edge> edges = new ArrayList<>();
for (Edge edge : graph.getEdges(u)) {
edges.add(edge);
}

for (Edge edge : edges) {
Node v = edge.getSource().equals(u) ? edge.getTarget() : edge.getSource();
double directWeight = edge.getWeight();

if (distances.containsKey(v) && distances.get(v) < directWeight) {
graph.removeEdge(edge);
}
}
}


// Only works for metric backbone.
private Map<Node, Double> metricDijkstra(Node source) {
Map<Node, Double> dist = new HashMap<>();
PriorityQueue<NodeDistance> queue = new PriorityQueue<>(Comparator.comparingDouble(nd -> nd.distance));
dist.put(source, 0.0);
queue.add(new NodeDistance(source, 0.0));

while (!queue.isEmpty()) {
NodeDistance current = queue.poll();
Node u = current.node;
double currentDist = current.distance;

if (currentDist > dist.get(u)) continue;

for (Edge edge : graph.getEdges(u)) {
Node v = edge.getSource().equals(u) ? edge.getTarget() : edge.getSource();
double weight = edge.getWeight();
double alt = dist.get(u) + weight;

if (!dist.containsKey(v) || alt < dist.get(v)) {
dist.put(v, alt);
queue.add(new NodeDistance(v, alt));
}
}
}

return dist;
}


private Map<Node, Double> ultrametricDijkstra(Node source) {
Map<Node, Double> dist = new HashMap<>();
PriorityQueue<NodeDistance> queue = new PriorityQueue<>(Comparator.comparingDouble(nd -> nd.distance));
dist.put(source, 0.0);
queue.add(new NodeDistance(source, 0.0));

while (!queue.isEmpty()) {
NodeDistance current = queue.poll();
Node u = current.node;
double currentDist = current.distance;

if (currentDist > dist.get(u)) continue;

for (Edge edge : graph.getEdges(u)) {
Node v = edge.getSource().equals(u) ? edge.getTarget() : edge.getSource();
double weight = edge.getWeight();
double alt = Math.max(dist.get(u), weight); // key difference for ultrametric

if (!dist.containsKey(v) || alt < dist.get(v)) {
dist.put(v, alt);
queue.add(new NodeDistance(v, alt));
}
}
}

return dist;
}


private static class NodeDistance {
Node node;
double distance;

NodeDistance(Node node, double distance) {
this.node = node;
this.distance = distance;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.robertpalermo.distancebackbonetoolbox;

import org.gephi.graph.api.GraphModel;
import org.gephi.statistics.spi.Statistics;

public class MetricBackbone implements Statistics {

@Override
public void execute(GraphModel graphModel) {
System.out.println("Running Metric Backbone...");

DistanceBackboneToolboxProcessor processor = new DistanceBackboneToolboxProcessor(graphModel);
processor.computeMetricBackbone();
}

@Override
public String getReport() {
return "<html><h1>Metric Backbone</h1><p>The metric backbone has been successfully computed.</p></html>";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.robertpalermo.distancebackbonetoolbox;

import org.gephi.statistics.spi.Statistics;
import org.gephi.statistics.spi.StatisticsBuilder;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = StatisticsBuilder.class)
public class MetricBackboneBuilder implements StatisticsBuilder {

public MetricBackboneBuilder() {
System.out.println(">>> MetricBackboneBuilder loaded");
}

@Override
public Statistics getStatistics() {
return new MetricBackbone();
}

@Override
public String getName() {
return "Metric Backbone";
}

@Override
public Class<? extends Statistics> getStatisticsClass() {
return MetricBackbone.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.robertpalermo.distancebackbonetoolbox;

import javax.swing.JPanel;
import org.gephi.statistics.spi.Statistics;
import org.gephi.statistics.spi.StatisticsUI;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = StatisticsUI.class)
public class MetricBackboneUI implements StatisticsUI {

private MetricBackbone metricBackbone;

@Override
public JPanel getSettingsPanel() {
return null;
}

@Override
public void setup(Statistics statistics) {
this.metricBackbone = (MetricBackbone) statistics;
}

@Override
public void unsetup() {
// Nothing to do
}

@Override
public Class<? extends Statistics> getStatisticsClass() {
return MetricBackbone.class;
}

@Override
public String getDisplayName() {
return "Metric Backbone";
}

@Override
public String getCategory() {
return StatisticsUI.CATEGORY_NETWORK_OVERVIEW;
}

@Override
public int getPosition() {
return 1000;
}

@Override
public String getShortDescription() {
return "Computes and filters the network using the metric backbone method.";
}

@Override
public String getValue() {
return null;
}
}
Loading