package ini.trakem2.analysis;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/** Following pseudo-code from:
* Ulrik Brandes. 2001. A faster algorithm for betweenness centrality.
* Journal of Mathematical Sociology 25(2):163-177, (2001). */
public class Centrality {
/** Like @see compute, but operates on a copy of all Vertex instances,
* and return a Collection with the same order as @param vs. */
static public final<T> ArrayList<Vertex<T>> safeCompute(final Collection<Vertex<T>> vs) {
final ArrayList<Vertex<T>> copies = Vertex.clone(vs);
compute(copies);
return copies;
}
/** Computes betweenness centrality of each vertex in the collection of vertices @param vs
* where all vertices are part of the same graph.
* Assumes the internal variables of each Vertex are reset to its initialization values,
* and that the neighbors array is proper as well.
* When done, the centrality of each Vertex is set in the homonymous Vertex field. */
static public final<T> void compute(final Collection<Vertex<T>> vs) {
if (1 == vs.size()) {
return;
}
final LinkedList<Vertex<T>> stack = new LinkedList<Vertex<T>>();
final LinkedList<Vertex<T>> queue = new LinkedList<Vertex<T>>();
for (final Vertex<T> s : vs) {
// Reset all:
for (final Vertex<T> t : vs) {
t.predecessors.clear();
t.sigma = 0;
t.d = -1;
}
// Prepare s:
s.sigma = 1;
s.d = 0;
queue.add(s);
//
while (!queue.isEmpty()) {
final Vertex<T> v = queue.removeFirst();
stack.add(v);
for (final Vertex<T> w : v.neighbors) {
// w found for the first time?
if (-1 == w.d) {
queue.add(w);
w.d = v.d + 1;
}
// shortest path to w via v?
if (w.d == v.d + 1) {
w.sigma += v.sigma; // sigma is always 1 in a tree
w.predecessors.add(v);
}
}
}
for (final Vertex<T> v : vs) {
v.delta = 0;
}
while (!stack.isEmpty()) {
final Vertex<T> w = stack.removeLast();
for (final Vertex<T> v : w.predecessors) {
v.delta += (v.sigma / (float)w.sigma) * (1 + w.delta); // sigma is always 1 in a tree
}
if (w != s) {
w.centrality += w.delta;
}
}
}
}
/** Find the chain of nodes, over branch points if necessary, that have not yet been processed. */
static private final <T> List<Vertex<T>> findChain(
final Vertex<T> origin,
final Vertex<T> parent,
final Set<Vertex<T>> processed) {
final ArrayList<Vertex<T>> chain = new ArrayList<Vertex<T>>();
chain.add(origin);
Vertex<T> o = origin;
Vertex<T> p = parent;
A: while (true) {
if (1 == o.neighbors.size()) { break; }
Vertex<T> o2 = o;
for (final Vertex<T> v : o.neighbors) {
if (v == p || processed.contains(v) || v.centrality < p.centrality) {
continue;
}
chain.add(v);
p = o;
o = v;
continue A; // there can only be one unexplored path
}
if (o2 == o) break;
}
return chain;
}
static public final<T> ArrayList<EtchingStep<T>> branchWise(final Collection<Vertex<T>> vs_, final int etching_multiplier) {
// Copy
final Set<Vertex<T>> vs = new HashSet<Vertex<T>>(Vertex.clone(vs_));
// Map of original vertex instances
final HashMap<T,Vertex<T>> m = new HashMap<T,Vertex<T>>();
for (final Vertex<T> v : vs_) {
m.put(v.data, v);
}
// A set of the remaining branch vertices
final Set<Vertex<T>> branch_vertices = new HashSet<Vertex<T>>();
for (final Vertex<T> v : vs) {
if (v.getNeighborCount() > 2) branch_vertices.add(v);
}
// Map of the count of remaining branch vertices and vertices removed at that step
final ArrayList<EtchingStep<T>> steps = new ArrayList<EtchingStep<T>>();
final HashSet<Vertex<T>> processed = new HashSet<Vertex<T>>();
// TODO the node centrality needs to be computed only once!
// TODO the centrality value is thrown out!
while (vs.size() > 0) {
// Reset all internal vars related to computing centrality
for (final Vertex<T> v : vs) {
v.reset();
}
// Recompute centrality for the now smaller graph
Centrality.compute(vs);
// Remove all vertices whose centrality falls below a certain threshold
final HashSet<Vertex<T>> removed = new HashSet<Vertex<T>>();
final int vs_size = vs.size();
for (final Iterator<Vertex<T>> it = vs.iterator(); it.hasNext(); ) {
final Vertex<T> v = it.next();
if (v.centrality < etching_multiplier * vs_size) {
it.remove();
removed.add(v);
}
}
// Determine which branches have been removed at this etching step
final Set<Collection<Vertex<T>>> etched_branches = new HashSet<Collection<Vertex<T>>>();
for (final Vertex<T> r : removed) {
for (final Vertex<T> w : r.neighbors) {
if (w.isBranching() && w.centrality >= r.centrality) {
final List<Vertex<T>> branch = findChain(m.get(r.data), m.get(w.data), processed);
processed.addAll(branch);
etched_branches.add(branch);
}
}
}
if (etched_branches.size() > 0) {
steps.add(new EtchingStep<T>(branch_vertices.size(), etched_branches));
}
// Fix neighbors of remaining vertices
for (final Vertex<T> v : removed) {
for (final Iterator<Vertex<T>> it = v.neighbors.iterator(); it.hasNext(); ) {
final Vertex<T> w = it.next();
if (removed.contains(w)) {
it.remove();
continue;
}
// to the vertex that remains, remove the vertex v from its neighbors:
w.neighbors.remove(v);
}
}
// Remove branch vertices which aren't branch vertices anymore
for (final Iterator<Vertex<T>> it = branch_vertices.iterator(); it.hasNext(); ) {
final Vertex<T> v = it.next();
if (v.neighbors.size() < 3 || removed.contains(v)) {
it.remove();
}
}
if (0 == branch_vertices.size()) {
break;
}
}
for (final EtchingStep<T> es : steps) {
for (final Collection<Vertex<T>> b : es.branches) {
List<Vertex<T>> b2 = new ArrayList<Vertex<T>>(b);
b.clear();
for (final Vertex<T> v : b2) {
b.add(m.get(v.data));
}
}
}
// The last, central branch
Set<T> done = new HashSet<T>();
for (final Vertex<T> v : processed) {
done.add(v.data);
}
HashMap<T,Vertex<T>> last = new HashMap<T,Vertex<T>>(m);
last.keySet().removeAll(done);
HashSet<Collection<Vertex<T>>> bs = new HashSet<Collection<Vertex<T>>>();
bs.add(last.values());
steps.add(new EtchingStep<T>(0, bs));
return steps;
}
/** An entry of the number of remaining branch vertices versus
* the set of branches (each as a {@code List<Vertex<T>>}) removed. */
static public class EtchingStep<T> implements Map.Entry<Integer,Set<Collection<Vertex<T>>>> {
public int remaining_branch_vertices = 0;
public Set<Collection<Vertex<T>>> branches = null;
public EtchingStep(final int remaining_branch_vertices, final Set<Collection<Vertex<T>>> branches) {
this.remaining_branch_vertices = remaining_branch_vertices;
this.branches = branches;
}
@Override
public Integer getKey() {
return remaining_branch_vertices;
}
@Override
public Set<Collection<Vertex<T>>> getValue() {
return branches;
}
@Override
public Set<Collection<Vertex<T>>> setValue(final Set<Collection<Vertex<T>>> value) {
throw new UnsupportedOperationException();
}
}
}