/*
* Created on Jul 15, 2007
*
* Copyright (c) 2007, 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.scoring;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight;
import edu.uci.ics.jung.graph.Hypergraph;
/**
* Assigns scores to vertices according to their 'voltage' in an approximate
* solution to the Kirchoff equations. This is accomplished by tying "source"
* vertices to specified positive voltages, "sink" vertices to 0 V, and
* iteratively updating the voltage of each other vertex to the (weighted)
* average of the voltages of its neighbors.
*
* <p>
* The resultant voltages will all be in the range <code>[0, max]</code> where
* <code>max</code> is the largest voltage of any source vertex (in the absence
* of negative source voltages; see below).
*
* <p>
* A few notes about this algorithm's interpretation of the graph data:
* <ul>
* <li/>Higher edge weights are interpreted as indicative of greater
* influence/effect than lower edge weights.
* <li/>Negative edge weights (and negative "source" voltages) invalidate the
* interpretation of the resultant values as voltages. However, this algorithm
* will not reject graphs with negative edge weights or source voltages.
* <li/>Parallel edges are equivalent to a single edge whose weight is the sum
* of the weights on the parallel edges.
* <li/>Current flows along undirected edges in both directions, but only flows
* along directed edges in the direction of the edge.
* </ul>
* </p>
*/
public class VoltageScorer<V, E> extends AbstractIterativeScorer<V, E, Double>
implements VertexScorer<V, Double> {
protected Map<V, ? extends Number> source_voltages;
protected Collection<V> sinks;
/**
* Creates an instance with the specified graph, edge weights, source
* voltages, and sinks.
*
* @param g
* the input graph
* @param edge_weights
* the edge weights, representing conductivity
* @param source_voltages
* the (fixed) voltage for each source
* @param sinks
* the vertices whose voltages are tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g,
Transformer<E, ? extends Number> edge_weights,
Map<V, ? extends Number> source_voltages, Collection<V> sinks) {
super(g, edge_weights);
this.source_voltages = source_voltages;
this.sinks = sinks;
initialize();
}
/**
* Creates an instance with the specified graph, edge weights, source
* vertices (each of whose 'voltages' are tied to 1), and sinks.
*
* @param g
* the input graph
* @param edge_weights
* the edge weights, representing conductivity
* @param sources
* the vertices whose voltages are tied to 1
* @param sinks
* the vertices whose voltages are tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g,
Transformer<E, ? extends Number> edge_weights,
Collection<V> sources, Collection<V> sinks) {
super(g, edge_weights);
Map<V, Double> unit_voltages = new HashMap<V, Double>();
for (V v : sources) {
unit_voltages.put(v, new Double(1.0));
}
this.source_voltages = unit_voltages;
this.sinks = sinks;
initialize();
}
/**
* Creates an instance with the specified graph, source vertices (each of
* whose 'voltages' are tied to 1), and sinks. The outgoing edges for each
* vertex are assigned weights that sum to 1.
*
* @param g
* the input graph
* @param sources
* the vertices whose voltages are tied to 1
* @param sinks
* the vertices whose voltages are tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g, Collection<V> sources,
Collection<V> sinks) {
super(g);
Map<V, Double> unit_voltages = new HashMap<V, Double>();
for (V v : sources) {
unit_voltages.put(v, new Double(1.0));
}
this.source_voltages = unit_voltages;
this.sinks = sinks;
initialize();
}
/**
* Creates an instance with the specified graph, source voltages, and sinks.
* The outgoing edges for each vertex are assigned weights that sum to 1.
*
* @param g
* the input graph
* @param source_voltages
* the (fixed) voltage for each source
* @param sinks
* the vertices whose voltages are tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g,
Map<V, ? extends Number> source_voltages, Collection<V> sinks) {
super(g);
this.source_voltages = source_voltages;
this.sinks = sinks;
this.edge_weights = new UniformDegreeWeight<V, E>(g);
initialize();
}
/**
* Creates an instance with the specified graph, edge weights, source, and
* sink. The source vertex voltage is tied to 1.
*
* @param g
* the input graph
* @param edge_weights
* the edge weights, representing conductivity
* @param source
* the vertex whose voltage is tied to 1
* @param sink
* the vertex whose voltage is tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g,
Transformer<E, ? extends Number> edge_weights, V source, V sink) {
this(g, edge_weights, Collections.singletonMap(source, 1.0),
Collections.singletonList(sink));
initialize();
}
/**
* Creates an instance with the specified graph, edge weights, source, and
* sink. The source vertex voltage is tied to 1. The outgoing edges for each
* vertex are assigned weights that sum to 1.
*
* @param g
* the input graph
* @param source
* the vertex whose voltage is tied to 1
* @param sink
* the vertex whose voltage is tied to 0
*/
public VoltageScorer(Hypergraph<V, E> g, V source, V sink) {
this(g, Collections.singletonMap(source, 1.0),
Collections.singletonList(sink));
initialize();
}
/**
* Initializes the state of this instance.
*/
@Override
public void initialize() {
super.initialize();
// sanity check
if (source_voltages.isEmpty() || sinks.isEmpty()) {
throw new IllegalArgumentException(
"Both sources and sinks (grounds) must be defined");
}
if (source_voltages.size() + sinks.size() > graph.getVertexCount()) {
throw new IllegalArgumentException(
"Source/sink sets overlap, or contain vertices not in graph");
}
for (Map.Entry<V, ? extends Number> entry : source_voltages
.entrySet()) {
V v = entry.getKey();
if (sinks.contains(v)) {
throw new IllegalArgumentException("Vertex " + v
+ " is incorrectly specified as both source and sink");
}
double value = entry.getValue().doubleValue();
if (value <= 0) {
throw new IllegalArgumentException(
"Source vertex " + v + " has negative voltage");
}
}
// set up initial voltages
for (V v : graph.getVertices()) {
if (source_voltages.containsKey(v)) {
setOutputValue(v, source_voltages.get(v).doubleValue());
} else {
setOutputValue(v, 0.0);
}
}
}
/**
* @see edu.uci.ics.jung.algorithms.scoring.AbstractIterativeScorer#update(Object)
*/
@Override
public double update(V v) {
// if it's a voltage source or sink, we're done
Number source_volts = source_voltages.get(v);
if (source_volts != null) {
setOutputValue(v, source_volts.doubleValue());
return 0.0;
}
if (sinks.contains(v)) {
setOutputValue(v, 0.0);
return 0.0;
}
Collection<E> edges = graph.getInEdges(v);
double voltage_sum = 0;
double weight_sum = 0;
for (E e : edges) {
int incident_count = getAdjustedIncidentCount(e);
for (V w : graph.getIncidentVertices(e)) {
if (!w.equals(v) || hyperedges_are_self_loops) {
double weight = getEdgeWeight(w, e).doubleValue()
/ incident_count;
voltage_sum += getCurrentValue(w).doubleValue() * weight;
weight_sum += weight;
}
}
// V w = graph.getOpposite(v, e);
// double weight = getEdgeWeight(w,e).doubleValue();
// voltage_sum += getCurrentValue(w).doubleValue() * weight;
// weight_sum += weight;
}
// if either is 0, new value is 0
if (voltage_sum == 0 || weight_sum == 0) {
setOutputValue(v, 0.0);
return getCurrentValue(v).doubleValue();
}
setOutputValue(v, voltage_sum / weight_sum);
return Math.abs(
getCurrentValue(v).doubleValue() - voltage_sum / weight_sum);
}
}