/*
* Copyright (c) 2004, the JUNG Project and the Regents of the University
* of California
* All rights reserved.
* Created on Jan 28, 2004
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.blockmodel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections15.CollectionUtils;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
/**
* Identifies sets of structurally equivalent vertices in a graph. Vertices
* <i> i</i> and <i>j</i> are structurally equivalent iff the set of <i>i</i>'s
* neighbors is identical to the set of <i>j</i>'s neighbors, with the exception
* of <i>i</i> and <i>j</i> themselves. This algorithm finds all sets of
* equivalent vertices in O(V^2) time.
*
* <p>
* You can extend this class to have a different definition of equivalence (by
* overriding <code>isStructurallyEquivalent</code>), and may give it hints for
* accelerating the process by overriding <code>canPossiblyCompare</code>. (For
* example, in a bipartite graph, <code>canPossiblyCompare</code> may return
* <code>false</code> for vertices in different partitions. This function should
* be fast.)
*
* @author Danyel Fisher
*/
public class StructurallyEquivalent<V, E>
implements Transformer<Graph<V, E>, VertexPartition<V, E>> {
@Override
public VertexPartition<V, E> transform(Graph<V, E> g) {
Set<Pair<V>> vertex_pairs = getEquivalentPairs(g);
Set<Set<V>> rv = new HashSet<Set<V>>();
Map<V, Set<V>> intermediate = new HashMap<V, Set<V>>();
for (Pair<V> p : vertex_pairs) {
Set<V> res = intermediate.get(p.getFirst());
if (res == null) {
res = intermediate.get(p.getSecond());
}
if (res == null) {
res = new HashSet<V>();
}
res.add(p.getFirst());
res.add(p.getSecond());
intermediate.put(p.getFirst(), res);
intermediate.put(p.getSecond(), res);
}
rv.addAll(intermediate.values());
// pick up the vertices which don't appear in intermediate; they are
// singletons (equivalence classes of size 1)
Collection<V> singletons = CollectionUtils.subtract(g.getVertices(),
intermediate.keySet());
for (V v : singletons) {
Set<V> v_set = Collections.singleton(v);
intermediate.put(v, v_set);
rv.add(v_set);
}
return new VertexPartition<V, E>(g, intermediate, rv);
}
/**
* For each vertex pair v, v1 in G, checks whether v and v1 are fully
* equivalent: meaning that they connect to the exact same vertices. (Is
* this regular equivalence, or whathaveyou?)
*
* Returns a Set of Pairs of vertices, where all the vertices in the inner
* Pairs are equivalent.
*
* @param g
*/
protected Set<Pair<V>> getEquivalentPairs(Graph<V, ?> g) {
Set<Pair<V>> rv = new HashSet<Pair<V>>();
Set<V> alreadyEquivalent = new HashSet<V>();
List<V> l = new ArrayList<V>(g.getVertices());
for (V v1 : l) {
if (alreadyEquivalent.contains(v1)) {
continue;
}
for (Iterator<V> iterator = l
.listIterator(l.indexOf(v1) + 1); iterator.hasNext();) {
V v2 = iterator.next();
if (alreadyEquivalent.contains(v2)) {
continue;
}
if (!canPossiblyCompare(v1, v2)) {
continue;
}
if (isStructurallyEquivalent(g, v1, v2)) {
Pair<V> p = new Pair<V>(v1, v2);
alreadyEquivalent.add(v2);
rv.add(p);
}
}
}
return rv;
}
/**
* Checks whether a pair of vertices are structurally equivalent.
* Specifically, whether v1's predecessors are equal to v2's predecessors,
* and same for successors.
*
* @param g
* the graph in which the structural equivalence comparison is to
* take place
* @param v1
* the vertex to check for structural equivalence to v2
* @param v2
* the vertex to check for structural equivalence to v1
*/
protected boolean isStructurallyEquivalent(Graph<V, ?> g, V v1, V v2) {
if (g.degree(v1) != g.degree(v2)) {
return false;
}
Set<V> n1 = new HashSet<V>(g.getPredecessors(v1));
n1.remove(v2);
n1.remove(v1);
Set<V> n2 = new HashSet<V>(g.getPredecessors(v2));
n2.remove(v1);
n2.remove(v2);
Set<V> o1 = new HashSet<V>(g.getSuccessors(v1));
Set<V> o2 = new HashSet<V>(g.getSuccessors(v2));
o1.remove(v1);
o1.remove(v2);
o2.remove(v1);
o2.remove(v2);
// this neglects self-loops and directed edges from 1 to other
boolean b = (n1.equals(n2) && o1.equals(o2));
if (!b) {
return b;
}
// if there's a directed edge v1->v2 then there's a directed edge v2->v1
b &= (g.isSuccessor(v1, v2) == g.isSuccessor(v2, v1));
// self-loop check
b &= (g.isSuccessor(v1, v1) == g.isSuccessor(v2, v2));
return b;
}
/**
* This is a space for optimizations. For example, for a bipartite graph,
* vertices from different partitions cannot possibly be compared.
*
* @param v1
* @param v2
*/
protected boolean canPossiblyCompare(V v1, V v2) {
return true;
}
}