diff --git a/modules/DistanceBackboneToolbox/README.md b/modules/DistanceBackboneToolbox/README.md new file mode 100644 index 000000000..9a9935893 --- /dev/null +++ b/modules/DistanceBackboneToolbox/README.md @@ -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. diff --git a/modules/DistanceBackboneToolbox/pom.xml b/modules/DistanceBackboneToolbox/pom.xml new file mode 100644 index 000000000..16c0c40cf --- /dev/null +++ b/modules/DistanceBackboneToolbox/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + gephi-plugin-parent + org.gephi + 0.10.0 + + + edu.binghamton.casci + distance-backbone-toolbox + 1.0.0 + nbm + + Distance Backbone Toolbox + + + Complex Adaptive Systems and Computational Intelligence Lab + https://casci.binghamton.edu/casci.php + + + + + org.netbeans.api + org-openide-util-lookup + + + org.gephi + graph-api + + + org.netbeans.api + org-openide-util + + + org.gephi + statistics-api + jar + + + + + + + org.apache.netbeans.utilities + nbm-maven-plugin + + Apache 2.0 + Robert Palermo, Felipe Xavier Costa, Luis M. Rocha + rpalermo@binghamton.edu + https://github.com/CASCI-lab/distanceclosure + https://github.com/robertjosephpalermo/gephi-plugins.git + + + + + + + + + + + + oss-sonatype + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + + + + diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/DistanceBackboneToolboxProcessor.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/DistanceBackboneToolboxProcessor.java new file mode 100644 index 000000000..fd5266709 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/DistanceBackboneToolboxProcessor.java @@ -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 sortedNodes = getNodesSortedByDegree(); + + for (Node u : sortedNodes) { + Map distances = ultrametricDijkstra(u); + + List 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 sortedNodes = getNodesSortedByDegree(); + + for (Node u : sortedNodes) { + Map distances = metricDijkstra(u); + removeNonEssentialEdges(u, distances); + } + } + + + private void removeSelfLoops() { + List 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 getNodesSortedByDegree() { + List 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 distances) { + List 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 metricDijkstra(Node source) { + Map dist = new HashMap<>(); + PriorityQueue 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 ultrametricDijkstra(Node source) { + Map dist = new HashMap<>(); + PriorityQueue 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; + } + } +} + diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackbone.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackbone.java new file mode 100644 index 000000000..9809719cb --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackbone.java @@ -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 "

Metric Backbone

The metric backbone has been successfully computed.

"; + } +} diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneBuilder.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneBuilder.java new file mode 100644 index 000000000..d6136ed51 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneBuilder.java @@ -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 getStatisticsClass() { + return MetricBackbone.class; + } +} diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneUI.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneUI.java new file mode 100644 index 000000000..2bd3e731d --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/MetricBackboneUI.java @@ -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 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; + } +} diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/Notes b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/Notes new file mode 100644 index 000000000..1b14670d2 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/Notes @@ -0,0 +1,109 @@ +/* +package org.robertpalermo.distancebackbone; + +import org.gephi.graph.api.*; +import java.util.*; + +public class DistanceBackboneProcessor { + private final Graph graph; + + public DistanceBackboneProcessor(GraphModel graphModel) { + this.graph = graphModel.getUndirectedGraph(); + } + + + public void compute() { + + // Removes self loops instead of just throwing an error like in the Python code. + List toRemove = new ArrayList<>(); + for (Edge edge : graph.getEdges()) { + if (edge.getSource().equals(edge.getTarget())) { + toRemove.add(edge); + } + } + for (Edge edge : toRemove) { + graph.removeEdge(edge); + } + + List nodes = new ArrayList<>(); + for (Node node : graph.getNodes()) { + nodes.add(node); + } + + + nodes.sort(Comparator.comparingDouble(graph::getDegree)); + + for (Node u : nodes) { + Map distances = dijkstra(u); + + + List edges = new ArrayList<>(); + for (Edge e : graph.getEdges(u)) { + edges.add(e); + } + + 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 the metric backbone. + private Map dijkstra(Node source) { + Map dist = new HashMap<>(); + PriorityQueue 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 static class NodeDistance { + Node node; + double distance; + + NodeDistance(Node node, double distance) { + this.node = node; + this.distance = distance; + } + } +} +*/ + + + + +My Manifest file: +Manifest-Version: 1.0 +OpenIDE-Module-Name: Distance Backbone Toolbox +OpenIDE-Module-Short-Description: Finds the metric and ultrametric backbone of weighted networks. They are very small subgraphs that preserve important structural and dynamical network features. +OpenIDE-Module-Long-Description: 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]. +OpenIDE-Module-Display-Category: Metric +OpenIDE-Module-Author: Robert Palermo diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackbone.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackbone.java new file mode 100644 index 000000000..f3a3bb692 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackbone.java @@ -0,0 +1,20 @@ +package org.robertpalermo.distancebackbonetoolbox; + +import org.gephi.graph.api.GraphModel; +import org.gephi.statistics.spi.Statistics; + +public class UltrametricBackbone implements Statistics { + + @Override + public void execute(GraphModel graphModel) { + System.out.println("Running Ultrametric Backbone..."); + + DistanceBackboneToolboxProcessor processor = new DistanceBackboneToolboxProcessor(graphModel); + processor.computeUltrametricBackbone(); + } + + @Override + public String getReport() { + return "

