com.googlecode.blaisemath.graph.mod.generators.DegreeDistributionGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.blaisemath.graph.mod.generators.DegreeDistributionGenerator.java

Source

/*
 * DegreeDistributionGenerator.java
 * Created Aug 6, 2010
 */
package com.googlecode.blaisemath.graph.mod.generators;

/*
 * #%L
 * BlaiseGraphTheory
 * --
 * Copyright (C) 2009 - 2016 Elisha Peterson
 * --
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.Iterables;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.googlecode.blaisemath.graph.Graph;
import com.googlecode.blaisemath.graph.GraphGenerator;
import com.googlecode.blaisemath.graph.SparseGraph;
import com.googlecode.blaisemath.graph.mod.generators.DegreeDistributionGenerator.DegreeDistributionParameters;
import java.util.Random;

/**
 * Provides a random-graph model based on a degree sequence. If the
 * graph is directed, the degree sequence is for the outdegrees. If the
 * graph is undirected, the degree sequence is as usual (and must sum to an
 * even number).
 *
 * @author Elisha Peterson
 */
public final class DegreeDistributionGenerator implements GraphGenerator<DegreeDistributionParameters, Integer> {

    private static final Logger LOG = Logger.getLogger(DegreeDistributionGenerator.class.getName());

    @Override
    public String toString() {
        return "Random Graph (fixed Degree Distribution)";
    }

    @Override
    public DegreeDistributionParameters createParameters() {
        return new DegreeDistributionParameters();
    }

    @Override
    public Graph<Integer> apply(DegreeDistributionParameters parm) {
        return parm.isDirected() ? generateDirected(parm.getDegreeSequence())
                : generateUndirected(parm.getDegreeSequence());
    }

    /**
     * Generate a random (directed) graph with specified outdegrees.
     * @param deg the outdegree distribution of the graph
     * @return directed instance of this type of random graph
     */
    public static Graph<Integer> generateDirected(int[] deg) {
        int n = sum(deg);
        if (deg.length > n) {
            throw new IllegalArgumentException(
                    "Maximum degree of sequence " + Arrays.toString(deg) + " is too large!");
        }
        Set<Integer[]> edges = new TreeSet<Integer[]>(EdgeCountGenerator.PAIR_COMPARE);
        int i = 0;
        for (int iDeg = 0; iDeg < deg.length; iDeg++) {
            for (int nDegI = 0; nDegI < deg[iDeg]; nDegI++) {
                // each iteration here is a separate vertex of degree iDeg
                // need to generate this many edges at random
                int[] subset = randomSubset(n, iDeg, i);
                for (int i2 : subset) {
                    edges.add(new Integer[] { i, i2 });
                }
                i++;
            }
        }
        return SparseGraph.createFromArrayEdges(true, ExtendedGeneratorParameters.intList(n), edges);
    }

