/*
* File: GraphMetricsTest.java
* Authors: Jeremy D. Wendt
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright 2016, Sandia Corporation.
* Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive
* license for use of this work by or on behalf of the U.S. Government.
* Export of this program may require a license from the United States
* Government. See CopyrightHistory.txt for complete details.
*
*/
package gov.sandia.cognition.graph;
import gov.sandia.cognition.util.DefaultKeyValuePair;
import gov.sandia.cognition.util.Pair;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Tests for the GraphMetrics class
*
* @author jdwendt
*/
public class GraphMetricsTest
{
/**
* Test basic functionality for all metrics
*/
@Test
public void basicTest()
{
DirectedNodeEdgeGraph<String> g = new DenseMemoryGraph<>(7, 10);
g.addEdge("a", "b");
g.addEdge("a", "c");
g.addEdge("a", "d");
g.addEdge("b", "c");
g.addEdge("b", "d");
g.addEdge("c", "d");
g.addEdge("d", "e");
g.addEdge("e", "f");
g.addEdge("e", "g");
g.addEdge("f", "g");
g.addEdge("g", "g");
GraphMetrics<String> metrics = new GraphMetrics<>(g);
assertEquals(7, metrics.numNodes());
assertEquals(11, metrics.numEdges());
assertEquals(3, metrics.degree("a"));
assertEquals(3, metrics.degree("b"));
assertEquals(3, metrics.degree("c"));
assertEquals(4, metrics.degree("d"));
assertEquals(3, metrics.degree("e"));
assertEquals(2, metrics.degree("f"));
assertEquals(4, metrics.degree("g"));
assertEquals(3, metrics.numSuccessors("a"));
assertEquals(2, metrics.numSuccessors("b"));
assertEquals(1, metrics.numSuccessors("c"));
assertEquals(1, metrics.numSuccessors("d"));
assertEquals(2, metrics.numSuccessors("e"));
assertEquals(1, metrics.numSuccessors("f"));
assertEquals(1, metrics.numSuccessors("g"));
assertFalse(metrics.successors("a").contains("a"));
assertTrue(metrics.successors("a").contains("b"));
assertTrue(metrics.successors("a").contains("c"));
assertTrue(metrics.successors("a").contains("d"));
assertFalse(metrics.successors("a").contains("e"));
assertFalse(metrics.successors("a").contains("f"));
assertFalse(metrics.successors("a").contains("g"));
assertFalse(metrics.successors("b").contains("a"));
assertFalse(metrics.successors("b").contains("b"));
assertTrue(metrics.successors("b").contains("c"));
assertTrue(metrics.successors("b").contains("d"));
assertFalse(metrics.successors("b").contains("e"));
assertFalse(metrics.successors("b").contains("f"));
assertFalse(metrics.successors("b").contains("g"));
assertFalse(metrics.successors("c").contains("a"));
assertFalse(metrics.successors("c").contains("b"));
assertFalse(metrics.successors("c").contains("c"));
assertTrue(metrics.successors("c").contains("d"));
assertFalse(metrics.successors("c").contains("e"));
assertFalse(metrics.successors("c").contains("f"));
assertFalse(metrics.successors("c").contains("g"));
assertFalse(metrics.successors("d").contains("a"));
assertFalse(metrics.successors("d").contains("b"));
assertFalse(metrics.successors("d").contains("c"));
assertFalse(metrics.successors("d").contains("d"));
assertTrue(metrics.successors("d").contains("e"));
assertFalse(metrics.successors("d").contains("f"));
assertFalse(metrics.successors("d").contains("g"));
assertFalse(metrics.successors("e").contains("a"));
assertFalse(metrics.successors("e").contains("b"));
assertFalse(metrics.successors("e").contains("c"));
assertFalse(metrics.successors("e").contains("d"));
assertFalse(metrics.successors("e").contains("e"));
assertTrue(metrics.successors("e").contains("f"));
assertTrue(metrics.successors("e").contains("g"));
assertFalse(metrics.successors("f").contains("a"));
assertFalse(metrics.successors("f").contains("b"));
assertFalse(metrics.successors("f").contains("c"));
assertFalse(metrics.successors("f").contains("d"));
assertFalse(metrics.successors("f").contains("e"));
assertFalse(metrics.successors("f").contains("f"));
assertTrue(metrics.successors("f").contains("g"));
assertFalse(metrics.successors("g").contains("a"));
assertFalse(metrics.successors("g").contains("b"));
assertFalse(metrics.successors("g").contains("c"));
assertFalse(metrics.successors("g").contains("d"));
assertFalse(metrics.successors("g").contains("e"));
assertFalse(metrics.successors("g").contains("f"));
assertTrue(metrics.successors("g").contains("g"));
assertEquals(3, metrics.numNeighbors("a"));
assertEquals(3, metrics.numNeighbors("b"));
assertEquals(3, metrics.numNeighbors("c"));
assertEquals(4, metrics.numNeighbors("d"));
assertEquals(3, metrics.numNeighbors("e"));
assertEquals(2, metrics.numNeighbors("f"));
// NOTE: One of its degree count is due to a self loop (where both ends are the same node
assertEquals(3, metrics.numNeighbors("g"));
assertFalse(metrics.neighbors("a").contains("a"));
assertTrue(metrics.neighbors("a").contains("b"));
assertTrue(metrics.neighbors("a").contains("c"));
assertTrue(metrics.neighbors("a").contains("d"));
assertFalse(metrics.neighbors("a").contains("e"));
assertFalse(metrics.neighbors("a").contains("f"));
assertFalse(metrics.neighbors("a").contains("g"));
assertTrue(metrics.neighbors("b").contains("a"));
assertFalse(metrics.neighbors("b").contains("b"));
assertTrue(metrics.neighbors("b").contains("c"));
assertTrue(metrics.neighbors("b").contains("d"));
assertFalse(metrics.neighbors("b").contains("e"));
assertFalse(metrics.neighbors("b").contains("f"));
assertFalse(metrics.neighbors("b").contains("g"));
assertTrue(metrics.neighbors("c").contains("a"));
assertTrue(metrics.neighbors("c").contains("b"));
assertFalse(metrics.neighbors("c").contains("c"));
assertTrue(metrics.neighbors("c").contains("d"));
assertFalse(metrics.neighbors("c").contains("e"));
assertFalse(metrics.neighbors("c").contains("f"));
assertFalse(metrics.neighbors("c").contains("g"));
assertTrue(metrics.neighbors("d").contains("a"));
assertTrue(metrics.neighbors("d").contains("b"));
assertTrue(metrics.neighbors("d").contains("c"));
assertFalse(metrics.neighbors("d").contains("d"));
assertTrue(metrics.neighbors("d").contains("e"));
assertFalse(metrics.neighbors("d").contains("f"));
assertFalse(metrics.neighbors("d").contains("g"));
assertFalse(metrics.neighbors("e").contains("a"));
assertFalse(metrics.neighbors("e").contains("b"));
assertFalse(metrics.neighbors("e").contains("c"));
assertTrue(metrics.neighbors("e").contains("d"));
assertFalse(metrics.neighbors("e").contains("e"));
assertTrue(metrics.neighbors("e").contains("f"));
assertTrue(metrics.neighbors("e").contains("g"));
assertFalse(metrics.neighbors("f").contains("a"));
assertFalse(metrics.neighbors("f").contains("b"));
assertFalse(metrics.neighbors("f").contains("c"));
assertFalse(metrics.neighbors("f").contains("d"));
assertTrue(metrics.neighbors("f").contains("e"));
assertFalse(metrics.neighbors("f").contains("f"));
assertTrue(metrics.neighbors("f").contains("g"));
assertFalse(metrics.neighbors("g").contains("a"));
assertFalse(metrics.neighbors("g").contains("b"));
assertFalse(metrics.neighbors("g").contains("c"));
assertFalse(metrics.neighbors("g").contains("d"));
assertTrue(metrics.neighbors("g").contains("e"));
assertTrue(metrics.neighbors("g").contains("f"));
// It's a neighbor to itself due to its self-loop
assertTrue(metrics.neighbors("g").contains("g"));
assertEquals(3, metrics.numNodeTriangles("a"));
assertEquals(3, metrics.numNodeTriangles("b"));
assertEquals(3, metrics.numNodeTriangles("c"));
assertEquals(3, metrics.numNodeTriangles("d"));
assertEquals(1, metrics.numNodeTriangles("e"));
assertEquals(1, metrics.numNodeTriangles("f"));
assertEquals(1, metrics.numNodeTriangles("g"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("a"), "b", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("a"), "b", "d"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("a"), "d", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("b"), "a", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("b"), "a", "d"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("b"), "d", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("c"), "a", "d"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("c"), "a", "b"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("c"), "b", "d"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("d"), "a", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("d"), "a", "b"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("d"), "b", "c"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("e"), "f", "g"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("f"), "e", "g"));
assertTrue(
triangleEndpoints(metrics.getNodeTriangleEndpoints("g"), "f", "e"));
// a->b
assertEquals(2.0 / 4.0, metrics.getEdgeJaccardSimilarity(0), 1e-6);
assertEquals(1.0, metrics.getPerEdgeTriangleDensity(0), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(0),
new String[]
{
"c", "d"
});
// a->c
assertEquals(2.0 / 4.0, metrics.getEdgeJaccardSimilarity(1), 1e-6);
assertEquals(1.0, metrics.getPerEdgeTriangleDensity(1), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(1),
new String[]
{
"b", "d"
});
// a->d
assertEquals(2.0 / 5.0, metrics.getEdgeJaccardSimilarity(2), 1e-6);
assertEquals(4.0 / 5.0, metrics.getPerEdgeTriangleDensity(2), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(2),
new String[]
{
"b", "c"
});
// b->c
assertEquals(2.0 / 4.0, metrics.getEdgeJaccardSimilarity(3), 1e-6);
assertEquals(1.0, metrics.getPerEdgeTriangleDensity(3), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(3),
new String[]
{
"a", "d"
});
// b->d
assertEquals(2.0 / 5.0, metrics.getEdgeJaccardSimilarity(4), 1e-6);
assertEquals(4.0 / 5.0, metrics.getPerEdgeTriangleDensity(4), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(4),
new String[]
{
"a", "c"
});
// c->d
assertEquals(2.0 / 5.0, metrics.getEdgeJaccardSimilarity(5), 1e-6);
assertEquals(4.0 / 5.0, metrics.getPerEdgeTriangleDensity(5), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(5),
new String[]
{
"a", "b"
});
// d->e
assertEquals(0.0 / 7.0, metrics.getEdgeJaccardSimilarity(6), 1e-6);
assertEquals(0.0, metrics.getPerEdgeTriangleDensity(6), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(6),
new String[]
{
});
// e->f
assertEquals(1.0 / 4.0, metrics.getEdgeJaccardSimilarity(7), 1e-6);
assertEquals(2.0 / 3.0, metrics.getPerEdgeTriangleDensity(7), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(7),
new String[]
{
"g"
});
// e->g
assertEquals(2.0 / 4.0, metrics.getEdgeJaccardSimilarity(8), 1e-6);
assertEquals(2.0 / 5.0, metrics.getPerEdgeTriangleDensity(8), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(8),
new String[]
{
"f"
});
// f->g
assertEquals(2.0 / 3.0, metrics.getEdgeJaccardSimilarity(9), 1e-6);
assertEquals(1.0 / 2.0, metrics.getPerEdgeTriangleDensity(9), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(9),
new String[]
{
"e"
});
// g->g
assertEquals(3.0 / 3.0, metrics.getEdgeJaccardSimilarity(10), 1e-6);
assertEquals(0.0, metrics.getPerEdgeTriangleDensity(10), 1e-6);
triangleEdgeEndpoints(metrics.getEdgeTriangleOtherEndpointNames(10),
new String[]
{
});
assertEquals(3, metrics.getPerNodeEccentricity("a"));
assertEquals(3, metrics.getPerNodeEccentricity("b"));
assertEquals(3, metrics.getPerNodeEccentricity("c"));
assertEquals(2, metrics.getPerNodeEccentricity("d"));
assertEquals(2, metrics.getPerNodeEccentricity("e"));
assertEquals(3, metrics.getPerNodeEccentricity("f"));
assertEquals(3, metrics.getPerNodeEccentricity("g"));
assertEquals(2, metrics.getRadius());
assertEquals(3, metrics.getDiameter());
assertTrue(metrics.isWcc());
}
private static void triangleEdgeEndpoints(Set<String> otherEndpoints,
String[] correct)
{
assertEquals(correct.length, otherEndpoints.size());
for (String s : correct)
{
assertTrue(otherEndpoints.contains(s));
}
}
/**
* Private helper that tests the triangle endpoints contain the input
* vertices (order independently)
*
* @param triangles The set of triangle endpoints found
* @param v1 One of the two endpoints to needed
* @param v2 The other of the two endpoints needed
* @return True if one of the two orderings of v1, v2 are in the input set
*/
private static boolean triangleEndpoints(Set<Pair<String, String>> triangles,
String v1,
String v2)
{
return triangles.contains(new DefaultKeyValuePair<>(v1, v2))
|| triangles.contains(new DefaultKeyValuePair<>(v2, v1));
}
/**
* Some specific tests for degree assortativity
*/
@Test
public void testDegreeAssortativity()
{
DirectedNodeEdgeGraph<String> g = new DenseMemoryGraph<>();
for (int i = 0; i < 100; ++i)
{
for (int j = i + 1; j < 10; ++j)
{
g.addEdge("a" + i, "a" + j);
}
}
GraphMetrics<String> metrics = new GraphMetrics<>(g);
assertEquals(1.0, metrics.degreeAssortativity(), 1e-6);
g.addEdge("a0", "a0");
metrics = new GraphMetrics<>(g);
assertTrue(metrics.degreeAssortativity() > 0);
g = new DenseMemoryGraph<>();
for (int i = 0; i < 100; ++i)
{
g.addEdge("a", "b" + i);
}
metrics = new GraphMetrics<>(g);
assertEquals(-1.0, metrics.degreeAssortativity(), 1e-6);
g.addEdge("b0", "b1");
metrics = new GraphMetrics<>(g);
assertEquals(-0.98, metrics.degreeAssortativity(), 1e-2);
// This example from the "Scope" section of https://reference.wolfram.com/language/ref/GraphAssortativity.html
g = new DenseMemoryGraph<>();
g.addEdge("2", "1");
g.addEdge("3", "1");
g.addEdge("6", "1");
g.addEdge("5", "1");
g.addEdge("3", "6");
g.addEdge("4", "6");
g.addEdge("4", "5");
metrics = new GraphMetrics<>(g);
assertEquals(-5.0 / 9.0, metrics.degreeAssortativity(), 1e-6);
}
/**
* Some corner-cases for triangle tests (no pun intended)
*/
@Test
public void testTriangleCornerCases()
{
// First test -- even though there are three different edges, they only
// pass through two nodes. By definition, a triangle requires three
// nodes.
DirectedNodeEdgeGraph<String> g = new DenseMemoryGraph<>();
g.addEdge("a", "b");
g.addEdge("b", "b");
g.addEdge("b", "a");
GraphMetrics<String> m = new GraphMetrics<>(g);
assertEquals(0, m.numNodeTriangles("a"));
assertEquals(0, m.numNodeTriangles("b"));
assertEquals(0, m.numEdgeTriangles(0));
assertEquals(0, m.numEdgeTriangles(1));
assertEquals(0, m.numEdgeTriangles(2));
// Second test -- even though there are many distinct edge-sets that
// allow paths through these three nodes, we consider the triangle
// the same here.
g = new DenseMemoryGraph<>();
g.addEdge("a", "b");
g.addEdge("a", "b");
g.addEdge("b", "c");
g.addEdge("a", "c");
g.addEdge("c", "a");
g.addEdge("b", "a");
m = new GraphMetrics<>(g);
assertEquals(1, m.numNodeTriangles("a"));
assertEquals(1, m.numNodeTriangles("b"));
assertEquals(1, m.numNodeTriangles("c"));
assertEquals(1, m.numEdgeTriangles(0));
assertEquals(1, m.numEdgeTriangles(1));
assertEquals(1, m.numEdgeTriangles(2));
assertEquals(1, m.numEdgeTriangles(3));
assertEquals(1, m.numEdgeTriangles(4));
assertEquals(1, m.numEdgeTriangles(5));
}
@Test
public void testEccentricityFurther()
{
// Test for a disconnected graph
DirectedNodeEdgeGraph<String> g = new DenseMemoryGraph<>();
g.addEdge("a", "b");
g.addEdge("b", "c");
g.addEdge("c", "d");
g.addEdge("d", "e");
g.addEdge("e", "f");
g.addEdge("f", "g");
g.addEdge("h", "i");
g.addNode("j");
g.addEdge("k", "l");
g.addEdge("k", "m");
g.addEdge("n", "o");
g.addEdge("n", "p");
g.addEdge("o", "p");
GraphMetrics<String> m = new GraphMetrics<>(g);
assertEquals(6, m.getPerNodeEccentricity("a"));
assertEquals(5, m.getPerNodeEccentricity("b"));
assertEquals(4, m.getPerNodeEccentricity("c"));
assertEquals(3, m.getPerNodeEccentricity("d"));
assertEquals(4, m.getPerNodeEccentricity("e"));
assertEquals(5, m.getPerNodeEccentricity("f"));
assertEquals(6, m.getPerNodeEccentricity("g"));
assertEquals(1, m.getPerNodeEccentricity("h"));
assertEquals(1, m.getPerNodeEccentricity("i"));
assertEquals(0, m.getPerNodeEccentricity("j"));
assertEquals(0, m.getPerNodeEccentricity("j"));
assertEquals(1, m.getPerNodeEccentricity("k"));
assertEquals(2, m.getPerNodeEccentricity("l"));
assertEquals(2, m.getPerNodeEccentricity("m"));
assertEquals(1, m.getPerNodeEccentricity("n"));
assertEquals(1, m.getPerNodeEccentricity("o"));
assertEquals(1, m.getPerNodeEccentricity("p"));
assertEquals(Integer.MAX_VALUE, m.getRadius());
assertEquals(Integer.MAX_VALUE, m.getDiameter());
assertFalse(m.isWcc());
}
@Test
public void testBetweennessCentrality()
{
// Graph and answers from http://support.sas.com/documentation/cdl/en/procgralg/68145/HTML/default/viewer.htm#procgralg_optgraph_examples04.htm
DirectedNodeEdgeGraph<String> graph = new DenseMemoryGraph<>();
graph.addEdge("A", "B");
graph.addEdge("A", "C");
graph.addEdge("A", "D");
graph.addEdge("B", "C");
graph.addEdge("B", "D");
graph.addEdge("B", "E");
graph.addEdge("C", "D");
graph.addEdge("C", "F");
graph.addEdge("C", "H");
graph.addEdge("D", "E");
graph.addEdge("D", "F");
graph.addEdge("D", "G");
graph.addEdge("E", "F");
graph.addEdge("E", "G");
graph.addEdge("F", "G");
graph.addEdge("F", "H");
graph.addEdge("H", "I");
graph.addEdge("I", "J");
GraphMetrics<String> m = new GraphMetrics<>(graph);
// NOTE: I normalize by an undirected graph's values, while SAS seems to
// normalize as if it were a directed graph (so I multiply their answer by 2)
assertEquals(0.23148 * 2, m.getPerNodeBetweennessCentrality("C"), 1e-5);
assertEquals(0.23148 * 2, m.getPerNodeBetweennessCentrality("F"), 1e-5);
assertEquals(0.10185 * 2, m.getPerNodeBetweennessCentrality("D"), 1e-5);
assertEquals(0.38889 * 2, m.getPerNodeBetweennessCentrality("H"), 1e-5);
assertEquals(0.02315 * 2, m.getPerNodeBetweennessCentrality("B"), 1e-5);
assertEquals(0.02315 * 2, m.getPerNodeBetweennessCentrality("E"), 1e-5);
assertEquals(0.00000 * 2, m.getPerNodeBetweennessCentrality("A"), 1e-5);
assertEquals(0.00000 * 2, m.getPerNodeBetweennessCentrality("G"), 1e-5);
assertEquals(0.22222 * 2, m.getPerNodeBetweennessCentrality("I"), 1e-5);
assertEquals(0.00000 * 2, m.getPerNodeBetweennessCentrality("J"), 1e-5);
}
}