Ultrametric Backbone

The ultrametric backbone has been successfully computed.

"; + } +} \ No newline at end of file diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneBuilder.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneBuilder.java new file mode 100644 index 000000000..dec9e0620 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneBuilder.java @@ -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 UltrametricBackboneBuilder implements StatisticsBuilder { + + public UltrametricBackboneBuilder() { + System.out.println(">>> UltrametricBackboneBuilder loaded"); + } + + @Override + public Statistics getStatistics() { + return new UltrametricBackbone(); + } + + @Override + public String getName() { + return "Ultrametric Backbone"; + } + + @Override + public Class getStatisticsClass() { + return UltrametricBackbone.class; + } +} \ No newline at end of file diff --git a/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneUI.java b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneUI.java new file mode 100644 index 000000000..3cae57d5f --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/java/org/robertpalermo/distancebackbonetoolbox/UltrametricBackboneUI.java @@ -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 UltrametricBackboneUI implements StatisticsUI { + + private UltrametricBackbone ultrametricBackbone; + + @Override + public JPanel getSettingsPanel() { + return null; + } + + @Override + public void setup(Statistics statistics) { + this.ultrametricBackbone = (UltrametricBackbone) statistics; + } + + @Override + public void unsetup() { + // Nothing to do + } + + @Override + public Class getStatisticsClass() { + return UltrametricBackbone.class; + } + + @Override + public String getDisplayName() { + return "Ultrametric 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 ultrametric backbone method."; + } + + @Override + public String getValue() { + return null; + } +} \ No newline at end of file diff --git a/modules/DistanceBackboneToolbox/src/main/nbm/manifest.mf b/modules/DistanceBackboneToolbox/src/main/nbm/manifest.mf new file mode 100644 index 000000000..c0b8978e6 --- /dev/null +++ b/modules/DistanceBackboneToolbox/src/main/nbm/manifest.mf @@ -0,0 +1,17 @@ +Manifest-Version: 1.0 +OpenIDE-Module-Name: Distance Backbone Toolbox +OpenIDE-Module-Short-Description: Finds the metric and ultrametric backbone of weighted networks. They are very small subgraphs that preserve important structural and dynamical network features. +OpenIDE-Module-Long-Description: +

This plugin allows you to compute the metric and ultrametric distance + backbones defined in Simas et al 2021 (doi:10.1093/comnet/cnab021). For larger networks, we recom + mend the Python library distanceclosure (github.com/CASCI-lab/distanceclosure).

+

These backbones are parameter-free, algebraically principled network spar + sifications that remove edges violating generalized triangle inequalities, r + endering them redundant for shortest-path computations. The ultrametric back + bone generalizes the minimum spanning tree to directed graphs and is a subgr + aph of the metric backbone for both directed and undirected networks.

+

NOTE: The distance measure of a given edge, dij, must be labelled + 'weight'.

+OpenIDE-Module-Display-Category: Metric diff --git a/pom.xml b/pom.xml index afb8f2bf5..1d9795304 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ + modules/DistanceBackboneToolbox