/*
 * 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 org.apache.commons.collections15.Transformer;

import edu.uci.ics.jung.algorithms.scoring.util.UniformDegreeWeight;
import edu.uci.ics.jung.graph.Hypergraph;

/**
 * A generalization of PageRank that permits non-uniformly-distributed random
 * jumps. The 'vertex_priors' (that is, prior probabilities for each vertex) may
 * be thought of as the fraction of the total 'potential' that is assigned to
 * that vertex at each step out of the portion that is assigned according to
 * random jumps (this portion is specified by 'alpha').
 * 
 * @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
 * @see PageRank
 */
public class PageRankWithPriors<V, E>
		extends AbstractIterativeScorerWithPriors<V, E, Double> {
	/**
	 * Maintains the amount of potential associated with vertices with no
	 * out-edges.
	 */
	protected double disappearing_potential = 0.0;

	/**
	 * Creates an instance with the specified graph, edge weights, vertex
	 * priors, and 'random jump' probability (alpha).
	 * 
	 * @param graph
	 *            the input graph
	 * @param edge_weights
	 *            the edge weights, denoting transition probabilities from
	 *            source to destination
	 * @param vertex_priors
	 *            the prior probabilities for each vertex
	 * @param alpha
	 *            the probability of executing a 'random jump' at each step
	 */
	public PageRankWithPriors(Hypergraph<V, E> graph,
			Transformer<E, ? extends Number> edge_weights,
			Transformer<V, Double> vertex_priors, double alpha) {
		super(graph, edge_weights, vertex_priors, alpha);
	}

	/**
	 * Creates an instance with the specified graph, vertex priors, and 'random
	 * jump' probability (alpha). The outgoing edge weights for each vertex will
	 * be equal and sum to 1.
	 * 
	 * @param graph
	 *            the input graph
	 * @param vertex_priors
	 *            the prior probabilities for each vertex
	 * @param alpha
	 *            the probability of executing a 'random jump' at each step
	 */
	public PageRankWithPriors(Hypergraph<V, E> graph,
			Transformer<V, Double> vertex_priors, double alpha) {
		super(graph, vertex_priors, alpha);
		this.edge_weights = new UniformDegreeWeight<V, E>(graph);
	}

	/**
	 * Updates the value for this vertex. Called by <code>step()</code>.
	 */
	@Override
	public double update(V v) {
		collectDisappearingPotential(v);

		double v_input = 0;
		for (E e : graph.getInEdges(v)) {
			// For graphs, the code below is equivalent to
			// V w = graph.getOpposite(v, e);
			// total_input += (getCurrentValue(w) *
			// getEdgeWeight(w,e).doubleValue());
			// For hypergraphs, this divides the potential coming from w
			// by the number of vertices in the connecting edge e.
			int incident_count = getAdjustedIncidentCount(e);
			for (V w : graph.getIncidentVertices(e)) {
				if (!w.equals(v) || hyperedges_are_self_loops) {
					v_input += (getCurrentValue(w)
							* getEdgeWeight(w, e).doubleValue()
							/ incident_count);
				}
			}
		}

		// modify total_input according to alpha
		double new_value = alpha > 0
				? v_input * (1 - alpha) + getVertexPrior(v) * alpha : v_input;
		setOutputValue(v, new_value);

		return Math.abs(getCurrentValue(v) - new_value);
	}

	/**
	 * Cleans up after each step. In this case that involves allocating the
	 * disappearing potential (thus maintaining normalization of the scores)
	 * according to the vertex probability priors, and then calling
	 * <code>super.afterStep</code>.
	 */
	@Override
	protected void afterStep() {
		// distribute disappearing potential according to priors
		if (disappearing_potential > 0) {
			for (V v : graph.getVertices()) {
				setOutputValue(v, getOutputValue(v) + (1 - alpha)
						* (disappearing_potential * getVertexPrior(v)));
			}
			disappearing_potential = 0;
		}

		super.afterStep();
	}

	/**
	 * Collects the "disappearing potential" associated with vertices that have
	 * no outgoing edges. Vertices that have no outgoing edges do not directly
	 * contribute to the scores of other vertices. These values are collected at
	 * each step and then distributed across all vertices as a part of the
	 * normalization process.
	 */
	@Override
	protected void collectDisappearingPotential(V v) {
		if (graph.outDegree(v) == 0) {
			if (isDisconnectedGraphOK()) {
				disappearing_potential += getCurrentValue(v);
			} else {
				throw new IllegalArgumentException(
						"Outdegree of " + v + " must be > 0");
			}
		}
	}
}
