edu.uci.ics.jung.algorithms.matrix.GraphMatrixOperations.java Source code

Java tutorial

Introduction

Here is the source code for edu.uci.ics.jung.algorithms.matrix.GraphMatrixOperations.java

Source

/*
 * Copyright (c) 2003, 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.matrix;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections15.BidiMap;
import org.apache.commons.collections15.Factory;

import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix1D;
import cern.colt.matrix.impl.SparseDoubleMatrix2D;
import cern.colt.matrix.linalg.Algebra;
import edu.uci.ics.jung.algorithms.util.ConstantMap;
import edu.uci.ics.jung.algorithms.util.Indexer;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedGraph;

/**
 * Contains methods for performing the analogues of certain matrix operations on
 * graphs.
 * <p>
 * These implementations are efficient on sparse graphs, but may not be the best
 * implementations for very dense graphs.
 * 
 * @author Joshua O'Madadhain
 * @see MatrixElementOperations
 */
public class GraphMatrixOperations {
    /**
     * Returns the graph that corresponds to the square of the (weighted)
     * adjacency matrix that the specified graph <code>g</code> encodes. The
     * implementation of MatrixElementOperations that is furnished to the
     * constructor specifies the implementation of the dot product, which is an
     * integral part of matrix multiplication.
     * 
     * @param g
     *            the graph to be squared
     * @return the result of squaring g
     */
    @SuppressWarnings("unchecked")
    public static <V, E> Graph<V, E> square(Graph<V, E> g, Factory<E> edgeFactory, MatrixElementOperations<E> meo) {
        // create new graph of same type
        Graph<V, E> squaredGraph = null;
        try {
            squaredGraph = g.getClass().newInstance();
        } catch (InstantiationException e3) {
            e3.printStackTrace();
        } catch (IllegalAccessException e3) {
            e3.printStackTrace();
        }

        Collection<V> vertices = g.getVertices();
        for (V v : vertices) {
            squaredGraph.addVertex(v);
        }
        for (V v : vertices) {
            for (V src : g.getPredecessors(v)) {
                // get the edge connecting src to v in G
                E e1 = g.findEdge(src, v);
                for (V dest : g.getSuccessors(v)) {
                    // get edge connecting v to dest in G
                    E e2 = g.findEdge(v, dest);
                    // collect data on path composed of e1 and e2
                    Number pathData = meo.computePathData(e1, e2);
                    E e = squaredGraph.findEdge(src, dest);
                    // if no edge from src to dest exists in G2, create one
                    if (e == null) {
                        e = edgeFactory.create();
                        squaredGraph.addEdge(e, src, dest);
                    }
                    meo.mergePaths(e, pathData);
                }
            }
        }
        return squaredGraph;
    }

