package i5.las2peer.services.ocd.benchmarks; import i5.las2peer.services.ocd.graphs.Cover; import i5.las2peer.services.ocd.graphs.CoverCreationLog; import i5.las2peer.services.ocd.graphs.CoverCreationType; import i5.las2peer.services.ocd.graphs.CustomGraph; import i5.las2peer.services.ocd.graphs.GraphType; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import org.la4j.matrix.Matrix; import org.la4j.matrix.sparse.CCSMatrix; import y.base.Edge; import y.base.Node; import y.base.NodeCursor; /** * Implements the Newman Benchmark Model. * @author Sebastian * */ public class NewmanBenchmark implements GroundTruthBenchmark { /** * Defines the number of external edges of each ground truth community. * The default value is 0. Must be in [0, 8]. */ private int externalEdges = 0; /* * PARAMETER NAMES */ protected static final String EXTERNAL_EDGES_NAME = "externalEdges"; /** * Creates a standardized instance of the benchmark model. */ public NewmanBenchmark() { } public NewmanBenchmark(int externalEdges) { if(externalEdges > 8) { externalEdges = 8; } else if(externalEdges < 0) { externalEdges = 0; } this.externalEdges = externalEdges; } @Override public Map<String, String> getParameters() { Map<String, String> parameters = new HashMap<String, String>(); parameters.put(EXTERNAL_EDGES_NAME, Integer.toString(externalEdges)); return parameters; } @Override public void setParameters(Map<String, String> parameters) { if(parameters.containsKey(EXTERNAL_EDGES_NAME)) { externalEdges = Integer.parseInt(parameters.get(EXTERNAL_EDGES_NAME)); parameters.remove(EXTERNAL_EDGES_NAME); if(externalEdges < 0 || externalEdges > 8) { throw new IllegalArgumentException(); } } if(parameters.size() > 0) { throw new IllegalArgumentException(); } } @Override public Cover createGroundTruthCover() throws InterruptedException { CustomGraph graph = new CustomGraph(); Matrix membershipMatrix = new CCSMatrix(128, 4); Random rand = new Random(); /* * Randomizes the order of the node indices to randomly assign them to 4 groups. */ List<Node> nodeOrder = new ArrayList<Node>(); for(int i=0; i<128; i++) { Node node = graph.createNode(); graph.setNodeName(node, Integer.toString(node.index())); nodeOrder.add(node); } Collections.shuffle(nodeOrder); Map<Node, Integer> groupMap = new HashMap<Node, Integer>(); for(int i=0; i<4; i++) { if(Thread.interrupted()) { throw new InterruptedException(); } /* * Defines the current group and the corresponding membership entries. */ List<Node> group = nodeOrder.subList(i*32, (i+1)*32); for(Node node : group) { membershipMatrix.set(node.index(), i, 1); groupMap.put(node, i); } /* * Creates edges until each group node has the required amount of internal edges. */ List<Node> unsatisfiedNodes = new ArrayList<Node>(group); while(!unsatisfiedNodes.isEmpty()) { if(Thread.interrupted()) { throw new InterruptedException(); } int nodeAListIndex = rand.nextInt(unsatisfiedNodes.size()); Node nodeA = unsatisfiedNodes.get(nodeAListIndex); boolean edgeCreated = generateRandomInternalEdge(graph, unsatisfiedNodes, nodeA, rand); if(!edgeCreated) { redesignInternalEdges(graph, group, unsatisfiedNodes, nodeA, rand); } } } if(externalEdges > 0) { List<Node> unsatisfiedNodes = new ArrayList<Node>(nodeOrder); while(!unsatisfiedNodes.isEmpty()) { if(Thread.interrupted()) { throw new InterruptedException(); } int nodeAListIndex = rand.nextInt(unsatisfiedNodes.size()); Node nodeA = unsatisfiedNodes.get(nodeAListIndex); boolean edgeCreated = generateRandomExternalEdge(graph, groupMap, unsatisfiedNodes, nodeA, rand); if(!edgeCreated) { redesignExternalEdges(graph, groupMap, nodeOrder, unsatisfiedNodes, nodeA, rand); } } } Cover cover = new Cover(graph, membershipMatrix); cover.setCreationMethod(new CoverCreationLog(CoverCreationType.GROUND_TRUTH, new HashMap<String, String>(), new HashSet<GraphType>())); return cover; } /* * Tries to generate a random internal edge between node A and another unsatisfied Node. * An edge must only be created with a node which is not yet a neighbor of A. Returns true after success, otherwise false. * @param graph The graph which is being created. * @param unsatisfiedNodes A list of nodes which still require additional internal edges. * @param nodeA An unsatisfied node for which the edge should be created. * @param rand A generator for random numbers, included for performance. * @return TRUE if an edge could be created, or FALSE if all unsatisfied nodes are already neighbors of A. */ private boolean generateRandomInternalEdge(CustomGraph graph, List<Node> unsatisfiedNodes, Node nodeA, Random rand) { List<Node> potentialNeighbors = new ArrayList<Node>(unsatisfiedNodes); potentialNeighbors.remove(nodeA); boolean edgeCreated = false; while(potentialNeighbors.size() > 0 && !edgeCreated) { int nodeBListIndex = rand.nextInt(potentialNeighbors.size()); Node nodeB = potentialNeighbors.get(nodeBListIndex); if(!graph.containsEdge(nodeA, nodeB)) { edgeCreated = true; graph.createEdge(nodeA, nodeB); graph.createEdge(nodeB, nodeA); /* * Nodes are removed from the unsatisfied nodes when they have reached * the bound for the amount of internal edges. */ if(nodeA.outDegree() == 16 - externalEdges) { unsatisfiedNodes.remove(nodeA); } if(nodeB.outDegree() == 16 - externalEdges) { unsatisfiedNodes.remove(nodeB); } } else { potentialNeighbors.remove(nodeB); } } return edgeCreated; } /* * Deletes an existing internal edge to create a new internal edge for the unsatisfied node A. * The existing edge is chosen as an edge incident to a satisfied node B, which is not a neighbor of A. * After deleting the existing edge an edge between A and B is created. Finally the list of unsatisfied nodes is updated. * @param graph The graph which is being created * @param group The group whose internal edges are being created. * @param unsatisfiedNodes A list of nodes which still require additional internal edges. * @param nodeA The node for which a new edge will be created. * @param rand A generator for random numbers, included for performance. */ private void redesignInternalEdges(CustomGraph graph, List<Node> group, List<Node> unsatisfiedNodes, Node nodeA, Random rand) { List<Node> potentialNeighbors = new ArrayList<Node>(group); potentialNeighbors.removeAll(unsatisfiedNodes); /* * Searches a non neighbor node B for A */ Node nodeB = null; boolean nodeBFound = false; while(!nodeBFound) { int nodeBListIndex = rand.nextInt(potentialNeighbors.size()); nodeB = potentialNeighbors.get(nodeBListIndex); if(!graph.containsEdge(nodeA, nodeB)) { nodeBFound = true; } else { potentialNeighbors.remove(nodeB); } } /* * Deletes an edge incident to B and creates * a new edge between A and B */ Edge outEdge = nodeB.lastOutEdge(); Node nodeC = outEdge.target(); Edge inEdge = nodeB.getEdgeFrom(nodeC); graph.removeEdge(outEdge); graph.removeEdge(inEdge); graph.createEdge(nodeA, nodeB); graph.createEdge(nodeB, nodeA); if(!unsatisfiedNodes.contains(nodeC)) { unsatisfiedNodes.add(nodeC); } if(nodeA.outDegree() == 16 - externalEdges) { unsatisfiedNodes.remove(nodeA); } } /* * Tries to generate a random external edge between node A and another unsatisfied Node. * An edge must only be created with a node that is not yet a neighbor of A and that belongs to a different group than A. * Returns true after success, otherwise false. * @param graph The graph which is being created. * @param groupMap A mapping containing the group id of each node. * @param unsatisfiedNodes A list of nodes which still require additional external edges. * @param nodeA An unsatisfied node for which the edge should be created. * @param rand A generator for random numbers, included for performance. * @return TRUE if an edge could be created, or FALSE if all unsatisfied nodes are either neighbors of A or belong to the same community. */ private boolean generateRandomExternalEdge(CustomGraph graph, Map<Node, Integer> groupMap, List<Node> unsatisfiedNodes, Node nodeA, Random rand) { List<Node> potentialNeighbors = new ArrayList<Node>(unsatisfiedNodes); potentialNeighbors.remove(nodeA); boolean edgeCreated = false; while(potentialNeighbors.size() > 0 && !edgeCreated) { int nodeBListIndex = rand.nextInt(potentialNeighbors.size()); Node nodeB = potentialNeighbors.get(nodeBListIndex); if(!graph.containsEdge(nodeA, nodeB) && groupMap.get(nodeA) != groupMap.get(nodeB)) { edgeCreated = true; graph.createEdge(nodeA, nodeB); graph.createEdge(nodeB, nodeA); /* * Nodes are removed from the unsatisfied nodes when they have reached * the bound for the amount of total edges. */ if(nodeA.outDegree() == 16) { unsatisfiedNodes.remove(nodeA); } if(nodeB.outDegree() == 16) { unsatisfiedNodes.remove(nodeB); } } else { potentialNeighbors.remove(nodeB); } } return edgeCreated; } /* * Deletes an existing external edge to create a new external edge for the unsatisfied node A. * The existing edge is chosen as an edge incident to a satisfied node B, which is not a neighbor of A and belongs to a different community than A. * After deleting the existing edge an edge between A and B is created. Finally the list of unsatisfied nodes is updated. * @param graph The graph which is being created * @param groupMap A mapping containing the group id of each node. * @param nodes A list of all nodes of the graph. * @param unsatisfiedNodes A list of nodes which still require additional external edges. * @param nodeA The node for which a new edge will be created. * @param rand A generator for random numbers, included for performance. */ private void redesignExternalEdges(CustomGraph graph, Map<Node, Integer> groupMap, List<Node> nodes, List<Node> unsatisfiedNodes, Node nodeA, Random rand) { List<Node> potentialNeighbors = new ArrayList<Node>(nodes); potentialNeighbors.removeAll(unsatisfiedNodes); /* * Searches a non neighbor node B from another group for A. */ Node nodeB = null; boolean nodeBFound = false; while(!nodeBFound) { int nodeBListIndex = rand.nextInt(potentialNeighbors.size()); nodeB = potentialNeighbors.get(nodeBListIndex); if(!graph.containsEdge(nodeA, nodeB) && groupMap.get(nodeA) != groupMap.get(nodeB)) { nodeBFound = true; } else { potentialNeighbors.remove(nodeB); } } /* * Deletes an external edge incident to B and creates * a new edge between A and B */ NodeCursor neighbors = nodeB.successors(); Node neighbor = null; boolean externalNeighborFound = false; while(neighbors.ok() && !externalNeighborFound) { neighbor = neighbors.node(); if(groupMap.get(nodeB) != groupMap.get(neighbor)) { externalNeighborFound = true; } neighbors.next(); } Edge outEdge = nodeB.getEdgeTo(neighbor); Edge inEdge = nodeB.getEdgeFrom(neighbor); graph.removeEdge(outEdge); graph.removeEdge(inEdge); graph.createEdge(nodeA, nodeB); graph.createEdge(nodeB, nodeA); if(!unsatisfiedNodes.contains(neighbor)) { unsatisfiedNodes.add(neighbor); } if(nodeA.outDegree() == 16) { unsatisfiedNodes.remove(nodeA); } } }