/*
* Created on Jul 6, 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.HashMap;
import java.util.Map;
import org.apache.commons.collections15.Transformer;
import edu.uci.ics.jung.algorithms.scoring.util.DelegateToEdgeTransformer;
import edu.uci.ics.jung.algorithms.scoring.util.VEPair;
import edu.uci.ics.jung.algorithms.util.IterativeContext;
import edu.uci.ics.jung.graph.Hypergraph;
/**
* An abstract class for algorithms that assign scores to vertices based on
* iterative methods. Generally, any (concrete) subclass will function by
* creating an instance, and then either calling <code>evaluate</code> (if the
* user wants to iterate until the algorithms is 'done') or repeatedly call
* <code>step</code> (if the user wants to observe the values at each step).
*/
public abstract class AbstractIterativeScorer<V, E, T>
implements IterativeContext, VertexScorer<V, T> {
/**
* Maximum number of iterations to use before terminating. Defaults to 100.
*/
protected int max_iterations;
/**
* Minimum change from one step to the next; if all changes are <=
* tolerance, no further updates will occur. Defaults to 0.001.
*/
protected double tolerance;
/**
* The graph on which the calculations are to be made.
*/
protected Hypergraph<V, E> graph;
/**
* The total number of iterations used so far.
*/
protected int total_iterations;
/**
* The edge weights used by this algorithm.
*/
protected Transformer<VEPair<V, E>, ? extends Number> edge_weights;
/**
* Indicates whether the output and current values are in a 'swapped' state.
* Intended for internal use only.
*/
protected boolean output_reversed;
/**
* The map in which the output values are stored.
*/
private Map<V, T> output;
/**
* The map in which the current values are stored.
*/
private Map<V, T> current_values;
/**
* A flag representing whether this instance tolerates disconnected graphs.
* Instances that do not accept disconnected graphs may have unexpected
* behavior on disconnected graphs; they are not guaranteed to do an
* explicit check. Defaults to true.
*/
private boolean accept_disconnected_graph;
protected boolean hyperedges_are_self_loops = false;
/**
* Sets the output value for this vertex.
*
* @param v
* the vertex whose output value is to be set
* @param value
* the value to set
*/
protected void setOutputValue(V v, T value) {
output.put(v, value);
}
/**
* Gets the output value for this vertex.
*
* @param v
* the vertex whose output value is to be retrieved
* @return the output value for this vertex
*/
protected T getOutputValue(V v) {
return output.get(v);
}
/**
* Gets the current value for this vertex
*
* @param v
* the vertex whose current value is to be retrieved
* @return the current value for this vertex
*/
protected T getCurrentValue(V v) {
return current_values.get(v);
}
/**
* Sets the current value for this vertex.
*
* @param v
* the vertex whose current value is to be set
* @param value
* the current value to set
*/
protected void setCurrentValue(V v, T value) {
current_values.put(v, value);
}
/**
* The largest change seen so far among all vertex scores.
*/
protected double max_delta;
/**
* Creates an instance for the specified graph and edge weights.
*
* @param g
* the graph for which the instance is to be created
* @param edge_weights
* the edge weights for this instance
*/
public AbstractIterativeScorer(Hypergraph<V, E> g,
Transformer<E, ? extends Number> edge_weights) {
this.graph = g;
this.max_iterations = 100;
this.tolerance = 0.001;
this.accept_disconnected_graph = true;
setEdgeWeights(edge_weights);
}
/**
* Creates an instance for the specified graph <code>g</code>. NOTE: This
* constructor does not set the internal <code>edge_weights</code> variable.
* If this variable is used by the subclass which invoked this constructor,
* it must be initialized by that subclass.
*
* @param g
* the graph for which the instance is to be created
*/
public AbstractIterativeScorer(Hypergraph<V, E> g) {
this.graph = g;
this.max_iterations = 100;
this.tolerance = 0.001;
this.accept_disconnected_graph = true;
}
/**
* Initializes the internal state for this instance.
*/
protected void initialize() {
this.total_iterations = 0;
this.max_delta = Double.MIN_VALUE;
this.output_reversed = true;
this.current_values = new HashMap<V, T>();
this.output = new HashMap<V, T>();
}
/**
* Steps through this scoring algorithm until a termination condition is
* reached.
*/
public void evaluate() {
do {
step();
} while (!done());
}
/**
* Returns true if the total number of iterations is greater than or equal
* to <code>max_iterations</code> or if the maximum value change observed is
* less than <code>tolerance</code>.
*/
@Override
public boolean done() {
return total_iterations >= max_iterations || max_delta < tolerance;
}
/**
* Performs one step of this algorithm; updates the state (value) for each
* vertex.
*/
@Override
public void step() {
swapOutputForCurrent();
for (V v : graph.getVertices()) {
double diff = update(v);
updateMaxDelta(v, diff);
}
total_iterations++;
afterStep();
}
/**
*
*/
protected void swapOutputForCurrent() {
Map<V, T> tmp = output;
output = current_values;
current_values = tmp;
output_reversed = !output_reversed;
}
/**
* Updates the value for <code>v</code>. This is the key
*
* @param v
* the vertex whose value is to be updated
* @return
*/
protected abstract double update(V v);
protected void updateMaxDelta(V v, double diff) {
max_delta = Math.max(max_delta, diff);
}
protected void afterStep() {
}
@Override
public T getVertexScore(V v) {
if (!graph.containsVertex(v)) {
throw new IllegalArgumentException(
"Vertex " + v + " not an element of this graph");
}
return output.get(v);
}
/**
* Returns the maximum number of iterations that this instance will use.
*
* @return the maximum number of iterations that <code>evaluate</code> will
* use prior to terminating
*/
public int getMaxIterations() {
return max_iterations;
}
/**
* Returns the number of iterations that this instance has used so far.
*
* @return the number of iterations that this instance has used so far
*/
public int getIterations() {
return total_iterations;
}
/**
* Sets the maximum number of times that <code>evaluate</code> will call
* <code>step</code>.
*
* @param max_iterations
* the maximum
*/
public void setMaxIterations(int max_iterations) {
this.max_iterations = max_iterations;
}
/**
* Gets the size of the largest change (difference between the current and
* previous values) for any vertex that can be tolerated. Once all changes
* are less than this value, <code>evaluate</code> will terminate.
*
* @return the size of the largest change that evaluate() will permit
*/
public double getTolerance() {
return tolerance;
}
/**
* Sets the size of the largest change (difference between the current and
* previous values) for any vertex that can be tolerated.
*
* @param tolerance
* the size of the largest change that evaluate() will permit
*/
public void setTolerance(double tolerance) {
this.tolerance = tolerance;
}
/**
* Returns the Transformer that this instance uses to associate edge weights
* with each edge.
*
* @return the Transformer that associates an edge weight with each edge
*/
public Transformer<VEPair<V, E>, ? extends Number> getEdgeWeights() {
return edge_weights;
}
/**
* Sets the Transformer that this instance uses to associate edge weights
* with each edge
*
* @param edge_weights
* the Transformer to use to associate an edge weight with each
* edge
* @see edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight
*/
public void setEdgeWeights(Transformer<E, ? extends Number> edge_weights) {
this.edge_weights = new DelegateToEdgeTransformer<V, E>(edge_weights);
}
/**
* Gets the edge weight for <code>e</code> in the context of its (incident)
* vertex <code>v</code>.
*
* @param v
* the vertex incident to e as a context in which the edge weight
* is to be calculated
* @param e
* the edge whose weight is to be returned
* @return the edge weight for <code>e</code> in the context of its
* (incident) vertex <code>v</code>
*/
protected Number getEdgeWeight(V v, E e) {
return edge_weights.transform(new VEPair<V, E>(v, e));
}
/**
* Collects the 'potential' from v (its current value) if it has no outgoing
* edges; this can then be redistributed among the other vertices as a means
* of normalization.
*
* @param v
*/
protected void collectDisappearingPotential(V v) {
}
/**
* Specifies whether this instance should accept vertices with no outgoing
* edges.
*
* @param accept
* true if this instance should accept vertices with no outgoing
* edges, false otherwise
*/
public void acceptDisconnectedGraph(boolean accept) {
this.accept_disconnected_graph = accept;
}
/**
* Returns true if this instance accepts vertices with no outgoing edges,
* and false otherwise.
*
* @return true if this instance accepts vertices with no outgoing edges,
* otherwise false
*/
public boolean isDisconnectedGraphOK() {
return this.accept_disconnected_graph;
}
/**
* Specifies whether hyperedges are to be treated as self-loops. If they
* are, then potential will flow along a hyperedge a vertex to itself, just
* as it does to all other vertices incident to that hyperedge.
*
* @param arg
* if {@code true}, hyperedges are treated as self-loops
*/
public void setHyperedgesAreSelfLoops(boolean arg) {
this.hyperedges_are_self_loops = arg;
}
/**
* Returns the effective number of vertices incident to this edge. If the
* graph is a binary relation or if hyperedges are treated as self-loops,
* the value returned is {@code graph.getIncidentCount(e)}; otherwise it is
* {@code graph.getIncidentCount(e) - 1}.
*/
protected int getAdjustedIncidentCount(E e) {
return graph.getIncidentCount(e) - (hyperedges_are_self_loops ? 0 : 1);
}
}