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

src.it.unimi.dsi.law.graph.LayeredLabelPropagation Maven / Gradle / Ivy

The newest version!
package it.unimi.dsi.law.graph;

/*
 * Copyright (C) 2010-2020 Paolo Boldi, Massimo Santini and Sebastiano Vigna
 *
 *  This library is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as published by the Free
 *  Software Foundation; either version 3 of the License, or (at your option)
 *  any later version.
 *
 *  This library is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, see .
 *
 */

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DecimalFormat;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;

import org.apache.commons.lang3.mutable.MutableDouble;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;
import com.martiansoftware.jsap.Parameter;
import com.martiansoftware.jsap.SimpleJSAP;
import com.martiansoftware.jsap.Switch;
import com.martiansoftware.jsap.UnflaggedOption;

import it.unimi.dsi.Util;
import it.unimi.dsi.bits.Fast;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArrays;
import it.unimi.dsi.fastutil.io.BinIO;
import it.unimi.dsi.fastutil.io.FastBufferedOutputStream;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.logging.ProgressLogger;
import it.unimi.dsi.util.XoRoShiRo128PlusRandom;
import it.unimi.dsi.webgraph.ImmutableGraph;
import it.unimi.dsi.webgraph.LazyIntIterator;
import it.unimi.dsi.webgraph.NodeIterator;
import it.unimi.dsi.webgraph.Transform;
import it.unimi.dsi.webgraph.algo.EliasFanoCumulativeOutdegreeList;


// RELEASE-STATUS: DIST

