All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.blaisemath.graph.generate.DegreeDistributionGenerator Maven / Gradle / Ivy

package com.googlecode.blaisemath.graph.generate;

/*
 * #%L
 * BlaiseGraphTheory
 * --
 * Copyright (C) 2009 - 2019 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 com.google.common.collect.Iterables;
import com.google.common.graph.Graph;
import com.googlecode.blaisemath.graph.GraphGenerator;
import com.googlecode.blaisemath.graph.GraphUtils;
import com.googlecode.blaisemath.graph.generate.DegreeDistributionGenerator.DegreeDistributionParameters;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.IntStream;

import static com.google.common.base.Preconditions.checkElementIndex;
import static java.util.Objects.requireNonNull;

/**
 * Provides a random-graph model based on a degree sequence. If the graph is directed, the degree sequence is for the
 * out-degrees. 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 {
    
    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 apply(DegreeDistributionParameters p) {
        return p.isDirected() ? generateDirected(p.getDegreeSequence()) : generateUndirected(p.getDegreeSequence());
    }

    /**
     * Generate a random (directed) graph with specified out-degrees.
     * @param deg the outdegree distribution of the graph
     * @return directed instance of this type of random graph
     */
    public static Graph generateDirected(int[] deg) {
        int n = IntStream.of(deg).sum();
        if (deg.length > n) {
            throw new IllegalArgumentException("Maximum degree of sequence " + Arrays.toString(deg) + " is too large!");
        }
        Set edges = new TreeSet<>(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 node 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 GraphUtils.createFromArrayEdges(true, GraphGenerators.intList(0, 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 generateUndirected(int[] deg) {
        requireNonNull(deg);
        int n = IntStream.of(deg).sum();
        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 vxLeft = new TreeMap<>();
        int i = 0;
        for (int iDeg = 1; iDeg < deg.length; iDeg++) // ignore the degree 0 nodes
        {
            for (int nDegI = 0; nDegI < deg[iDeg]; nDegI++) {
                vxLeft.put(i++, iDeg);
            }
        }

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

        while (vxLeft.size() > 1) {
            Set 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 edgesLeft = new TreeSet<>(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 nodes. Remaining list={0}", vxLeft);
        }
        return GraphUtils.createFromArrayEdges(false, GraphGenerators.intList(0, n), edges);
    }

    //region ALGORITHM
    
    private static  E random(Set 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
     * @throws 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 left = new TreeSet<>();
        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;
    }
    
    //endregion

    //region 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[] degrees) {
            this.degSequence = Arrays.copyOf(degrees, degrees.length);
        }
    }
    
    //endregion
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy