package i5.las2peer.services.ocd.graphs;
import i5.las2peer.services.ocd.utils.Pair;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.la4j.matrix.Matrix;
import org.la4j.matrix.sparse.CCSMatrix;
import y.algo.GraphConnectivity;
import y.base.Edge;
import y.base.EdgeCursor;
import y.base.Node;
import y.base.NodeCursor;
import y.base.NodeList;
/**
* Pre-processes graphs to facilitate community detection.
* @author Sebastian
*
*/
public class GraphProcessor {
/**
* Sets the graph types of a given graph.
* @param graph A graph without multi edges.
*/
public void determineGraphTypes(CustomGraph graph) {
graph.clearTypes();
EdgeCursor edges = graph.edges();
Edge edge;
Edge reverseEdge;
while(edges.ok()) {
edge = edges.edge();
double edgeWeight = graph.getEdgeWeight(edge);
if(edgeWeight != 1) {
graph.addType(GraphType.WEIGHTED);
}
if(edgeWeight == 0) {
graph.addType(GraphType.ZERO_WEIGHTS);
}
if(edgeWeight < 0) {
graph.addType(GraphType.NEGATIVE_WEIGHTS);
}
if(edge.source().equals(edge.target())) {
graph.addType(GraphType.SELF_LOOPS);
}
reverseEdge = edge.target().getEdgeTo(edge.source());
if(reverseEdge == null || graph.getEdgeWeight(reverseEdge) != edgeWeight) {
graph.addType(GraphType.DIRECTED);
}
edges.next();
}
}
/**
* Transforms a graph into an undirected Graph.
* For each edge a reverse edge leading the opposite way is added, if missing.
* The reverse edge is assigned the same weight as the original one. If edges in both
* ways do already exist, they are assigned the sum of both weights.
* @param graph The graph to be transformed.
*/
public void makeUndirected(CustomGraph graph) {
EdgeCursor edges = graph.edges();
while(edges.ok()) {
Edge edge = edges.edge();
double edgeWeight = graph.getEdgeWeight(edge);
Edge reverseEdge;
Node target = edge.target();
Node source = edge.source();
reverseEdge = target.getEdgeTo(source);
if(reverseEdge != null && reverseEdge.index() > edge.index() && ! target.equals(source)) {
edgeWeight += graph.getEdgeWeight(reverseEdge);
graph.setEdgeWeight(edge, edgeWeight);
graph.setEdgeWeight(reverseEdge, edgeWeight);
}
else if (reverseEdge == null){
reverseEdge = graph.createEdge(target, source);
graph.setEdgeWeight(reverseEdge, edgeWeight);
}
edges.next();
}
graph.removeType(GraphType.DIRECTED);
}
/**
* Removes multi edges from a graph. Each set of parallel edges is replaced
* by an edge whose weight is the sum of the original edge weights. Other edge
* attributes correspond to a random original edge.
* @param graph The graph to be transformed.
*/
protected void removeMultiEdges(CustomGraph graph) {
EdgeCursor edges = graph.edges();
Map<Pair<Integer, Integer>, Double> nodePairWeights = new HashMap<Pair<Integer, Integer>, Double>();
while(edges.ok()) {
Edge edge = edges.edge();
Pair<Integer, Integer> nodePair = new Pair<Integer, Integer>(edge.source().index(), edge.target().index());
Double edgeWeight = nodePairWeights.get(nodePair);
if(edgeWeight == null) {
nodePairWeights.put(nodePair, graph.getEdgeWeight(edge));
}
else {
edgeWeight += graph.getEdgeWeight(edge);
nodePairWeights.put(nodePair, edgeWeight);
graph.removeEdge(edge);
}
edges.next();
}
edges.toFirst();
while(edges.ok()) {
Edge edge = edges.edge();
double edgeWeight = nodePairWeights.get(new Pair<Integer, Integer>(edge.source().index(), edge.target().index()));
graph.setEdgeWeight(edge, edgeWeight);
edges.next();
}
}
/**
* Redefines the edges of a graph according to certain criteria.
* @param graph The graph to be transformed
* @param noNegativeWeights If true edges with negative weight are removed from the graph.
* @param noZeroWeights If true edges with weight zero are removed from the graph.
* @param noSelfLoops If true self loops will be removed from the graph.
* @param setToOne If true the weight of remaining edges will be set to 1.
*/
protected void redefineEdges(CustomGraph graph, boolean noNegativeWeights, boolean noZeroWeights, boolean noSelfLoops, boolean setToOne) {
EdgeCursor edges = graph.edges();
while(edges.ok()) {
Edge edge = edges.edge();
double edgeWeight = graph.getEdgeWeight(edge);
if(noNegativeWeights && edgeWeight < 0) {
graph.removeEdge(edge);
}
else if(noZeroWeights && edgeWeight == 0) {
graph.removeEdge(edge);
}
else if(noSelfLoops && edge.source().equals(edge.target())) {
graph.removeEdge(edge);
}
else if(setToOne) {
graph.setEdgeWeight(edge, 1);
}
edges.next();
}
if(noSelfLoops) {
graph.removeType(GraphType.SELF_LOOPS);
}
if(setToOne) {
graph.removeType(GraphType.WEIGHTED);
}
if(noNegativeWeights) {
graph.removeType(GraphType.NEGATIVE_WEIGHTS);
}
if(noZeroWeights) {
graph.removeType(GraphType.ZERO_WEIGHTS);
}
}
/**
* Returns all connected components of a graph.
* @param graph The graph whose connected components are identified.
* @return A map containing the connected components and a corresponding mapping
* from the new component nodes to the original graph nodes.
*/
public List<Pair<CustomGraph, Map<Node, Node>>> divideIntoConnectedComponents(CustomGraph graph) {
/*
* Iterates over all connected components of the graph creating a copy for each of them.
*/
NodeList[] componentsArray = GraphConnectivity.connectedComponents(graph);
List<Pair<CustomGraph, Map<Node, Node>>> componentsList = new ArrayList<Pair<CustomGraph, Map<Node, Node>>>();
for(int i=0; i<componentsArray.length; i++) {
CustomGraph component = new CustomGraph();
Map<Node, Node> nodeMap = new HashMap<Node, Node>();
Map<Node, Node> tmpNodeMap = new HashMap<Node, Node>();
/*
* Sets component nodes
*/
NodeCursor nodes = componentsArray[i].nodes();
while(nodes.ok()) {
Node originalNode = nodes.node();
Node newNode = component.createNode();
component.setNodeName(newNode, graph.getNodeName(originalNode));
nodeMap.put(newNode, originalNode);
tmpNodeMap.put(originalNode, newNode);
nodes.next();
}
/*
* Sets component edges
*/
nodes.toFirst();
while(nodes.ok()) {
Node node = nodes.node();
EdgeCursor outEdges = node.outEdges();
while(outEdges.ok()) {
Edge outEdge = outEdges.edge();
Node target = outEdge.target();
Edge newEdge = component.createEdge(tmpNodeMap.get(node), tmpNodeMap.get(target));
double edgeWeight = graph.getEdgeWeight(outEdge);
component.setEdgeWeight(newEdge, edgeWeight);
outEdges.next();
}
nodes.next();
}
componentsList.add(new Pair<CustomGraph, Map<Node, Node>>(component, nodeMap));
}
return componentsList;
}
/**
* Merges the covers of the separated connected components of a graph to one single cover.
* @param graph The graph containing the connected components.
* @param componentCovers A mapping from covers of all the connected components of a graph to a corresponding node mapping,
* that maps the nodes from the connected component to the original graph nodes.
* @return The single cover of the original graph.
*/
public Cover mergeComponentCovers(CustomGraph graph, List<Pair<Cover, Map<Node, Node>>> componentCovers) {
int totalCommunityCount = 0;
for(Pair<Cover, Map<Node, Node>> componentCover : componentCovers) {
totalCommunityCount += componentCover.getFirst().communityCount();
}
Matrix memberships = new CCSMatrix(graph.nodeCount(), totalCommunityCount);
Cover currentCover = null;
CoverCreationLog algo = new CoverCreationLog(CoverCreationType.UNDEFINED, new HashMap<String, String>(), new HashSet<GraphType>());
if(!componentCovers.isEmpty()) {
algo = componentCovers.get(0).getFirst().getCreationMethod();
}
NodeCursor currentNodes;
Node node;
int currentCoverFirstCommunityIndex = 0;
double belongingFactor;
for(Pair<Cover, Map<Node, Node>> componentCover : componentCovers) {
currentCover = componentCover.getFirst();
currentNodes = currentCover.getGraph().nodes();
while(currentNodes.ok()) {
node = currentNodes.node();
for(int i=0; i<currentCover.communityCount(); i++) {
belongingFactor = currentCover.getBelongingFactor(node, i);
memberships.set(componentCover.getSecond().get(node).index(), currentCoverFirstCommunityIndex + i, belongingFactor);
}
currentNodes.next();
}
currentCoverFirstCommunityIndex += currentCover.communityCount();
if(!currentCover.getCreationMethod().equals(algo)) {
algo = new CoverCreationLog(CoverCreationType.UNDEFINED, new HashMap<String, String>(), new HashSet<GraphType>());
}
}
Cover cover = new Cover(graph, memberships);
cover.setCreationMethod(algo);
return cover;
}
/**
* Restructures a graph to make it compatible to given graph types.
* Note that the adapted graph might actually have a corrupted graph types attribute in the sense
* that there might be graph types included which are not actually required anymore.
* E.g. through the removal of self loops an originally weighted graph might become unweighted, although
* the graph type "weighted" will not be removed from the types attribute by this method due to efficiency reasons.
* However the graph types attribute of the adapted graph is guaranteed to only include types
* which appear in both the graph types attribute of the original graph and the compatible types.
* @param graph The graph to be restructured. Its graph types must be set correctly.
* @param compatibleTypes The graph types which are regarded compatible.
*/
public void makeCompatible(CustomGraph graph, Set<GraphType> compatibleTypes) {
/*
* Directed is checked before weight because e.g. positive edge weights and negative
* edge weights might balance each other out in the resulting undirected edge. This
* way more information from the original graph is maintained.
*/
removeMultiEdges(graph);
if(graph.isOfType(GraphType.DIRECTED) && ! compatibleTypes.contains(GraphType.DIRECTED)) {
this.makeUndirected(graph);
}
boolean noSelfLoops = false;
boolean noNegativeWeights = false;
boolean noZeroWeights = false;
boolean setToOne = false;
if(graph.isOfType(GraphType.SELF_LOOPS) && ! compatibleTypes.contains(GraphType.SELF_LOOPS)) {
noSelfLoops = true;
}
if(graph.isOfType(GraphType.WEIGHTED) && ! compatibleTypes.contains(GraphType.WEIGHTED)) {
setToOne = true;
noNegativeWeights = true;
noZeroWeights = true;
}
if(graph.isOfType(GraphType.NEGATIVE_WEIGHTS) && ! compatibleTypes.contains(GraphType.NEGATIVE_WEIGHTS)) {
noNegativeWeights = true;
}
if(graph.isOfType(GraphType.ZERO_WEIGHTS) && ! compatibleTypes.contains(GraphType.ZERO_WEIGHTS)) {
noZeroWeights = true;
}
this.redefineEdges(graph, noNegativeWeights, noZeroWeights, noSelfLoops, setToOne);
}
/**
* Transforms an undirected graph into a directed Graph. For each edge a
* "reverse" edge (if node A points to node B, then node B also points to node A) is added. The new
* edge is assigned the same weight as the original one.
*
* @param graph
* The graph to be transformed
* @author YLi
*/
public void makeDirected(CustomGraph graph) {
EdgeCursor edges = graph.edges();
while (edges.ok()) {
Edge edge = edges.edge();
double edgeWeight = graph.getEdgeWeight(edge);
Edge reverseEdge;
Node target = edge.target();
Node source = edge.source();
if (target.index() > source.index()) {
reverseEdge = graph.createEdge(target, source);
graph.setEdgeWeight(reverseEdge, edgeWeight);
}
edges.next();
}
graph.addType(GraphType.DIRECTED);
}
/**
* Creates a graph, which is exactly a copy of the input graph
*
* @param graph
* The graph to be copied.
* @author YLi
*/
public CustomGraph copyGraph(CustomGraph graph) {
CustomGraph graphCopy = new CustomGraph();
int nodeCount = graph.nodeCount();
Node t[] = new Node[nodeCount];
for (int i = 0; i < nodeCount; i++) {
t[i] = graphCopy.createNode();
}
EdgeCursor edges = graph.edges();
Edge edge;
while (edges.ok()) {
edge = edges.edge();
int source = edge.source().index();
int target = edge.target().index();
Edge newEdge = graphCopy.createEdge(t[source], t[target]);
graphCopy.setEdgeWeight(newEdge, graph.getEdgeWeight(edge));
edges.next();
}
return graphCopy;
}
}