/*
* Created on Sep 19, 2005
*
* Copyright (c) 2005, the JUNG Project and the Regents of the University
* of California
* All rights reserved.
*
* 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.metrics;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.graph.Graph;
/**
* Calculates some of the measures from Burt's text "Structural Holes: The
* Social Structure of Competition".
*
* <p>
* <b>Notes</b>:
* <ul>
* <li/>Each of these measures assumes that each edge has an associated non-null
* weight whose value is accessed through the specified <code>Transformer</code>
* instance.
* <li/>Nonexistent edges are treated as edges with weight 0 for purposes of
* edge weight calculations.
* </ul>
*
* <p>
* Based on code donated by Jasper Voskuilen and Diederik van Liere of the
* Department of Information and Decision Sciences at Erasmus University.
* </p>
*
* @author Joshua O'Madadhain
* @author Jasper Voskuilen
* @see "Ronald Burt, Structural Holes: The Social Structure of Competition"
* @author Tom Nelson - converted to jung2
*/
public class StructuralHoles<V, E> {
protected Transformer<E, ? extends Number> edge_weight;
protected Graph<V, E> g;
/**
* Creates a <code>StructuralHoles</code> instance based on the edge weights
* specified by <code>nev</code>.
*/
public StructuralHoles(Graph<V, E> graph,
Transformer<E, ? extends Number> nev) {
this.g = graph;
this.edge_weight = nev;
}
/**
* Burt's measure of the effective size of a vertex's network. Essentially,
* the number of neighbors minus the average degree of those in
* <code>v</code>'s neighbor set, not counting ties to <code>v</code>.
* Formally:
*
* <pre>
* effectiveSize(v) = v.degree() - (sum_{u in N(v)} sum_{w in N(u), w !=u,v} p(v,w)*m(u,w))
* </pre>
*
* where
* <ul>
* <li/><code>N(a) = a.getNeighbors()</code>
* <li/><code>p(v,w) =</code> normalized mutual edge weight of v and w
* <li/><code>m(u,w)</code> = maximum-scaled mutual edge weight of u and w
* </ul>
*
* @see #normalizedMutualEdgeWeight(Object, Object)
* @see #maxScaledMutualEdgeWeight(Object, Object)
*/
public double effectiveSize(V v) {
double result = g.degree(v);
for (V u : g.getNeighbors(v)) {
for (V w : g.getNeighbors(u)) {
if (w != v && w != u) {
result -= normalizedMutualEdgeWeight(v, w)
* maxScaledMutualEdgeWeight(u, w);
}
}
}
return result;
}
/**
* Returns the effective size of <code>v</code> divided by the number of
* alters in <code>v</code>'s network. (In other words,
* <code>effectiveSize(v) / v.degree()</code>.) If
* <code>v.degree() == 0</code>, returns 0.
*/
public double efficiency(V v) {
double degree = g.degree(v);
if (degree == 0) {
return 0;
}
return effectiveSize(v) / degree;
}
/**
* Burt's constraint measure (equation 2.4, page 55 of Burt, 1992).
* Essentially a measure of the extent to which <code>v</code> is invested
* in people who are invested in other of <code>v</code>'s alters
* (neighbors). The "constraint" is characterized by a lack of primary holes
* around each neighbor. Formally:
*
* <pre>
* constraint(v) = sum_{w in MP(v), w != v} localConstraint(v,w)
* </pre>
*
* where MP(v) is the subset of v's neighbors that are both predecessors and
* successors of v.
*
* @see #localConstraint(Object, Object)
*/
public double constraint(V v) {
double result = 0;
for (V w : g.getSuccessors(v)) {
if (v != w && g.isPredecessor(v, w)) {
result += localConstraint(v, w);
}
}
return result;
}
/**
* Calculates the hierarchy value for a given vertex. Returns
* <code>NaN</code> when <code>v</code>'s degree is 0, and 1 when
* <code>v</code>'s degree is 1. Formally:
*
* <pre>
* hierarchy(v) = (sum_{v in N(v), w != v} s(v,w) * log(s(v,w))}) / (v.degree() * Math.log(v.degree())
* </pre>
*
* where
* <ul>
* <li/><code>N(v) = v.getNeighbors()</code>
* <li/>
* <code>s(v,w) = localConstraint(v,w) / (aggregateConstraint(v) / v.degree())</code>
* </ul>
*
* @see #localConstraint(Object, Object)
* @see #aggregateConstraint(Object)
*/
public double hierarchy(V v) {
double v_degree = g.degree(v);
if (v_degree == 0) {
return Double.NaN;
}
if (v_degree == 1) {
return 1;
}
double v_constraint = aggregateConstraint(v);
double numerator = 0;
for (V w : g.getNeighbors(v)) {
if (v != w) {
double sl_constraint = localConstraint(v, w)
/ (v_constraint / v_degree);
numerator += sl_constraint * Math.log(sl_constraint);
}
}
return numerator / (v_degree * Math.log(v_degree));
}
/**
* Returns the local constraint on <code>v</code> from a lack of primary
* holes around its neighbor <code>v2</code>. Based on Burt's equation 2.4.
* Formally:
*
* <pre>
* localConstraint(v1, v2) = ( p(v1,v2) + ( sum_{w in N(v)} p(v1,w) * p(w, v2) ) )^2
* </pre>
*
* where
* <ul>
* <li/><code>N(v) = v.getNeighbors()</code>
* <li/><code>p(v,w) =</code> normalized mutual edge weight of v and w
* </ul>
*
* @see #normalizedMutualEdgeWeight(Object, Object)
*/
public double localConstraint(V v1, V v2) {
double nmew_vw = normalizedMutualEdgeWeight(v1, v2);
double inner_result = 0;
for (V w : g.getNeighbors(v1)) {
inner_result += normalizedMutualEdgeWeight(v1, w)
* normalizedMutualEdgeWeight(w, v2);
}
return (nmew_vw + inner_result) * (nmew_vw + inner_result);
}
/**
* The aggregate constraint on <code>v</code>. Based on Burt's equation 2.7.
* Formally:
*
* <pre>
* aggregateConstraint(v) = sum_{w in N(v)} localConstraint(v,w) * O(w)
* </pre>
*
* where
* <ul>
* <li/><code>N(v) = v.getNeighbors()</code>
* <li/><code>O(w) = organizationalMeasure(w)</code>
* </ul>
*/
public double aggregateConstraint(V v) {
double result = 0;
for (V w : g.getNeighbors(v)) {
result += localConstraint(v, w) * organizationalMeasure(g, w);
}
return result;
}
/**
* A measure of the organization of individuals within the subgraph centered
* on <code>v</code>. Burt's text suggests that this is in some sense a
* measure of how "replaceable" <code>v</code> is by some other element of
* this subgraph. Should be a number in the closed interval [0,1].
*
* <p>
* This implementation returns 1. Users may wish to override this method in
* order to define their own behavior.
* </p>
*/
protected double organizationalMeasure(Graph<V, E> g, V v) {
return 1.0;
}
/**
* Returns the proportion of <code>v1</code>'s network time and energy
* invested in the relationship with <code>v2</code>. Formally:
*
* <pre>
* normalizedMutualEdgeWeight(a,b) = mutual_weight(a,b) / (sum_c mutual_weight(a,c))
* </pre>
*
* Returns 0 if either numerator or denominator = 0, or if
* <code>v1 == v2</code>.
*
* @see #mutualWeight(Object, Object)
*/
protected double normalizedMutualEdgeWeight(V v1, V v2) {
if (v1 == v2) {
return 0;
}
double numerator = mutualWeight(v1, v2);
if (numerator == 0) {
return 0;
}
double denominator = 0;
for (V v : g.getNeighbors(v1)) {
denominator += mutualWeight(v1, v);
}
if (denominator == 0) {
return 0;
}
return numerator / denominator;
}
/**
* Returns the weight of the edge from <code>v1</code> to <code>v2</code>
* plus the weight of the edge from <code>v2</code> to <code>v1</code>; if
* either edge does not exist, it is treated as an edge with weight 0.
* Undirected edges are treated as two antiparallel directed edges (that is,
* if there is one undirected edge with weight <i>w</i> connecting
* <code>v1</code> to <code>v2</code>, the value returned is 2<i>w</i>).
* Ignores parallel edges; if there are any such, one is chosen at random.
* Throws <code>NullPointerException</code> if either edge is present but
* not assigned a weight by the constructor-specified
* <code>NumberEdgeValue</code>.
*/
protected double mutualWeight(V v1, V v2) {
E e12 = g.findEdge(v1, v2);
E e21 = g.findEdge(v2, v1);
double w12 = (e12 != null ? edge_weight.transform(e12).doubleValue()
: 0);
double w21 = (e21 != null ? edge_weight.transform(e21).doubleValue()
: 0);
return w12 + w21;
}
/**
* The marginal strength of v1's relation with contact vertex2. Formally:
*
* <pre>
* normalized_mutual_weight = mutual_weight(a,b) / (max_c mutual_weight(a,c))
* </pre>
*
* Returns 0 if either numerator or denominator is 0, or if
* <code>v1 == v2</code>.
*
* @see #mutualWeight(Object, Object)
*/
protected double maxScaledMutualEdgeWeight(V v1, V v2) {
if (v1 == v2) {
return 0;
}
double numerator = mutualWeight(v1, v2);
if (numerator == 0) {
return 0;
}
double denominator = 0;
for (V w : g.getNeighbors(v1)) {
if (v2 != w) {
denominator = Math.max(numerator, mutualWeight(v1, w));
}
}
if (denominator == 0) {
return 0;
}
return numerator / denominator;
}
}