    /**
     * @param deg a specified degree sequence
     * @return undirected instance of this type of random graph; not guaranteed
     * to have the actual degree sum
     * @throws IllegalArgumentException if the degree sequence is not good (i.e.
     * sums to an odd total degree, or the maximum degree is too large)
     */
    public static Graph<Integer> generateUndirected(int[] deg) {
        checkNotNull(deg);
        int n = sum(deg);
        if (deg.length > n) {
            throw new IllegalArgumentException(
                    "Maximum degree of sequence " + Arrays.toString(deg) + " is too large!");
        }
        int degSum = 0;
        for (int i = 0; i < deg.length; i++) {
            degSum += i * deg[i];
        }
        if (degSum % 2 != 0) {
            throw new IllegalArgumentException(
                    "Degree sequence " + Arrays.toString(deg) + " has odd total degree!");
        }

        // stores the mapping of nodes to desired degrees
        Map<Integer, Integer> vxLeft = new TreeMap<Integer, Integer>();
        int i = 0;
        for (int iDeg = 1; iDeg < deg.length; iDeg++) // ignore the degree 0 vertices
        {
            for (int nDegI = 0; nDegI < deg[iDeg]; nDegI++) {
                vxLeft.put(i++, iDeg);
            }
        }

        // stores the edges in the resulting graph
        Set<Integer[]> edges = new TreeSet<Integer[]>(EdgeCountGenerator.PAIR_COMPARE_UNDIRECTED);

        while (vxLeft.size() > 1) {
            Set<Integer> vv = vxLeft.keySet();
            Integer[] edge = new Integer[] { 0, 0 };
            while (edge[0].equals(edge[1])) {
                edge = new Integer[] { random(vv), random(vv) };
            }

            int attempts = 1;

            // attempt to find new edge at random
            while ((edges.contains(edge) || edge[0].equals(edge[1])) && attempts < 20) {
                edge = new Integer[] { random(vv), random(vv) };
                attempts++;
            }

            // if it takes too long, brute-force check to ensure edges are not there
            if (edges.contains(edge) || edge[0].equals(edge[1])) {
                Set<Integer[]> edgesLeft = new TreeSet<Integer[]>(EdgeCountGenerator.PAIR_COMPARE_UNDIRECTED);
                for (Integer i1 : vv) {
                    for (Integer i2 : vv) {
                        if (!i1.equals(i2)) {
                            Integer[] e = new Integer[] { i1, i2 };
                            if (vxLeft.containsKey(i1) && vxLeft.containsKey(i2) && !edges.contains(e)) {
                                edgesLeft.add(e);
                            }
                        }
                    }
                }
                if (edgesLeft.isEmpty()) {
                    break;
                } else {
                    edge = random(edgesLeft);
                }
            }

            if (edges.contains(edge)) {
                throw new IllegalStateException();
            } else {
                edges.add(edge);
                if (vxLeft.get(edge[0]) == 1) {
                    vxLeft.remove(edge[0]);
                } else {
                    vxLeft.put(edge[0], vxLeft.get(edge[0]) - 1);
                }
                if (vxLeft.get(edge[1]) == 1) {
                    vxLeft.remove(edge[1]);
                } else {
                    vxLeft.put(edge[1], vxLeft.get(edge[1]) - 1);
                }
            }
        }
        if (!vxLeft.isEmpty()) {
            LOG.log(Level.WARNING, "Unable to find edges for all vertices. Remaining list={0}", vxLeft);
        }
        return SparseGraph.createFromArrayEdges(false, ExtendedGeneratorParameters.intList(n), edges);
    }

    //<editor-fold defaultstate="collapsed" desc="UTILITY FUNCTIONS">

    /**
     * @return random value in given set
     */
    private static <V> V random(Set<V> set) {
        return Iterables.get(set, new Random().nextInt(set.size()));
    }

    /**
     * Generate a random subset of the integers 0,...,n-1, possibly with the
     * exclusion of a specific value.
     *
     * @param n the overall set size
     * @param k the subset size
     * @param omit an integer value to omit from the sequence; if outside the
     * range 0,...,n-1, it is ignored
     * @return random subset of integers 0,...,n-1 of given size
     * @throw IllegalArgumentException if k is not in the range 0,...,n (if omit
     * is in the sequence), or the range 0,...,n-1 (if omit is not in the
     * sequence)
     */
    private static int[] randomSubset(int n, int k, int omit) {
        checkElementIndex(k, n + 1);
        if (k == n && omit >= 0 && omit <= n - 1) {
            throw new IllegalArgumentException(
                    "Cannot construct subset of size " + k + " from " + n + " values omitting " + omit);
        }
        int[] result = new int[k];
        Set<Integer> left = new TreeSet<Integer>();
        for (int i = 0; i < n; i++) {
            left.add(i);
        }
        // will be ignored if omit is outside of range
        left.remove(omit);

        for (int i = 0; i < k; i++) {
            Integer value = random(left);
            result[i] = value;
            left.remove(value);
        }

        return result;
    }

    /**
     * @return sum of array
     */
    private static int sum(int[] arr) {
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }

    //</editor-fold>

    //<editor-fold defaultstate="collapsed" desc="PARAMETERS CLASS">

    /** Parameters associated with a particular degree distribution. */
    public static final class DegreeDistributionParameters {
        private boolean directed = false;
        private int[] degSequence;

        public boolean isDirected() {
            return directed;
        }

        public void setDirected(boolean directed) {
            this.directed = directed;
        }

        public int[] getDegreeSequence() {
            return degSequence;
        }

        public void setDegreeSequence(int[] degs) {
            this.degSequence = Arrays.copyOf(degs, degs.length);
        }
    }

    //</editor-fold>

}