View Javadoc
1   /*
2    * Copyright (c) 2004, The JUNG Authors 
3    *
4    * All rights reserved.
5    * Created on Jan 28, 2004
6    *
7    * This software is open-source under the BSD license; see either
8    * "license.txt" or
9    * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
10   */
11  package edu.uci.ics.jung.algorithms.blockmodel;
12  
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Collections;
16  import java.util.HashMap;
17  import java.util.HashSet;
18  import java.util.Iterator;
19  import java.util.List;
20  import java.util.Map;
21  import java.util.Set;
22  
23  import com.google.common.base.Function;
24  
25  import edu.uci.ics.jung.graph.Graph;
26  import edu.uci.ics.jung.graph.util.Pair;
27  
28  /**
29   * Identifies sets of structurally equivalent vertices in a graph. Vertices <i>
30   * i</i> and <i>j</i> are structurally equivalent iff the set of <i>i</i>'s
31   * neighbors is identical to the set of <i>j</i>'s neighbors, with the
32   * exception of <i>i</i> and <i>j</i> themselves. This algorithm finds all
33   * sets of equivalent vertices in O(V^2) time.
34   * 
35   * <p>You can extend this class to have a different definition of equivalence (by
36   * overriding <code>isStructurallyEquivalent</code>), and may give it hints for
37   * accelerating the process by overriding <code>canPossiblyCompare</code>. 
38   * (For example, in a bipartite graph, <code>canPossiblyCompare</code> may 
39   * return <code>false</code> for vertices in
40   * different partitions. This function should be fast.)
41   * 
42   * @author Danyel Fisher
43   */
44  public class StructurallyEquivalent<V,E> implements Function<Graph<V,E>, VertexPartition<V,E>> 
45  {
46  	public VertexPartition<V,E> apply(Graph<V,E> g) 
47  	{
48  	    Set<Pair<V>> vertex_pairs = getEquivalentPairs(g);
49  	    
50  	    Set<Set<V>> rv = new HashSet<Set<V>>();
51          Map<V, Set<V>> intermediate = new HashMap<V, Set<V>>();
52          for (Pair<V> p : vertex_pairs)
53          {
54              Set<V> res = intermediate.get(p.getFirst());
55              if (res == null)
56                  res = intermediate.get(p.getSecond());
57              if (res == null)  // we haven't seen this one before
58                  res = new HashSet<V>();
59              res.add(p.getFirst());
60              res.add(p.getSecond());
61              intermediate.put(p.getFirst(), res);
62              intermediate.put(p.getSecond(), res);
63          }
64          rv.addAll(intermediate.values());
65  
66          // pick up the vertices which don't appear in intermediate; they are
67          // singletons (equivalence classes of size 1)
68          Collection<V> singletons = new ArrayList<V>(g.getVertices());
69          singletons.removeAll(intermediate.keySet());
70          for (V v : singletons)
71          {
72              Set<V> v_set = Collections.singleton(v);
73              intermediate.put(v, v_set);
74              rv.add(v_set);
75          }
76  
77          return new VertexPartition<V, E>(g, intermediate, rv);
78  	}
79  
80  	/**
81  	 * For each vertex pair v, v1 in G, checks whether v and v1 are fully
82  	 * equivalent: meaning that they connect to the exact same vertices. (Is
83  	 * this regular equivalence, or whathaveyou?)
84  	 * 
85  	 * @param g the graph whose equivalent pairs are to be generated
86  	 * @return a Set of Pairs of vertices, where all the vertices in the inner
87  	 * Pairs are equivalent.
88  	 */
89  	protected Set<Pair<V>> getEquivalentPairs(Graph<V,?> g) {
90  
91  		Set<Pair<V>> rv = new HashSet<Pair<V>>();
92  		Set<V> alreadyEquivalent = new HashSet<V>();
93  
94  		List<V> l = new ArrayList<V>(g.getVertices());
95  
96  		for (V v1 : l)
97  		{
98  			if (alreadyEquivalent.contains(v1))
99  				continue;
100 
101 			for (Iterator<V> iterator = l.listIterator(l.indexOf(v1) + 1); iterator.hasNext();) {
102 			    V v2 = iterator.next();
103 
104 				if (alreadyEquivalent.contains(v2))
105 					continue;
106 
107 				if (!canBeEquivalent(v1, v2))
108 					continue;
109 
110 				if (isStructurallyEquivalent(g, v1, v2)) {
111 					Pair<V> p = new Pair<V>(v1, v2);
112 					alreadyEquivalent.add(v2);
113 					rv.add(p);
114 				}
115 			}
116 		}
117 		
118 		return rv;
119 	}
120 
121 	/**
122 	 * @param g the graph in which the structural equivalence comparison is to take place
123 	 * @param v1 the vertex to check for structural equivalence to v2
124 	 * @param v2 the vertex to check for structural equivalence to v1
125 	 * @return {@code true} if {@code v1}'s predecessors/successors are equal to
126 	 *     {@code v2}'s predecessors/successors
127 	 */
128 	protected boolean isStructurallyEquivalent(Graph<V,?> g, V v1, V v2) {
129 		
130 		if( g.degree(v1) != g.degree(v2)) {
131 			return false;
132 		}
133 
134 		Set<V> n1 = new HashSet<V>(g.getPredecessors(v1));
135 		n1.remove(v2);
136 		n1.remove(v1);
137 		Set<V> n2 = new HashSet<V>(g.getPredecessors(v2));
138 		n2.remove(v1);
139 		n2.remove(v2);
140 
141 		Set<V> o1 = new HashSet<V>(g.getSuccessors(v1));
142 		Set<V> o2 = new HashSet<V>(g.getSuccessors(v2));
143 		o1.remove(v1);
144 		o1.remove(v2);
145 		o2.remove(v1);
146 		o2.remove(v2);
147 
148 		// this neglects self-loops and directed edges from 1 to other
149 		boolean b = (n1.equals(n2) && o1.equals(o2));
150 		if (!b)
151 			return b;
152 		
153 		// if there's a directed edge v1->v2 then there's a directed edge v2->v1
154 		b &= ( g.isSuccessor(v1, v2) == g.isSuccessor(v2, v1));
155 		
156 		// self-loop check
157 		b &= ( g.isSuccessor(v1, v1) == g.isSuccessor(v2, v2));
158 
159 		return b;
160 
161 	}
162 
163 	/**
164 	 * This is a space for optimizations. For example, for a bipartite graph,
165 	 * vertices from different partitions cannot possibly be equivalent.
166 	 * 
167 	 * @param v1 the first vertex to compare
168 	 * @param v2 the second vertex to compare
169 	 * @return {@code true} if the vertices can be equivalent
170 	 */
171 	protected boolean canBeEquivalent(V v1, V v2) {
172 		return true;
173 	}
174 }