1 /*
2 * Created on Sep 19, 2005
3 *
4 * Copyright (c) 2005, The JUNG Authors
5 *
6 * All rights reserved.
7 *
8 * This software is open-source under the BSD license; see either
9 * "license.txt" or
10 * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
11 */
12 package edu.uci.ics.jung.algorithms.metrics;
13
14 import com.google.common.base.Function;
15
16 import edu.uci.ics.jung.graph.Graph;
17
18 /**
19 * Calculates some of the measures from Burt's text "Structural Holes:
20 * The Social Structure of Competition".
21 *
22 * <p><b>Notes</b>:
23 * <ul>
24 * <li>Each of these measures assumes that each edge has an associated
25 * non-null weight whose value is accessed through the specified
26 * <code>Transformer</code> instance.
27 * <li>Nonexistent edges are treated as edges with weight 0 for purposes
28 * of edge weight calculations.
29 * </ul>
30 *
31 * <p>Based on code donated by Jasper Voskuilen and
32 * Diederik van Liere of the Department of Information and Decision Sciences
33 * at Erasmus University.
34 *
35 * @author Joshua O'Madadhain
36 * @author Jasper Voskuilen
37 * @see "Ronald Burt, Structural Holes: The Social Structure of Competition"
38 * @author Tom Nelson - converted to jung2
39 */
40 public class StructuralHoles<V,E> {
41
42 protected Function<E, ? extends Number> edge_weight;
43 protected Graph<V,E> g;
44
45 /**
46 * @param graph the graph for which the metrics are to be calculated
47 * @param nev the edge weights
48 */
49 public StructuralHoles(Graph<V,E> graph, Function<E, ? extends Number> nev)
50 {
51 this.g = graph;
52 this.edge_weight = nev;
53 }
54
55 /**
56 * Burt's measure of the effective size of a vertex's network. Essentially, the
57 * number of neighbors minus the average degree of those in <code>v</code>'s neighbor set,
58 * not counting ties to <code>v</code>. Formally:
59 * <pre>
60 * effectiveSize(v) = v.degree() - (sum_{u in N(v)} sum_{w in N(u), w !=u,v} p(v,w)*m(u,w))
61 * </pre>
62 * where
63 * <ul>
64 * <li><code>N(a) = a.getNeighbors()</code>
65 * <li><code>p(v,w) =</code> normalized mutual edge weight of v and w
66 * <li><code>m(u,w)</code> = maximum-scaled mutual edge weight of u and w
67 * </ul>
68 * @param v the vertex whose properties are being measured
69 * @return the effective size of the vertex's network
70 *
71 * @see #normalizedMutualEdgeWeight(Object, Object)
72 * @see #maxScaledMutualEdgeWeight(Object, Object)
73 */
74 public double effectiveSize(V v)
75 {
76 double result = g.degree(v);
77 for(V u : g.getNeighbors(v)) {
78
79 for(V w : g.getNeighbors(u)) {
80
81 if (w != v && w != u)
82 result -= normalizedMutualEdgeWeight(v,w) *
83 maxScaledMutualEdgeWeight(u,w);
84 }
85 }
86 return result;
87 }
88
89 /**
90 * Returns the effective size of <code>v</code> divided by the number of
91 * alters in <code>v</code>'s network. (In other words,
92 * <code>effectiveSize(v) / v.degree()</code>.)
93 * If <code>v.degree() == 0</code>, returns 0.
94 *
95 * @param v the vertex whose properties are being measured
96 * @return the effective size of the vertex divided by its degree
97 */
98 public double efficiency(V v) {
99 double degree = g.degree(v);
100
101 if (degree == 0)
102 return 0;
103 else
104 return effectiveSize(v) / degree;
105 }
106
107 /**
108 * Burt's constraint measure (equation 2.4, page 55 of Burt, 1992). Essentially a
109 * measure of the extent to which <code>v</code> is invested in people who are invested in
110 * other of <code>v</code>'s alters (neighbors). The "constraint" is characterized
111 * by a lack of primary holes around each neighbor. Formally:
112 * <pre>
113 * constraint(v) = sum_{w in MP(v), w != v} localConstraint(v,w)
114 * </pre>
115 * where MP(v) is the subset of v's neighbors that are both predecessors and successors of v.
116 * @see #localConstraint(Object, Object)
117 *
118 * @param v the vertex whose properties are being measured
119 * @return the constraint of the vertex
120 */
121 public double constraint(V v) {
122 double result = 0;
123 for(V w : g.getSuccessors(v)) {
124
125 if (v != w && g.isPredecessor(v,w))
126 {
127 result += localConstraint(v, w);
128 }
129 }
130
131 return result;
132 }
133
134
135 /**
136 * Calculates the hierarchy value for a given vertex. Returns <code>NaN</code> when
137 * <code>v</code>'s degree is 0, and 1 when <code>v</code>'s degree is 1.
138 * Formally:
139 * <pre>
140 * hierarchy(v) = (sum_{v in N(v), w != v} s(v,w) * log(s(v,w))}) / (v.degree() * Math.log(v.degree())
141 * </pre>
142 * where
143 * <ul>
144 * <li><code>N(v) = v.getNeighbors()</code>
145 * <li><code>s(v,w) = localConstraint(v,w) / (aggregateConstraint(v) / v.degree())</code>
146 * </ul>
147 * @see #localConstraint(Object, Object)
148 * @see #aggregateConstraint(Object)
149 *
150 * @param v the vertex whose properties are being measured
151 * @return the hierarchy value for a given vertex
152 */
153 public double hierarchy(V v)
154 {
155 double v_degree = g.degree(v);
156
157 if (v_degree == 0)
158 return Double.NaN;
159 if (v_degree == 1)
160 return 1;
161
162 double v_constraint = aggregateConstraint(v);
163
164 double numerator = 0;
165 for (V w : g.getNeighbors(v)) {
166
167 if (v != w)
168 {
169 double sl_constraint = localConstraint(v, w) / (v_constraint / v_degree);
170 numerator += sl_constraint * Math.log(sl_constraint);
171 }
172 }
173
174 return numerator / (v_degree * Math.log(v_degree));
175 }
176
177 /**
178 * Returns the local constraint on <code>v1</code> from a lack of primary holes
179 * around its neighbor <code>v2</code>.
180 * Based on Burt's equation 2.4. Formally:
181 * <pre>
182 * localConstraint(v1, v2) = ( p(v1,v2) + ( sum_{w in N(v)} p(v1,w) * p(w, v2) ) )^2
183 * </pre>
184 * where
185 * <ul>
186 * <li><code>N(v) = v.getNeighbors()</code>
187 * <li><code>p(v,w) =</code> normalized mutual edge weight of v and w
188 * </ul>
189 * @param v1 the first vertex whose local constraint is desired
190 * @param v2 the second vertex whose local constraint is desired
191 * @return the local constraint on (v1, v2)
192 * @see #normalizedMutualEdgeWeight(Object, Object)
193 */
194 public double localConstraint(V v1, V v2)
195 {
196 double nmew_vw = normalizedMutualEdgeWeight(v1, v2);
197 double inner_result = 0;
198 for (V w : g.getNeighbors(v1)) {
199
200 inner_result += normalizedMutualEdgeWeight(v1,w) *
201 normalizedMutualEdgeWeight(w,v2);
202 }
203 return (nmew_vw + inner_result) * (nmew_vw + inner_result);
204 }
205
206 /**
207 * The aggregate constraint on <code>v</code>. Based on Burt's equation 2.7.
208 * Formally:
209 * <pre>
210 * aggregateConstraint(v) = sum_{w in N(v)} localConstraint(v,w) * O(w)
211 * </pre>
212 * where
213 * <ul>
214 * <li><code>N(v) = v.getNeighbors()</code>
215 * <li><code>O(w) = organizationalMeasure(w)</code>
216 * </ul>
217 *
218 * @param v the vertex whose properties are being measured
219 * @return the aggregate constraint on v
220 */
221 public double aggregateConstraint(V v)
222 {
223 double result = 0;
224 for (V w : g.getNeighbors(v)) {
225
226 result += localConstraint(v, w) * organizationalMeasure(g, w);
227 }
228 return result;
229 }
230
231 /**
232 * A measure of the organization of individuals within the subgraph
233 * centered on <code>v</code>. Burt's text suggests that this is
234 * in some sense a measure of how "replaceable" <code>v</code> is by
235 * some other element of this subgraph. Should be a number in the
236 * closed interval [0,1].
237 *
238 * <p>This implementation returns 1. Users may wish to override this
239 * method in order to define their own behavior.
240 * @param g the subgraph centered on v
241 * @param v the vertex whose properties are being measured
242 * @return 1.0 (in this implementation)
243 */
244 protected double organizationalMeasure(Graph<V,E> g, V v) {
245 return 1.0;
246 }
247
248
249 /**
250 * Returns the proportion of <code>v1</code>'s network time and energy invested
251 * in the relationship with <code>v2</code>. Formally:
252 * <pre>
253 * normalizedMutualEdgeWeight(a,b) = mutual_weight(a,b) / (sum_c mutual_weight(a,c))
254 * </pre>
255 * Returns 0 if either numerator or denominator = 0, or if <code>v1 == v2</code>.
256 * @see #mutualWeight(Object, Object)
257 * @param v1 the first vertex of the pair whose property is being measured
258 * @param v2 the second vertex of the pair whose property is being measured
259 * @return the normalized mutual edge weight between v1 and v2
260 */
261 protected double normalizedMutualEdgeWeight(V v1, V v2)
262 {
263 if (v1 == v2)
264 return 0;
265
266 double numerator = mutualWeight(v1, v2);
267
268 if (numerator == 0)
269 return 0;
270
271 double denominator = 0;
272 for (V v : g.getNeighbors(v1)) {
273 denominator += mutualWeight(v1, v);
274 }
275 if (denominator == 0)
276 return 0;
277
278 return numerator / denominator;
279 }
280
281 /**
282 * Returns the weight of the edge from <code>v1</code> to <code>v2</code>
283 * plus the weight of the edge from <code>v2</code> to <code>v1</code>;
284 * if either edge does not exist, it is treated as an edge with weight 0.
285 * Undirected edges are treated as two antiparallel directed edges (that
286 * is, if there is one undirected edge with weight <i>w</i> connecting
287 * <code>v1</code> to <code>v2</code>, the value returned is 2<i>w</i>).
288 * Ignores parallel edges; if there are any such, one is chosen at random.
289 * Throws <code>NullPointerException</code> if either edge is
290 * present but not assigned a weight by the constructor-specified
291 * <code>NumberEdgeValue</code>.
292 *
293 * @param v1 the first vertex of the pair whose property is being measured
294 * @param v2 the second vertex of the pair whose property is being measured
295 * @return the weights of the edges {@code<v1, v2>} and {@code <v2, v1>}
296 */
297 protected double mutualWeight(V v1, V v2)
298 {
299 E e12 = g.findEdge(v1,v2);
300 E e21 = g.findEdge(v2,v1);
301 double w12 = (e12 != null ? edge_weight.apply(e12).doubleValue() : 0);
302 double w21 = (e21 != null ? edge_weight.apply(e21).doubleValue() : 0);
303
304 return w12 + w21;
305 }
306
307 /**
308 * The marginal strength of v1's relation with contact v2.
309 * Formally:
310 * <pre>
311 * normalized_mutual_weight = mutual_weight(a,b) / (max_c mutual_weight(a,c))
312 * </pre>
313 * Returns 0 if either numerator or denominator is 0, or if <code>v1 == v2</code>.
314 *
315 * @param v1 the first vertex of the pair whose property is being measured
316 * @param v2 the second vertex of the pair whose property is being measured
317 * @return the marginal strength of v1's relation with v2
318 *
319 * @see #mutualWeight(Object, Object)
320 */
321 protected double maxScaledMutualEdgeWeight(V v1, V v2)
322 {
323 if (v1 == v2)
324 return 0;
325
326 double numerator = mutualWeight(v1, v2);
327
328 if (numerator == 0)
329 return 0;
330
331 double denominator = 0;
332 for (V w : g.getNeighbors(v1)) {
333
334 if (v2 != w)
335 denominator = Math.max(numerator, mutualWeight(v1, w));
336 }
337
338 if (denominator == 0)
339 return 0;
340
341 return numerator / denominator;
342 }
343 }