/** An implementation of the layered label propagation algorithm described by
 * by Paolo Boldi, Sebastiano Vigna, Marco Rosa, Massimo Santini, and Sebastiano Vigna in “Layered label propagation:
 * A multiresolution coordinate-free ordering for compressing social networks”,
 * Proceedings of the 20th international conference on World Wide Web, pages 587−596, ACM, 2011.
 *
 * 

The method {@link #computePermutation(double[], String, int)} returns a permutation of the original * symmetric graph provided with the {@linkplain #LayeredLabelPropagation(ImmutableGraph, int[], long, boolean) constructor} * which will (hopefully) increase locality (see the paper). Usually, the permutation is fed to * {@link Transform#mapOffline(ImmutableGraph, int[], int, File, ProgressLogger)} to permute the original graph. * *

Note that the graph provided must be symmetric and loopless. If this is not the case, * please use {@link Transform#symmetrizeOffline(ImmutableGraph, int, File, ProgressLogger)} and possibly * {@link Transform#filterArcs(ImmutableGraph, it.unimi.dsi.webgraph.Transform.ArcFilter, ProgressLogger)} with * filter {@link Transform#NO_LOOPS} to generate a suitable graph. * *

This class can also be used to run just label propagation over a given graph to * get the {@linkplain #computeLabels(double, int) labels assigned to the nodes} for a fixed γ. * *

Memory requirements

* *

This class requires 13 bytes per node (three integers and a boolean), plus the memory * that is necessary to load the graph, which however can be just * {@link ImmutableGraph#loadMapped(CharSequence, ProgressLogger) memory-mapped}. * *

Note that the main method will warm up the algorithm by performing a {@linkplain DFS depth-first visit} * if the graph is not mapped. The visit will require storing an additional array of integers. * * @author Paolo Boldi * @author Marco Rosa * @author Massimo Santini * @author Sebastiano Vigna */ public class LayeredLabelPropagation { private final static Logger LOGGER = LoggerFactory.getLogger(LayeredLabelPropagation.class); /** The list of default γ values. It must be kept in sync with the {@link #main(String[])} default parameters. */ public static final double[] DEFAULT_GAMMAS = { 1., 1./2, 1./4, 1./8, 1./16, 1./32, 1./64, 1./128, 1./256, 1./512, 1./1024, 0 }; /** The format used to print γ's. */ private static final DecimalFormat GAMMA_FORMAT = new java.text.DecimalFormat("0.############"); /** The default maximum number of updates. */ public static final int MAX_UPDATES = 100; /** The minimum gain in the Hamiltonian. Under this threshold we stop. */ private final static double GAIN_TRESHOLD = 0.001; /** The update list will be shuffled by blocks of this size, to ensure some locality. */ private static final int SHUFFLE_GRANULARITY = 100000; /** A symmetric, loopless graph. */ private final ImmutableGraph symGraph; /** The number of nodes of {@link #symGraph}. */ private final int n; /** The label of each node. After a call to {@link #computePermutation(int, double[], String)} * this field contains the final list of labels. */ private AtomicIntegerArray label; /** Volume of each current cluster, indexed by label (many will be zeroes). */ private final AtomicIntegerArray volume; /** The chosen update order. */ private final int[] updateList; /** The objective function (Hamiltonian of the potts model). */ private final double[] objectiveFunction; /** The objective function (Hamiltonian of the potts model). */ private final MutableDouble gapCost; /** The random-number generator. */ private final XoRoShiRo128PlusRandom r; /** The basename of temporary files containing labellings for various γ's. */ private File labelling; /** If true, the user has set a basename for label files, and such files must not be deleted. */ private boolean labelBasenameSet; /** A virtual permutation applied to the graph, or {@code null} for no permutation. */ private final int[] startPerm; /** Whether to perform an exactly reproducible run in case {@link #startPerm} is not {@code null} (slower). */ private final boolean exact; /** The number of threads used in the computation. */ private final int numberOfThreads; /** The random seed. */ private final long seed; /** For each note, true iff at least one of the successors changed its label. */ private final boolean[] canChange; /** The number of nodes that changed their label in the current iteration. */ private final AtomicInteger modified; /** A simple exception handler that stores the thrown exception in {@link #threadException}. */ private final SimpleUncaughtExceptionHandler simpleUncaughtExceptionHandler; /** One of the throwables thrown by some of the threads, if at least one thread has thrown a throwable. */ private volatile Throwable threadException; /** The current update. */ private int update; /** The starting node of the next chunk of nodes to be processed. */ protected int nextNode; /** The number of arcs before {@link #nextNode}. */ protected long nextArcs; /** The outdegrees cumulative function. */ protected final EliasFanoCumulativeOutdegreeList cumulativeOutdegrees; /** Creates a new instance. * * @param symGraph a symmetric, loopless graph. * @param seed a random seed. */ public LayeredLabelPropagation(final ImmutableGraph symGraph, final long seed) throws IOException { this(symGraph, null, seed, false); } /** Creates a new instance using a specific initial permutation. * * @param symGraph a symmetric, loopless graph. * @param startPerm an initial permutation of the graph, or {@code null} for no permutation. * @param seed a random seed. */ public LayeredLabelPropagation(final ImmutableGraph symGraph, final int[] startPerm, final long seed) throws IOException { this(symGraph, startPerm, seed, false); } /** Creates a new instance using a specific initial permutation. * *

If exact is true, the final permutation is * exactly the same as if you first permute the graph with startPerm and * then apply LLP with an {@code null} starting permutation. * * @param symGraph a symmetric, loopless graph. * @param startPerm an initial permutation of the graph, or {@code null} for no permutation. * @param seed a random seed. * @param exact a boolean flag that forces the algorithm to run exactly. */ public LayeredLabelPropagation(final ImmutableGraph symGraph, final int[] startPerm, final long seed, final boolean exact) throws IOException { this(symGraph, startPerm, 0, seed, exact); } /** Creates a new instance using a specific initial permutation and specified number of threads. * *

If exact is true, the final permutation is * exactly the same as if you first permute the graph with startPerm and * then apply LLP with an {@code null} starting permutation. * * @param symGraph a symmetric, loopless graph. * @param startPerm an initial permutation of the graph, or {@code null} for no permutation. * @param numberOfThreads the number of threads to be used (0 for automatic sizing). * @param seed a random seed. * @param exact a boolean flag that forces the algorithm to run exactly. */ public LayeredLabelPropagation(final ImmutableGraph symGraph, final int[] startPerm, final int numberOfThreads, final long seed, final boolean exact) throws IOException { this.symGraph = symGraph; this.n = symGraph.numNodes(); this.startPerm = startPerm; this.seed = seed; this.r = new XoRoShiRo128PlusRandom(seed); this.exact = exact; this.label = new AtomicIntegerArray(n); this.volume = new AtomicIntegerArray(n); cumulativeOutdegrees = new EliasFanoCumulativeOutdegreeList(symGraph, symGraph.numArcs(), 1); this.gapCost = new MutableDouble(); this.updateList = Util.identity(n); simpleUncaughtExceptionHandler = new SimpleUncaughtExceptionHandler(); labelling = File.createTempFile(this.getClass().getName(), "labelling"); labelling.deleteOnExit(); this.numberOfThreads = numberOfThreads != 0 ? numberOfThreads : Runtime.getRuntime().availableProcessors(); this.canChange = new boolean[n]; this.modified = new AtomicInteger(0); this.objectiveFunction = new double[this.numberOfThreads]; } /** * Sets the basename for label files. * * @param labelBasename basename for label files. */ public void labelBasename(final String labelBasename) { labelBasenameSet = true; labelling = new File(labelBasename); } /** * Combines two labellings devilishly into a new one. * * @param label the minor label; the result will be stored here. * @param major the major label. * @param perm a virtual permutation applied to the graph, or {@code null} for no permutation. * @param support a support array. * @return the resulting number of labels. */ private static int combine(final int[] label, final int[] major, final int[] perm, final int[] support) { final int n = label.length; if (n == 0) return 0; if (n != major.length) throw new IllegalArgumentException(); Util.identity(support); if (perm == null) IntArrays.mergeSort(support, 0, n, (a, b) -> { int t = label[major[a]] - label[major[b]]; if (t != 0) return t; t = major[a] - major[b]; return t != 0 ? t : label[a] - label[b]; }); else IntArrays.mergeSort(support, 0, n, (a, b) -> { int t = label[major[a]] - label[major[b]]; if (t != 0) return t; t = perm[major[a]] - perm[major[b]]; return t != 0 ? t : label[a] - label[b]; }); int currMinor = label[support[0]]; int currMajor = major[support[0]]; int curr = 0; label[support[0]] = curr; for (int i = 1; i < n; i++) { final int t = support[i]; final int u = label[t]; if (major[t] != currMajor || u != currMinor) { currMinor = u; currMajor = major[t]; curr++; } label[t] = curr; } return ++curr; } /** A minimal implementation of a set of counters using a hash table without rehashing. */ private final static class OpenHashTableCounter { /** The keys. Always sized as a power of two. */ private int[] key; /** The counters associated to {@link #key}. */ private int[] count; /** Keeps track of the location of each key. Useful for linear-time iteration over the key/value pairs. */ private int[] location; /** The mask used to compute the key locations. */ private int mask; /** The number of keys in the table. */ private int n; public OpenHashTableCounter() { mask = -1; count = IntArrays.EMPTY_ARRAY; key = IntArrays.EMPTY_ARRAY; location = IntArrays.EMPTY_ARRAY; } public void incr(final int k) { int pos = (k * 2056437379) & mask; while (count[pos] != 0 && key[pos] != k) pos = (pos + 1) & mask; if (count[pos]++ == 0) { key[pos] = k; location[n++] = pos; } } public boolean containsKey(final int k) { int pos = (k * 2056437379) & mask; while (count[pos] != 0 && key[pos] != k) pos = (pos + 1) & mask; return count[pos] != 0; } // After a call to this method, incr() cannot be called anymore. public void addZeroCount(final int k) { int pos = (k * 2056437379) & mask; while (count[pos] != 0 && key[pos] != k) pos = (pos + 1) & mask; if (count[pos] == 0) { key[pos] = k; location[n++] = pos; } } private final static class Entry extends AbstractInt2IntMap.BasicEntry { public Entry() { super(0, 0); } public void setKey(final int key) { this.key = key; } @Override public int setValue(final int value) { this.value = value; return -1; // Violates the interface, but it's all internal. } } public Iterator entries() { return new ObjectIterator() { private int i; private final Entry entry = new Entry(); @Override public boolean hasNext() { return i < n; } @Override public Entry next() { if (!hasNext()) throw new NoSuchElementException(); final int l = location[i++]; entry.setKey(key[l]); entry.setValue(count[l]); return entry; } }; } public void clear(final int size) { if (mask + 1 < (1 << (Fast.ceilLog2(size) + 1))) { mask = (1 << (Fast.ceilLog2(size) + 1)) - 1; count = new int[mask + 1]; key = new int[mask + 1]; location = new int[mask + 1]; } else while (n-- != 0) count[location[n]] = 0; n = 0; } } private final class GapCostThread extends Thread { @SuppressWarnings("hiding") private final ImmutableGraph symGraph; /** The permutation whose cost is to be evaluated. */ private final int[] perm; private GapCostThread(final ImmutableGraph symGraph, final int[] perm) { this.symGraph = symGraph; this.perm = perm; } @Override public void run() { final ImmutableGraph symGraph = this.symGraph; final int numNodes = LayeredLabelPropagation.this.n; final long numArcs = LayeredLabelPropagation.this.symGraph.numArcs(); final int[] perm = this.perm; int[] permutedSuccessors = new int[32]; int[] successors; final long granularity = Math.max(1024, numArcs >>> 9); int start, end; double gapCost = 0; for (;;) { // Try to get another piece of work. synchronized(LayeredLabelPropagation.this.cumulativeOutdegrees) { if (nextNode == numNodes) { LayeredLabelPropagation.this.gapCost.add(gapCost); break; } start = nextNode; final long target = nextArcs + granularity; if (target >= numArcs) nextNode = numNodes; else { nextArcs = cumulativeOutdegrees.skipTo(target); nextNode = cumulativeOutdegrees.currentIndex(); } end = nextNode; } final NodeIterator nodeIterator = symGraph.nodeIterator(start); for (int i = start; i < end; i++) { nodeIterator.nextInt(); final int node = perm[i]; final int outdegree = nodeIterator.outdegree(); if (outdegree > 0) { successors = nodeIterator.successorArray(); permutedSuccessors = IntArrays.grow(permutedSuccessors, outdegree); for (int j = outdegree; j-- != 0;) permutedSuccessors[j] = perm[successors[j]]; IntArrays.quickSort(permutedSuccessors, 0, outdegree); int prev = node; for (int j = 0; j < outdegree; j++) { gapCost += Fast.ceilLog2(Math.abs(prev - permutedSuccessors[j])); prev = permutedSuccessors[j]; } } } } } } private final class IterationThread extends Thread { @SuppressWarnings("hiding") private final ImmutableGraph symGraph; /** The current value of γ. */ private final double gamma; /** A progress logger. */ private final ProgressLogger pl; private final int index; private IterationThread(final ImmutableGraph symGraph, final double gamma, final int index, final ProgressLogger pl) { this.symGraph = symGraph; this.gamma = gamma; this.index = index; this.pl = pl; } @Override public void run() { final XoRoShiRo128PlusRandom r = new XoRoShiRo128PlusRandom(LayeredLabelPropagation.this.seed); final AtomicIntegerArray label = LayeredLabelPropagation.this.label; final AtomicIntegerArray volume = LayeredLabelPropagation.this.volume; final ImmutableGraph symGraph = this.symGraph; final int numNodes = LayeredLabelPropagation.this.n; final long numArcs = LayeredLabelPropagation.this.symGraph.numArcs(); final int[] updateList = LayeredLabelPropagation.this.updateList; final int[] startPerm = LayeredLabelPropagation.this.startPerm; final boolean[] canChange = LayeredLabelPropagation.this.canChange; final boolean exact = LayeredLabelPropagation.this.exact; final double gamma = this.gamma; final long granularity = Math.max(1024, numArcs >>> 9); int start, end; double delta = LayeredLabelPropagation.this.objectiveFunction[index]; for (;;) { // Try to get another piece of work. synchronized(LayeredLabelPropagation.this.cumulativeOutdegrees) { if (nextNode == numNodes) { LayeredLabelPropagation.this.objectiveFunction[index] = delta; break; } start = nextNode; final long target = nextArcs + granularity; if (target >= numArcs) nextNode = numNodes; else { nextArcs = cumulativeOutdegrees.skipTo(target); nextNode = cumulativeOutdegrees.currentIndex(); } end = nextNode; } final OpenHashTableCounter map = new OpenHashTableCounter(); for (int i = start; i < end; i++) { final int node = updateList[i]; /** Note that here we are using a heuristic optimisation: if no neighbour has changed, * the label of a node cannot change. If gamma != 0, this is not necessarily true, * as a node might need to change its value just because of a change of volume of * the adjacent labels. */ if (canChange[node]) { canChange[node] = false; final int outdegree = symGraph.outdegree(node); if (outdegree > 0) { final int currentLabel = label.get(node); volume.decrementAndGet(currentLabel); map.clear(outdegree); LazyIntIterator successors = symGraph.successors(node); for (int j = outdegree; j-- != 0;) map.incr(label.get(successors.nextInt())); if (!map.containsKey(currentLabel)) map.addZeroCount(currentLabel); double max = Double.NEGATIVE_INFINITY; double old = 0; final IntArrayList majorities = new IntArrayList(); for (final Iterator entries = map.entries(); entries.hasNext();) { final Int2IntMap.Entry entry = entries.next(); final int l = entry.getIntKey(); final int freq = entry.getIntValue(); // Frequency of label in my // neighbourhood final double val = freq - gamma * (volume.get(l) + 1 - freq); if (max == val) majorities.add(l); if (max < val) { majorities.clear(); max = val; majorities.add(l); } if (l == currentLabel) old = val; } if (exact) { if (startPerm != null) IntArrays.quickSort(majorities.elements(), 0, majorities.size(), (a, b) -> startPerm[a] - startPerm[b]); else IntArrays.quickSort(majorities.elements(), 0, majorities.size()); } // Extract a label from the majorities final int nextLabel = majorities.getInt(r.nextInt(majorities.size())); if (nextLabel != currentLabel) { modified.addAndGet(1); successors = symGraph.successors(node); for (int j = outdegree; j-- != 0;) canChange[successors.nextInt()] = true; } label.set(node, nextLabel); volume.incrementAndGet(nextLabel); delta += max - old; } } } synchronized (pl) { pl.update(end - start); } } } } private final class SimpleUncaughtExceptionHandler implements UncaughtExceptionHandler { @Override public void uncaughtException(final Thread t, final Throwable e) { threadException = e; } } private void update(final double gamma) { final int n = this.n; final int[] updateList = this.updateList; modified.set(0); nextArcs = nextNode = 0; if (exact) { if (startPerm == null) Util.identity(updateList); else Util.invertPermutation(startPerm, updateList); } // Local shuffle for(int i = 0; i < n;) IntArrays.shuffle(updateList, i, Math.min(i += SHUFFLE_GRANULARITY, n), r); final ProgressLogger pl = new ProgressLogger(LOGGER); pl.expectedUpdates = n; pl.logInterval = ProgressLogger.TEN_SECONDS; pl.itemsName = "nodes"; pl.start("Starting update " + update + "..."); final Thread[] thread = new Thread[numberOfThreads]; nextArcs = nextNode = 0; for (int i = 0; i < numberOfThreads; i++) { thread[i] = new IterationThread(symGraph.copy(), gamma, i, pl); thread[i].setUncaughtExceptionHandler(simpleUncaughtExceptionHandler); thread[i].start(); } for (int i = 0; i < numberOfThreads; i++) try { thread[i].join(); } catch (final InterruptedException e) { throw new RuntimeException(e); } if (threadException != null) throw new RuntimeException(threadException); pl.done(); } private void computeGapCost(final int[] newPerm) { final int[] startPerm = this.startPerm; final AtomicIntegerArray label = this.label; Util.identity(newPerm); if (startPerm != null) IntArrays.quickSort(newPerm, (x, y) -> { final int t = startPerm[label.get(x)] - startPerm[label.get(y)]; return t != 0 ? t : startPerm[x] - startPerm[y]; }); else IntArrays.quickSort(newPerm, (x, y) -> { final int t = label.get(x) - label.get(y); return t != 0 ? t : x - y; }); Util.invertPermutationInPlace(newPerm); final Thread[] thread = new Thread[numberOfThreads]; nextArcs = nextNode = 0; for (int i = 0; i < numberOfThreads; i++) (thread[i] = new GapCostThread(symGraph.copy(), newPerm)).start(); for (int i = 0; i < numberOfThreads; i++) try { thread[i].join(); } catch (final InterruptedException e) { throw new RuntimeException(e); } } private double objectiveFunction() { double res = 0; for (final double d : objectiveFunction) res += d; return res; } private void init() { for (int i = 0; i < n; i++) { label.set(i, i); volume.set(i, 1); canChange[i] = true; updateList[i] = i; } for (int i = 0; i < numberOfThreads; i++) objectiveFunction[i] = 0; } /** * Computes the labels of a graph for a given value of γ using the {@linkplain #MAX_UPDATES default maximum number of updates}. * * @param gamma the gamma parameter. * @return the labels. */ public AtomicIntegerArray computeLabels(final double gamma) { return computeLabels(gamma, MAX_UPDATES); } /** * Computes the labels of a graph for a given value of γ. * * @param gamma the gamma parameter. * @param maxUpdates the maximum number of updates performed. * @return the labels. */ public AtomicIntegerArray computeLabels(final double gamma, final int maxUpdates) { init(); final String gammaFormatted = GAMMA_FORMAT.format(gamma); double prevObjFun = 0; double gain = 0; final ProgressLogger pl = new ProgressLogger(LOGGER, "updates"); pl.logger().info("Running " + this.numberOfThreads + " threads"); pl.start("Starting iterations with gamma=" + gammaFormatted + "..."); update = 0; do { prevObjFun = objectiveFunction(); update(gamma); pl.updateAndDisplay(); gain = 1 - (prevObjFun / objectiveFunction()); LOGGER.info("Gain: " + gain); LOGGER.info("Modified: " + modified.get()); update++; } while (modified.get() > 0 && gain > GAIN_TRESHOLD && update < maxUpdates); pl.done(); return label; } /** * Computes the final permutation of the graph using the {@linkplain #MAX_UPDATES default maximum number of updates} and * the {@linkplain #DEFAULT_GAMMAS default gammas}. * * @param cluster if not {@code null}, clusters will be saved to a file with this name. * @return the final permutation of the graph. */ public int[] computePermutation(final String cluster) throws IOException { return computePermutation(DEFAULT_GAMMAS, cluster, MAX_UPDATES); } /** * Computes the final permutation of the graph using the {@linkplain #MAX_UPDATES default maximum number of updates}. * * @param gammas a set of parameters that will be used to generate labellings. * @param cluster if not {@code null}, clusters will be saved to a file with this name. * @return the final permutation of the graph. */ public int[] computePermutation(final double[] gammas, final String cluster) throws IOException { return computePermutation(gammas, cluster, MAX_UPDATES); } /** * Computes the final permutation of the graph. * * @param gammas a set of parameters that will be used to generate labellings. * @param cluster if not {@code null}, clusters will be saved to a file with this name. * @param maxUpdates the maximum number of updates performed. * @return the final permutation of the graph. */ public int[] computePermutation(final double[] gammas, final String cluster, final int maxUpdates) throws IOException { final int n = this.n; final int m = gammas.length; final double[] gapCosts = new double[m]; final ProgressLogger plGammas = new ProgressLogger(LOGGER); plGammas.itemsName = "gammas"; plGammas.expectedUpdates = m; plGammas.start(); for (int index = 0; index < m; index++) { init(); final double gamma = gammas[index]; final String gammaFormatted = GAMMA_FORMAT.format(gamma); double prevObjFun = 0; double gain = 0; final ProgressLogger pl = new ProgressLogger(LOGGER, "updates"); pl.logger().info("Running " + this.numberOfThreads + " threads"); pl.start("Starting iterations with gamma=" + gammaFormatted + " (" + (index + 1) + "/" + m + ") ..."); update = 0; do { prevObjFun = objectiveFunction(); update(gamma); pl.updateAndDisplay(); gain = 1 - (prevObjFun / objectiveFunction()); LOGGER.info("Gain: " + gain); LOGGER.info("Modified: " + modified.get()); update++; } while (modified.get() > 0 && gain > GAIN_TRESHOLD && update < maxUpdates); pl.done(); final int length = label.length(); final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(labelling + "-" + index))); for (int i = 0; i < length; i++) dos.writeInt(label.get(i)); dos.close(); if (!labelBasenameSet) new File(labelling + "-" + index).deleteOnExit(); gapCost.setValue(0); computeGapCost(updateList); gapCosts[index] = gapCost.doubleValue(); LOGGER.info("Completed iteration with gamma " + gammaFormatted + " (" + (index + 1) + "/" + m + ") , gap cost: " + gapCost.doubleValue()); plGammas.updateAndDisplay(); } plGammas.done(); label = null; // We no longer need the atomic list final int[] best = Util.identity(m); IntArrays.quickSort(best, 0, best.length, (x, y) -> (int)Math.signum(gapCosts[y] - gapCosts[x])); final int bestGamma = best[m - 1]; LOGGER.info("Best gamma: " + GAMMA_FORMAT.format(gammas[bestGamma]) + "\twith GapCost: " + gapCosts[bestGamma]); LOGGER.info("Worst gamma: " + GAMMA_FORMAT.format(gammas[best[0]]) + "\twith GapCost: " + gapCosts[best[0]]); final int intLabel[] = BinIO.loadInts(labelling + "-" + bestGamma); if (startPerm != null) for (int i = 0; i < n; i++) intLabel[i] = startPerm[intLabel[i]]; for (int step = 0; step < m; step++) { LOGGER.info("Starting step " + step + "..."); int[] major = BinIO.loadInts(labelling + "-" + best[step]); combine(intLabel, major, startPerm, updateList); major = BinIO.loadInts(labelling + "-" + bestGamma); final int numberOflabels = combine(intLabel, major, startPerm, updateList); LOGGER.info("Number of labels: " + numberOflabels); LOGGER.info("Finished step " + step); } final int[] newPerm = this.updateList; // It is no longer necessary: we reuse it. final int[] startPerm = this.startPerm; Util.identity(newPerm); if (startPerm == null) IntArrays.radixSortIndirect(newPerm, intLabel, true); else IntArrays.mergeSort(newPerm, (x, y) -> { final int t = intLabel[x] - intLabel[y]; return t != 0 ? t : startPerm[x] - startPerm[y]; }); if (cluster != null) { final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(cluster))); // Printing clusters; volume is really the best saved clustering BinIO.loadInts(labelling + "-" + bestGamma, intLabel); int current = intLabel[newPerm[0]]; int j = 0; for (int i = 0; i < n; i++) { final int tmp = intLabel[newPerm[i]]; if (tmp != current) { current = tmp; j++; } dos.writeInt(j); } dos.close(); } Util.invertPermutationInPlace(newPerm); return newPerm; } @SuppressWarnings("deprecation") public static void main(final String[] args) throws IOException, JSAPException { final SimpleJSAP jsap = new SimpleJSAP(LayeredLabelPropagation.class.getName(), "Runs the Layered Label Propagation algorithm on a graph.", new Parameter[] { new FlaggedOption("gammas", JSAP.STRING_PARSER, "-0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,0-0", JSAP.NOT_REQUIRED, 'g', "gammas", "The set of values of gamma, expressed as a comma-separated list of dyadics k/2^j specified as [k]-j (if missing, k=1)."), new FlaggedOption("threads", JSAP.INTSIZE_PARSER, "0", JSAP.NOT_REQUIRED, 'T', "threads", "The number of threads to be used. If 0, the number will be estimated automatically."), new FlaggedOption("maxUpdates", JSAP.INTEGER_PARSER, Integer.toString(MAX_UPDATES), JSAP.NOT_REQUIRED, 'u', "max-updates", "Maximum number of updates."), new FlaggedOption("cluster", JSAP.STRING_PARSER, null, JSAP.NOT_REQUIRED, 'c', "clusters", "Store clusters id in the given file."), new Switch("random", 'r', "random", "The graph will be virtually permuted in a random fashion."), new FlaggedOption("randomSeed", JSAP.LONG_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, 's', "random-seed", "The random seed."), new FlaggedOption("labelBasename", JSAP.STRING_PARSER, JSAP.NO_DEFAULT, JSAP.NOT_REQUIRED, 'l', "label-basename", "A basename for label files."), new Switch("mapped", 'm', "mapped", "The graph will be mapped into memory, rather than loaded. Moreover, the initial warm-up visit will be skipped."), new UnflaggedOption("symGraph", JSAP.STRING_PARSER, JSAP.REQUIRED, "The basename of a symmetric, loopless version of the graph."), new Switch("longs", 'L', "longs", "The permutation will be saved as a list of longs."), new UnflaggedOption("perm", JSAP.STRING_PARSER, JSAP.REQUIRED, "The output permutation."), }); final JSAPResult jsapResult = jsap.parse(args); if (jsap.messagePrinted()) System.exit(1); final boolean mapped = jsapResult.getBoolean("mapped"); final boolean random = jsapResult.getBoolean("random"); final int threads = jsapResult.getInt("threads"); final ImmutableGraph symGraph = mapped ? ImmutableGraph.loadMapped(jsapResult.getString("symGraph")) : ImmutableGraph.load(jsapResult.getString("symGraph")); final int n = symGraph.numNodes(); int[] startPerm = mapped && ! random ? null : Util.identity(n); final XoRoShiRo128PlusRandom r = jsapResult.userSpecified("randomSeed") ? new XoRoShiRo128PlusRandom(jsapResult.getLong("randomSeed")) : new XoRoShiRo128PlusRandom(); if (random) IntArrays.shuffle(startPerm, r); if (startPerm != null && ! mapped) startPerm = Util.invertPermutationInPlace(DFS.dfsperm(symGraph, startPerm)); final LayeredLabelPropagation clustering = new LayeredLabelPropagation(symGraph, startPerm, threads, jsapResult.userSpecified("randomSeed") ? jsapResult.getLong("randomSeed") : r.nextLong(), false); if (jsapResult.userSpecified("labelBasename")) clustering.labelBasename(jsapResult.getString("labelBasename")); final DoubleArrayList gammas = new DoubleArrayList(); for (final String gamma : jsapResult.getString("gammas").split(",")) { final String[] p = gamma.split("-"); gammas.add((p[0].length() != 0 ? Integer.parseInt(p[0]) : 1) * Math.pow(1. / 2, Integer.parseInt(p[1]))); } Collections.sort(gammas); final int[] permutation = clustering.computePermutation(gammas.toDoubleArray(), jsapResult.getString("cluster"), jsapResult.getInt("maxUpdates")); if (jsapResult.userSpecified("longs")) { final int length = permutation.length; final DataOutputStream dos = new DataOutputStream(new FastBufferedOutputStream(new FileOutputStream(jsapResult.getString("perm")))); for(int i = 0; i < length; i++) dos.writeLong(permutation[i]); dos.close(); } else BinIO.storeInts(permutation, jsapResult.getString("perm")); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy