/* * File: PermanenceTest.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.community; import gov.sandia.cognition.graph.DenseMemoryGraph; import java.util.Set; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * * @author jdwendt */ public class PermanenceTest { /** * Runs the community detection and tests that modularity strictly increases * after each pass of the Louvain algorithm. */ private <NodeNameType> NodePartitioning<NodeNameType> communityDetect( DenseMemoryGraph<NodeNameType> graph) { Permanence<NodeNameType> permanence = new Permanence<>(graph, 100, 0); NodePartitioning<NodeNameType> result = permanence.solveCommunities(); return result; } /** * This helper makes the test code easier to read. It insures the two input * nodes are in the same community as each other. */ private static <NodeNameType> void testSameCommunity(NodeNameType v1, NodeNameType v2, NodePartitioning<NodeNameType> results) { assertEquals(results.getPartition(v1), results.getPartition(v2)); } @Test public void exactTest() { DenseMemoryGraph<Integer> graph = new DenseMemoryGraph<>(8, 15); graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(0, 3); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 4); graph.addEdge(4, 5); graph.addEdge(4, 6); graph.addEdge(4, 7); graph.addEdge(5, 6); // Run community detection NodePartitioning<Integer> results = communityDetect(graph); testSameCommunity(0, 1, results); testSameCommunity(0, 2, results); testSameCommunity(0, 3, results); testSameCommunity(4, 5, results); testSameCommunity(4, 6, results); testSameCommunity(4, 7, results); assertEquals(8, results.getAllMembers().size()); assertEquals(2, results.getNumPartitions()); Set<Integer> p0 = results.getPartitionMembers(0); Set<Integer> p1 = results.getPartitionMembers(1); // Swap the two so I know which partition is which if (!p0.contains(0)) { Set<Integer> tmp = p0; p0 = p1; p1 = tmp; } assertEquals(4, p0.size()); assertTrue(p0.contains(0)); assertTrue(p0.contains(1)); assertTrue(p0.contains(2)); assertTrue(p0.contains(3)); assertEquals(4, p1.size()); assertTrue(p1.contains(4)); assertTrue(p1.contains(5)); assertTrue(p1.contains(6)); assertTrue(p1.contains(7)); } @Test public void threeCommunityExactTest() { DenseMemoryGraph<Integer> graph = new DenseMemoryGraph<>(13, 33); graph.addEdge(0, 1); graph.addEdge(0, 2); graph.addEdge(0, 3); graph.addEdge(1, 2); graph.addEdge(1, 3); graph.addEdge(2, 3); graph.addEdge(3, 4); graph.addEdge(4, 5); graph.addEdge(4, 6); graph.addEdge(4, 7); graph.addEdge(4, 8); graph.addEdge(5, 6); graph.addEdge(5, 7); graph.addEdge(5, 8); graph.addEdge(6, 7); graph.addEdge(6, 8); graph.addEdge(7, 8); graph.addEdge(7, 10); graph.addEdge(7, 11); graph.addEdge(7, 12); graph.addEdge(8, 9); graph.addEdge(8, 10); // graph.addEdge(8, 11); graph.addEdge(9, 10); graph.addEdge(9, 11); graph.addEdge(9, 12); graph.addEdge(9, 13); graph.addEdge(10, 11); graph.addEdge(10, 12); graph.addEdge(10, 13); graph.addEdge(11, 12); graph.addEdge(11, 13); graph.addEdge(12, 13); // Run community detection NodePartitioning<Integer> results = communityDetect(graph); testSameCommunity(0, 1, results); testSameCommunity(0, 2, results); testSameCommunity(0, 3, results); testSameCommunity(4, 5, results); testSameCommunity(4, 6, results); testSameCommunity(4, 7, results); testSameCommunity(4, 8, results); testSameCommunity(9, 10, results); testSameCommunity(9, 11, results); testSameCommunity(9, 12, results); testSameCommunity(9, 13, results); assertEquals(14, results.getAllMembers().size()); // Turns out sometimes it finds only two partitions // assertEquals(3, results.numPartitions()); if (results.getNumPartitions() == 3) { Set<Integer> p0 = results.getPartitionMembers(0); Set<Integer> p1 = results.getPartitionMembers(1); Set<Integer> p2 = results.getPartitionMembers(2); // Swap the two so I know which partition is which if (p1.contains(0)) { Set<Integer> tmp = p0; p0 = p1; p1 = tmp; } if (p2.contains(0)) { Set<Integer> tmp = p0; p0 = p2; p2 = tmp; } if (p2.contains(4)) { Set<Integer> tmp = p1; p1 = p2; p2 = tmp; } assertEquals(4, p0.size()); assertTrue(p0.contains(0)); assertTrue(p0.contains(1)); assertTrue(p0.contains(2)); assertTrue(p0.contains(3)); assertEquals(5, p1.size()); assertTrue(p1.contains(4)); assertTrue(p1.contains(5)); assertTrue(p1.contains(6)); assertTrue(p1.contains(7)); assertTrue(p1.contains(8)); assertEquals(5, p2.size()); assertTrue(p2.contains(9)); assertTrue(p2.contains(10)); assertTrue(p2.contains(11)); assertTrue(p2.contains(12)); assertTrue(p2.contains(13)); } } /** * A more complicated test in that it takes a significantly larger graph * (and a "real" one at that!) and makes sure that various pairs of nodes * are in the same communities after community detection completes. */ @Test public void lesMisTest() { DenseMemoryGraph<String> graph = new DenseMemoryGraph<>(); graph.addEdge("Napoleon", "Myriel"); graph.addEdge("MlleBaptistine", "Myriel"); graph.addEdge("MmeMagloire", "Myriel"); graph.addEdge("MmeMagloire", "MlleBaptistine"); graph.addEdge("CountessDeLo", "Myriel"); graph.addEdge("Geborand", "Myriel"); graph.addEdge("Champtercier", "Myriel"); graph.addEdge("Cravatte", "Myriel"); graph.addEdge("Count", "Myriel"); graph.addEdge("OldMan", "Myriel"); graph.addEdge("Valjean", "Myriel"); graph.addEdge("Valjean", "MlleBaptistine"); graph.addEdge("Valjean", "MmeMagloire"); graph.addEdge("Valjean", "Labarre"); graph.addEdge("Marguerite", "Valjean"); graph.addEdge("MmeDeR", "Valjean"); graph.addEdge("Isabeau", "Valjean"); graph.addEdge("Gervais", "Valjean"); graph.addEdge("Listolier", "Tholomyes"); graph.addEdge("Fameuil", "Tholomyes"); graph.addEdge("Fameuil", "Listolier"); graph.addEdge("Blacheville", "Tholomyes"); graph.addEdge("Blacheville", "Listolier"); graph.addEdge("Blacheville", "Fameuil"); graph.addEdge("Favourite", "Tholomyes"); graph.addEdge("Favourite", "Listolier"); graph.addEdge("Favourite", "Fameuil"); graph.addEdge("Favourite", "Blacheville"); graph.addEdge("Dahlia", "Tholomyes"); graph.addEdge("Dahlia", "Listolier"); graph.addEdge("Dahlia", "Fameuil"); graph.addEdge("Dahlia", "Blacheville"); graph.addEdge("Dahlia", "Favourite"); graph.addEdge("Zephine", "Tholomyes"); graph.addEdge("Zephine", "Listolier"); graph.addEdge("Zephine", "Fameuil"); graph.addEdge("Zephine", "Blacheville"); graph.addEdge("Zephine", "Favourite"); graph.addEdge("Zephine", "Dahlia"); graph.addEdge("Fantine", "Valjean"); graph.addEdge("Fantine", "Marguerite"); graph.addEdge("Fantine", "Tholomyes"); graph.addEdge("Fantine", "Listolier"); graph.addEdge("Fantine", "Fameuil"); graph.addEdge("Fantine", "Blacheville"); graph.addEdge("Fantine", "Favourite"); graph.addEdge("Fantine", "Dahlia"); graph.addEdge("Fantine", "Zephine"); graph.addEdge("MmeThenardier", "Valjean"); graph.addEdge("MmeThenardier", "Fantine"); graph.addEdge("Thenardier", "Valjean"); graph.addEdge("Thenardier", "Fantine"); graph.addEdge("Thenardier", "MmeThenardier"); graph.addEdge("Cosette", "Valjean"); graph.addEdge("Cosette", "Tholomyes"); graph.addEdge("Cosette", "MmeThenardier"); graph.addEdge("Cosette", "Thenardier"); graph.addEdge("Javert", "Valjean"); graph.addEdge("Javert", "Fantine"); graph.addEdge("Javert", "MmeThenardier"); graph.addEdge("Javert", "Thenardier"); graph.addEdge("Javert", "Cosette"); graph.addEdge("Fauchelevent", "Valjean"); graph.addEdge("Fauchelevent", "Javert"); graph.addEdge("Bamatabois", "Valjean"); graph.addEdge("Bamatabois", "Fantine"); graph.addEdge("Bamatabois", "Javert"); graph.addEdge("Perpetue", "Fantine"); graph.addEdge("Simplice", "Valjean"); graph.addEdge("Simplice", "Fantine"); graph.addEdge("Simplice", "Javert"); graph.addEdge("Simplice", "Perpetue"); graph.addEdge("Scaufflaire", "Valjean"); graph.addEdge("Woman1", "Valjean"); graph.addEdge("Woman1", "Javert"); graph.addEdge("Judge", "Valjean"); graph.addEdge("Judge", "Bamatabois"); graph.addEdge("Champmathieu", "Valjean"); graph.addEdge("Champmathieu", "Bamatabois"); graph.addEdge("Champmathieu", "Judge"); graph.addEdge("Brevet", "Valjean"); graph.addEdge("Brevet", "Bamatabois"); graph.addEdge("Brevet", "Judge"); graph.addEdge("Brevet", "Champmathieu"); graph.addEdge("Chenildieu", "Valjean"); graph.addEdge("Chenildieu", "Bamatabois"); graph.addEdge("Chenildieu", "Judge"); graph.addEdge("Chenildieu", "Champmathieu"); graph.addEdge("Chenildieu", "Brevet"); graph.addEdge("Cochepaille", "Valjean"); graph.addEdge("Cochepaille", "Bamatabois"); graph.addEdge("Cochepaille", "Judge"); graph.addEdge("Cochepaille", "Champmathieu"); graph.addEdge("Cochepaille", "Brevet"); graph.addEdge("Cochepaille", "Chenildieu"); graph.addEdge("Pontmercy", "Thenardier"); graph.addEdge("Boulatruelle", "Thenardier"); graph.addEdge("Eponine", "MmeThenardier"); graph.addEdge("Eponine", "Thenardier"); graph.addEdge("Anzelma", "MmeThenardier"); graph.addEdge("Anzelma", "Thenardier"); graph.addEdge("Anzelma", "Eponine"); graph.addEdge("Woman2", "Valjean"); graph.addEdge("Woman2", "Cosette"); graph.addEdge("Woman2", "Javert"); graph.addEdge("MotherInnocent", "Valjean"); graph.addEdge("MotherInnocent", "Fauchelevent"); graph.addEdge("Gribier", "Fauchelevent"); graph.addEdge("MmeBurgon", "Jondrette"); graph.addEdge("Gavroche", "Valjean"); graph.addEdge("Gavroche", "Thenardier"); graph.addEdge("Gavroche", "Javert"); graph.addEdge("Gavroche", "MmeBurgon"); graph.addEdge("Gillenormand", "Valjean"); graph.addEdge("Gillenormand", "Cosette"); graph.addEdge("Magnon", "MmeThenardier"); graph.addEdge("Magnon", "Gillenormand"); graph.addEdge("MlleGillenormand", "Valjean"); graph.addEdge("MlleGillenormand", "Cosette"); graph.addEdge("MlleGillenormand", "Gillenormand"); graph.addEdge("MmePontmercy", "Pontmercy"); graph.addEdge("MmePontmercy", "MlleGillenormand"); graph.addEdge("MlleVaubois", "MlleGillenormand"); graph.addEdge("LtGillenormand", "Cosette"); graph.addEdge("LtGillenormand", "Gillenormand"); graph.addEdge("LtGillenormand", "MlleGillenormand"); graph.addEdge("Marius", "Valjean"); graph.addEdge("Marius", "Tholomyes"); graph.addEdge("Marius", "Thenardier"); graph.addEdge("Marius", "Cosette"); graph.addEdge("Marius", "Pontmercy"); graph.addEdge("Marius", "Eponine"); graph.addEdge("Marius", "Gavroche"); graph.addEdge("Marius", "Gillenormand"); graph.addEdge("Marius", "MlleGillenormand"); graph.addEdge("Marius", "LtGillenormand"); graph.addEdge("BaronessT", "Gillenormand"); graph.addEdge("BaronessT", "Marius"); graph.addEdge("Mabeuf", "Eponine"); graph.addEdge("Mabeuf", "Gavroche"); graph.addEdge("Mabeuf", "Marius"); graph.addEdge("Enjolras", "Valjean"); graph.addEdge("Enjolras", "Javert"); graph.addEdge("Enjolras", "Gavroche"); graph.addEdge("Enjolras", "Marius"); graph.addEdge("Enjolras", "Mabeuf"); graph.addEdge("Combeferre", "Gavroche"); graph.addEdge("Combeferre", "Marius"); graph.addEdge("Combeferre", "Mabeuf"); graph.addEdge("Combeferre", "Enjolras"); graph.addEdge("Prouvaire", "Gavroche"); graph.addEdge("Prouvaire", "Enjolras"); graph.addEdge("Prouvaire", "Combeferre"); graph.addEdge("Feuilly", "Gavroche"); graph.addEdge("Feuilly", "Marius"); graph.addEdge("Feuilly", "Mabeuf"); graph.addEdge("Feuilly", "Enjolras"); graph.addEdge("Feuilly", "Combeferre"); graph.addEdge("Feuilly", "Prouvaire"); graph.addEdge("Courfeyrac", "Eponine"); graph.addEdge("Courfeyrac", "Gavroche"); graph.addEdge("Courfeyrac", "Marius"); graph.addEdge("Courfeyrac", "Mabeuf"); graph.addEdge("Courfeyrac", "Enjolras"); graph.addEdge("Courfeyrac", "Combeferre"); graph.addEdge("Courfeyrac", "Prouvaire"); graph.addEdge("Courfeyrac", "Feuilly"); graph.addEdge("Bahorel", "Gavroche"); graph.addEdge("Bahorel", "Marius"); graph.addEdge("Bahorel", "Mabeuf"); graph.addEdge("Bahorel", "Enjolras"); graph.addEdge("Bahorel", "Combeferre"); graph.addEdge("Bahorel", "Prouvaire"); graph.addEdge("Bahorel", "Feuilly"); graph.addEdge("Bahorel", "Courfeyrac"); graph.addEdge("Bossuet", "Valjean"); graph.addEdge("Bossuet", "Gavroche"); graph.addEdge("Bossuet", "Marius"); graph.addEdge("Bossuet", "Mabeuf"); graph.addEdge("Bossuet", "Enjolras"); graph.addEdge("Bossuet", "Combeferre"); graph.addEdge("Bossuet", "Prouvaire"); graph.addEdge("Bossuet", "Feuilly"); graph.addEdge("Bossuet", "Courfeyrac"); graph.addEdge("Bossuet", "Bahorel"); graph.addEdge("Joly", "Gavroche"); graph.addEdge("Joly", "Marius"); graph.addEdge("Joly", "Mabeuf"); graph.addEdge("Joly", "Enjolras"); graph.addEdge("Joly", "Combeferre"); graph.addEdge("Joly", "Prouvaire"); graph.addEdge("Joly", "Feuilly"); graph.addEdge("Joly", "Courfeyrac"); graph.addEdge("Joly", "Bahorel"); graph.addEdge("Joly", "Bossuet"); graph.addEdge("Grantaire", "Gavroche"); graph.addEdge("Grantaire", "Enjolras"); graph.addEdge("Grantaire", "Combeferre"); graph.addEdge("Grantaire", "Prouvaire"); graph.addEdge("Grantaire", "Feuilly"); graph.addEdge("Grantaire", "Courfeyrac"); graph.addEdge("Grantaire", "Bahorel"); graph.addEdge("Grantaire", "Bossuet"); graph.addEdge("Grantaire", "Joly"); graph.addEdge("MotherPlutarch", "Mabeuf"); graph.addEdge("Gueulemer", "Valjean"); graph.addEdge("Gueulemer", "MmeThenardier"); graph.addEdge("Gueulemer", "Thenardier"); graph.addEdge("Gueulemer", "Javert"); graph.addEdge("Gueulemer", "Eponine"); graph.addEdge("Gueulemer", "Gavroche"); graph.addEdge("Babet", "Valjean"); graph.addEdge("Babet", "MmeThenardier"); graph.addEdge("Babet", "Thenardier"); graph.addEdge("Babet", "Javert"); graph.addEdge("Babet", "Eponine"); graph.addEdge("Babet", "Gavroche"); graph.addEdge("Babet", "Gueulemer"); graph.addEdge("Claquesous", "Valjean"); graph.addEdge("Claquesous", "MmeThenardier"); graph.addEdge("Claquesous", "Thenardier"); graph.addEdge("Claquesous", "Javert"); graph.addEdge("Claquesous", "Eponine"); graph.addEdge("Claquesous", "Enjolras"); graph.addEdge("Claquesous", "Gueulemer"); graph.addEdge("Claquesous", "Babet"); graph.addEdge("Montparnasse", "Valjean"); graph.addEdge("Montparnasse", "Thenardier"); graph.addEdge("Montparnasse", "Javert"); graph.addEdge("Montparnasse", "Eponine"); graph.addEdge("Montparnasse", "Gavroche"); graph.addEdge("Montparnasse", "Gueulemer"); graph.addEdge("Montparnasse", "Babet"); graph.addEdge("Montparnasse", "Claquesous"); graph.addEdge("Toussaint", "Valjean"); graph.addEdge("Toussaint", "Cosette"); graph.addEdge("Toussaint", "Javert"); graph.addEdge("Child1", "Gavroche"); graph.addEdge("Child2", "Gavroche"); graph.addEdge("Child2", "Child1"); graph.addEdge("Brujon", "Thenardier"); graph.addEdge("Brujon", "Eponine"); graph.addEdge("Brujon", "Gavroche"); graph.addEdge("Brujon", "Gueulemer"); graph.addEdge("Brujon", "Babet"); graph.addEdge("Brujon", "Claquesous"); graph.addEdge("Brujon", "Montparnasse"); graph.addEdge("MmeHucheloup", "Gavroche"); graph.addEdge("MmeHucheloup", "Enjolras"); graph.addEdge("MmeHucheloup", "Courfeyrac"); graph.addEdge("MmeHucheloup", "Bahorel"); graph.addEdge("MmeHucheloup", "Bossuet"); graph.addEdge("MmeHucheloup", "Joly"); graph.addEdge("MmeHucheloup", "Grantaire"); // Louvain is a randomized algorithm and this graph is sufficiently // complex that there are various possible results. Run Louvain lots // of times to see the various results are all within parameters. for (int i = 0; i < 50; ++i) { NodePartitioning<String> results = communityDetect(graph); // NodePartitioning.printPartitioning(results); // Test that various people that should be in the same community are // These are the communities assigned in d3 example on http://bl.ocks.org/mbostock/4062045 // Except those that were in individual communities and a few outliers // (commented out) that are debatable and w/ a random algorithm are // likely to flip between communities on different runs. testSameCommunity("Myriel", "Napoleon", results); //testSameCommunity("Myriel", "MlleBaptistine", results); //testSameCommunity("Myriel", "MmeMagloire", results); testSameCommunity("Myriel", "CountessDeLo", results); testSameCommunity("Myriel", "Geborand", results); testSameCommunity("Myriel", "Champtercier", results); testSameCommunity("Myriel", "Cravatte", results); testSameCommunity("Myriel", "Count", results); testSameCommunity("Myriel", "OldMan", results); testSameCommunity("Valjean", "Labarre", results); testSameCommunity("Valjean", "MmeDeR", results); testSameCommunity("Valjean", "Isabeau", results); testSameCommunity("Valjean", "Gervais", results); //testSameCommunity("Valjean", "Simplice", results); testSameCommunity("Valjean", "Scaufflaire", results); //testSameCommunity("Valjean", "Woman1", results); // I'm splitting this group in two (Labarre/Valjean) from the fully connected component below testSameCommunity("Judge", "Bamatabois", results); testSameCommunity("Judge", "Champmathieu", results); testSameCommunity("Judge", "Brevet", results); testSameCommunity("Judge", "Chenildieu", results); testSameCommunity("Judge", "Cochepaille", results); // I had to switch the person connecting to all the rest as // the old version of this file used a person tenuously connected // to the group to check all of the others testSameCommunity("Tholomyes", "Listolier", results); testSameCommunity("Tholomyes", "Fameuil", results); testSameCommunity("Tholomyes", "Blacheville", results); testSameCommunity("Tholomyes", "Favourite", results); testSameCommunity("Tholomyes", "Dahlia", results); testSameCommunity("Tholomyes", "Zephine", results); // testSameCommunity("Tholomyes", "Fantine", results); // testSameCommunity("Claquesous", "MmeThenardier", results); // testSameCommunity("Claquesous", "Thenardier", results); //testSameCommunity("Claquesous", "Javert", results); //testSameCommunity("Claquesous", "Pontmercy", results); // testSameCommunity("Claquesous", "Eponine", results); // testSameCommunity("Claquesous", "Anzelma", results); // testSameCommunity("Claquesous", "Gueulemer", results); // testSameCommunity("Claquesous", "Babet", results); // testSameCommunity("Claquesous", "Montparnasse", results); // testSameCommunity("Claquesous", "Brujon", results); //testSameCommunity("Gillenormand", "Magnon", results); // testSameCommunity("Gillenormand", "MlleGillenormand", results); //testSameCommunity("Gillenormand", "MmePontmercy", results); // testSameCommunity("Gillenormand", "MlleVaubois", results); // testSameCommunity("Gillenormand", "LtGillenormand", results); // testSameCommunity("Gillenormand", "BaronessT", results); //testSameCommunity("Gillenormand", "Toussaint", results); //testSameCommunity("Gillenormand", "Woman2", results); //testSameCommunity("Gillenormand", "Cosette", results); testSameCommunity("Jondrette", "MmeBurgon", results); //testSameCommunity("Courfeyrac", "Marius", results); // testSameCommunity("Courfeyrac", "MmeHucheloup", results); // testSameCommunity("Courfeyrac", "Mabeuf", results); // testSameCommunity("Courfeyrac", "Enjolras", results); // testSameCommunity("Courfeyrac", "Combeferre", results); // testSameCommunity("Courfeyrac", "Prouvaire", results); // testSameCommunity("Courfeyrac", "Feuilly", results); //testSameCommunity("Courfeyrac", "Gavroche", results); // testSameCommunity("Courfeyrac", "Bahorel", results); // testSameCommunity("Courfeyrac", "Bossuet", results); // testSameCommunity("Courfeyrac", "Joly", results); // testSameCommunity("Courfeyrac", "Grantaire", results); testSameCommunity("Child1", "Child2", results); // assertTrue(results.getModularity( // results.numLevels() - 1) > 0.53); // assertTrue(results.getModularity( // results.numLevels() - 1) < 0.57); } } }