/* * 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); } }