    /**
     * Creates a graph from a square (weighted) adjacency matrix.  If 
     * <code>nev</code> is non-null then it will be used to store the edge weights.
     * 
     * <p>Notes on implementation: 
     * <ul>
     * <li>The matrix indices will be mapped onto vertices in the order in which the
     * vertex factory generates the vertices.  This means the user is responsible
     * <li>The type of edges created (directed or undirected) depends
     * entirely on the graph factory supplied, regardless of whether the 
     * matrix is symmetric or not.  The Colt {@code Property.isSymmetric} 
     * method may be used to find out whether the matrix
     * is symmetric prior to making this call.
     * <li>The matrix supplied need not be square.  If it is not square, then 
     * the 
     * 
     * @return a representation of <code>matrix</code> as a JUNG
     *         <code>Graph</code>
     */
    public static <V, E> Graph<V, E> matrixToGraph(DoubleMatrix2D matrix,
            Factory<? extends Graph<V, E>> graphFactory, Factory<V> vertexFactory, Factory<E> edgeFactory,
            Map<E, Number> nev) {
        if (matrix.rows() != matrix.columns()) {
            throw new IllegalArgumentException("Matrix must be square.");
        }
        int size = matrix.rows();

        Graph<V, E> graph = graphFactory.create();

        for (int i = 0; i < size; i++) {
            V vertex = vertexFactory.create();
            graph.addVertex(vertex);
        }

        List<V> vertices = new ArrayList<V>(graph.getVertices());
        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                double value = matrix.getQuick(i, j);
                if (value != 0) {
                    E e = edgeFactory.create();
                    if (graph.addEdge(e, vertices.get(i), vertices.get(j))) {
                        if (e != null && nev != null)
                            nev.put(e, value);
                    }
                }
            }
        }

        return graph;
    }

    /**
     * Creates a graph from a square (weighted) adjacency matrix.
     *  
     * @return a representation of <code>matrix</code> as a JUNG <code>Graph</code>
     */
    public static <V, E> Graph<V, E> matrixToGraph(DoubleMatrix2D matrix,
            Factory<? extends Graph<V, E>> graphFactory, Factory<V> vertexFactory, Factory<E> edgeFactory) {
        return GraphMatrixOperations.<V, E>matrixToGraph(matrix, graphFactory, vertexFactory, edgeFactory, null);
    }

    /**
     * Returns an unweighted (0-1) adjacency matrix based on the specified graph.
     * @param <V> the vertex type
     * @param <E> the edge type
     * @param g the graph to convert to a matrix
     */
    public static <V, E> SparseDoubleMatrix2D graphToSparseMatrix(Graph<V, E> g) {
        return graphToSparseMatrix(g, null);
    }

    /**
     * Returns a SparseDoubleMatrix2D whose entries represent the edge weights for the
     * edges in <code>g</code>, as specified by <code>nev</code>.  
     * 
     * <p>The <code>(i,j)</code> entry of the matrix returned will be equal to the sum
     * of the weights of the edges connecting the vertex with index <code>i</code> to 
     * <code>j</code>.
     * 
     * <p>If <code>nev</code> is <code>null</code>, then a constant edge weight of 1 is used.
     * 
     * @param g
     * @param nev
     */
    public static <V, E> SparseDoubleMatrix2D graphToSparseMatrix(Graph<V, E> g, Map<E, Number> nev) {
        if (nev == null)
            nev = new ConstantMap<E, Number>(1);
        int numVertices = g.getVertexCount();
        SparseDoubleMatrix2D matrix = new SparseDoubleMatrix2D(numVertices, numVertices);

        BidiMap<V, Integer> indexer = Indexer.<V>create(g.getVertices());
        int i = 0;

        for (V v : g.getVertices()) {
            for (E e : g.getOutEdges(v)) {
                V w = g.getOpposite(v, e);
                int j = indexer.get(w);
                matrix.set(i, j, matrix.getQuick(i, j) + nev.get(e).doubleValue());
            }
            i++;
        }
        return matrix;
    }

    /**
     * Returns a diagonal matrix whose diagonal entries contain the degree for
     * the corresponding node.
     * 
     * <p>NOTE: the vertices will be traversed in the order given by the graph's vertex 
     * collection.  If you want to be assured of a particular ordering, use a graph 
     * implementation that guarantees such an ordering (see the implementations with {@code Ordered}
     * or {@code Sorted} in their name).
     * 
     * @return SparseDoubleMatrix2D
     */
    public static <V, E> SparseDoubleMatrix2D createVertexDegreeDiagonalMatrix(Graph<V, E> graph) {
        int numVertices = graph.getVertexCount();
        SparseDoubleMatrix2D matrix = new SparseDoubleMatrix2D(numVertices, numVertices);
        int i = 0;
        for (V v : graph.getVertices()) {
            matrix.set(i, i, graph.degree(v));
            i++;
        }
        return matrix;
    }

    /**
     * The idea here is based on the metaphor of an electric circuit. We assume
     * that an undirected graph represents the structure of an electrical
     * circuit where each edge has unit resistance. One unit of current is
     * injected into any arbitrary vertex s and one unit of current is extracted
     * from any arbitrary vertex t. The voltage at some vertex i for source
     * vertex s and target vertex t can then be measured according to the
     * equation: V_i^(s,t) = T_is - T-it where T is the voltage potential matrix
     * returned by this method. *
     * 
     * @param graph
     *            an undirected graph representing an electrical circuit
     * @return the voltage potential matrix
     * @see "P. Doyle and J. Snell, 'Random walks and electric networks,', 1989"
     * @see "M. Newman, 'A measure of betweenness centrality based on random walks', pp. 5-7, 2003"
     */
    public static <V, E> DoubleMatrix2D computeVoltagePotentialMatrix(UndirectedGraph<V, E> graph) {
        int numVertices = graph.getVertexCount();
        //create adjacency matrix from graph
        DoubleMatrix2D A = GraphMatrixOperations.graphToSparseMatrix(graph, null);
        //create diagonal matrix of vertex degrees
        DoubleMatrix2D D = GraphMatrixOperations.createVertexDegreeDiagonalMatrix(graph);
        DoubleMatrix2D temp = new SparseDoubleMatrix2D(numVertices - 1, numVertices - 1);
        //compute D - A except for last row and column
        for (int i = 0; i < numVertices - 1; i++) {
            for (int j = 0; j < numVertices - 1; j++) {
                temp.set(i, j, D.get(i, j) - A.get(i, j));
            }
        }
        Algebra algebra = new Algebra();
        DoubleMatrix2D tempInverse = algebra.inverse(temp);
        DoubleMatrix2D T = new SparseDoubleMatrix2D(numVertices, numVertices);
        //compute "voltage" matrix
        for (int i = 0; i < numVertices - 1; i++) {
            for (int j = 0; j < numVertices - 1; j++) {
                T.set(i, j, tempInverse.get(i, j));
            }
        }
        return T;
    }

    /**
     * Converts a Map of (Vertex, Double) pairs to a DoubleMatrix1D.
     * 
     * <p>Note: the vertices will appear in the output array in the order given
     * by {@code map}'s iterator.  If you want a particular ordering, use a {@code Map}
     * implementation that provides such an ordering ({@code SortedMap, LinkedHashMap}, etc.).
     */
    public static <V> DoubleMatrix1D mapTo1DMatrix(Map<V, Number> map) {
        int numVertices = map.size();
        DoubleMatrix1D vector = new DenseDoubleMatrix1D(numVertices);
        int i = 0;
        for (V v : map.keySet()) {
            vector.set(i, map.get(v).doubleValue());
            i++;
        }
        return vector;
    }

    /**
     * Computes the all-pairs mean first passage time for the specified graph,
     * given an existing stationary probability distribution.
     * <P>
     * The mean first passage time from vertex v to vertex w is defined, for a
     * Markov network (in which the vertices represent states and the edge
     * weights represent state->state transition probabilities), as the expected
     * number of steps required to travel from v to w if the steps occur
     * according to the transition probabilities.
     * <P>
     * The stationary distribution is the fraction of time, in the limit as the
     * number of state transitions approaches infinity, that a given state will
     * have been visited. Equivalently, it is the probability that a given state
     * will be the current state after an arbitrarily large number of state
     * transitions.
     * 
     * @param G
     *            the graph on which the MFPT will be calculated
     * @param edgeWeights
     *            the edge weights
     * @param stationaryDistribution
     *            the asymptotic state probabilities
     * @return the mean first passage time matrix
     */
    public static <V, E> DoubleMatrix2D computeMeanFirstPassageMatrix(Graph<V, E> G, Map<E, Number> edgeWeights,
            DoubleMatrix1D stationaryDistribution) {
        DoubleMatrix2D temp = GraphMatrixOperations.graphToSparseMatrix(G, edgeWeights);
        for (int i = 0; i < temp.rows(); i++) {
            for (int j = 0; j < temp.columns(); j++) {
                double value = -1 * temp.get(i, j) + stationaryDistribution.get(j);
                if (i == j)
                    value += 1;
                if (value != 0)
                    temp.set(i, j, value);
            }
        }
        Algebra algebra = new Algebra();
        DoubleMatrix2D fundamentalMatrix = algebra.inverse(temp);
        temp = new SparseDoubleMatrix2D(temp.rows(), temp.columns());
        for (int i = 0; i < temp.rows(); i++) {
            for (int j = 0; j < temp.columns(); j++) {
                double value = -1.0 * fundamentalMatrix.get(i, j);
                value += fundamentalMatrix.get(j, j);
                if (i == j)
                    value += 1;
                if (value != 0)
                    temp.set(i, j, value);
            }
        }
        DoubleMatrix2D stationaryMatrixDiagonal = new SparseDoubleMatrix2D(temp.rows(), temp.columns());
        int numVertices = stationaryDistribution.size();
        for (int i = 0; i < numVertices; i++)
            stationaryMatrixDiagonal.set(i, i, 1.0 / stationaryDistribution.get(i));
        DoubleMatrix2D meanFirstPassageMatrix = algebra.mult(temp, stationaryMatrixDiagonal);
        return meanFirstPassageMatrix;
    }
}