package i5.las2peer.services.ocd.algorithms;
import i5.las2peer.services.ocd.algorithms.utils.SlpaListenerRuleCommand;
import i5.las2peer.services.ocd.algorithms.utils.SlpaPopularityListenerRule;
import i5.las2peer.services.ocd.algorithms.utils.SlpaSpeakerRuleCommand;
import i5.las2peer.services.ocd.algorithms.utils.SlpaUniformSpeakerRule;
import i5.las2peer.services.ocd.graphs.Cover;
import i5.las2peer.services.ocd.graphs.CoverCreationType;
import i5.las2peer.services.ocd.graphs.CustomGraph;
import i5.las2peer.services.ocd.graphs.GraphType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.la4j.matrix.Matrix;
import org.la4j.matrix.dense.Basic2DMatrix;
import org.la4j.vector.Vector;
import org.la4j.vector.sparse.CompressedVector;
import y.base.Node;
import y.base.NodeCursor;
/**
* Implements a custom extended version of the original Speaker Listener Label Propagation Algorithm
* using the Uniform Speaker Rule and the Popularity Listener Rule.
* Handles directed and unweighted graphs. For unweighted and undirected graphs,
* it behaves the same as the original algorithm.
*/
public class ExtendedSpeakerListenerLabelPropagationAlgorithm implements
OcdAlgorithm {
/**
* The size of the node memories and the number of iterations.
* The default value is 100. Must be greater than 0.
*/
private int memorySize = 100;
/**
* The lower bound for the relative label occurrence.
* Labels received by a node with a relative occurrence lower than this threshold will be ignored
* and do not have any influence on that nodes community memberships.
* The default value is 0.15. Must be at least 0 and at most 1.
* Recommended are values between 0.02 and 0.1.
*/
private double probabilityThreshold = 0.15;
/*
* The speaker rule according to which a speaker decides which label to send.
* Currently only the UniformSpeakerRule is implemented.
*/
private SlpaSpeakerRuleCommand speakerRule = new SlpaUniformSpeakerRule();
/*
* The listener rule according to which a listener decides which label to accept.
* Currently only the popularity listener rule is implemented.
*/
private SlpaListenerRuleCommand listenerRule = new SlpaPopularityListenerRule();
/*
* PARAMETER NAMES
*/
protected static final String PROBABILITY_THRESHOLD_NAME = "probabilityThreshold";
protected static final String MEMORY_SIZE_NAME = "memorySize";
/**
* Creates a standard instance of the algorithm.
* All attributes are assigned their default values.
*/
public ExtendedSpeakerListenerLabelPropagationAlgorithm() {
}
@Override
public CoverCreationType getAlgorithmType() {
return CoverCreationType.EXTENDED_SPEAKER_LISTENER_LABEL_PROPAGATION_ALGORITHM;
}
@Override
public Map<String, String> getParameters() {
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(MEMORY_SIZE_NAME, Integer.toString(memorySize));
parameters.put(PROBABILITY_THRESHOLD_NAME, Double.toString(probabilityThreshold));
return parameters;
}
@Override
public void setParameters(Map<String, String> parameters) throws IllegalArgumentException {
if(parameters.containsKey(MEMORY_SIZE_NAME)) {
memorySize = Integer.parseInt(parameters.get(MEMORY_SIZE_NAME));
if(memorySize <= 0) {
throw new IllegalArgumentException();
}
parameters.remove(MEMORY_SIZE_NAME);
}
if(parameters.containsKey(PROBABILITY_THRESHOLD_NAME)) {
probabilityThreshold = Double.parseDouble(parameters.get(PROBABILITY_THRESHOLD_NAME));
if(probabilityThreshold < 0 || probabilityThreshold > 1) {
throw new IllegalArgumentException();
}
parameters.remove(PROBABILITY_THRESHOLD_NAME);
}
if(parameters.size() > 0) {
throw new IllegalArgumentException();
}
}
@Override
public Set<GraphType> compatibleGraphTypes() {
Set<GraphType> compatibilities = new HashSet<GraphType>();
compatibilities.add(GraphType.WEIGHTED);
compatibilities.add(GraphType.DIRECTED);
return compatibilities;
}
@Override
public Cover detectOverlappingCommunities(
CustomGraph graph) throws InterruptedException {
/*
* Initializes node memories and node order
*/
List<List<Integer>> memories = new ArrayList<List<Integer>>();
List<Node> nodeOrder = new ArrayList<Node>();
initializeCommunityDetection(graph, memories, nodeOrder);
/*
* Selects each node as a listener and updates its memory until
* the node memories are full.
*/
Node listener;
List<Integer> memory;
for(int t=0; t+1<memorySize; t++) {
Collections.shuffle(nodeOrder);
for(int i=0; i<graph.nodeCount(); i++) {
if(Thread.interrupted()) {
throw new InterruptedException();
}
listener = nodeOrder.get(i);
memory = memories.get(listener.index());
memory.add(getNextLabel(graph, memories, listener));
}
}
/*
* Returns the cover based on the node memories.
*/
return calculateMembershipDegrees(graph, memories);
}
protected void initializeCommunityDetection(CustomGraph graph, List<List<Integer>> memories, List<Node> nodeOrder) throws InterruptedException {
List<Integer> memory;
for(int i=0; i<graph.nodeCount(); i++) {
if(Thread.interrupted()) {
throw new InterruptedException();
}
memory = new ArrayList<Integer>();
memory.add(i);
memories.add(memory);
nodeOrder.add(graph.getNodeArray()[i]);
}
}
/*
* Returns the next label to be received by the listener according to the speaker
* and the listener rule.
*/
protected int getNextLabel(CustomGraph graph, List<List<Integer>> memories, Node listener) {
Map<Node, Integer> receivedLabels = new HashMap<Node, Integer>();
NodeCursor speakers = listener.successors();
Node speaker;
while(speakers.ok()) {
speaker = speakers.node();
receivedLabels.put(speaker, speakerRule.getLabel(graph, speaker, memories.get(speaker.index())));
speakers.next();
}
return listenerRule.getLabel(graph, listener, receivedLabels);
}
/*
* Calculates a cover with the membership degrees for all nodes based on the node memories.
*/
protected Cover calculateMembershipDegrees(CustomGraph graph, List<List<Integer>> memories) throws InterruptedException {
Matrix membershipMatrix = new Basic2DMatrix();
List<Integer> communities = new ArrayList<Integer>();
/*
* Creates a label histogram for each node based on its memory
* and adapts the membership matrix accordingly.
*/
List<Integer> memory;
int labelCount;
Map<Integer, Integer> histogram;
Vector nodeMembershipDegrees;
for(int i=0; i<memories.size(); i++) {
if(Thread.interrupted()) {
throw new InterruptedException();
}
memory = memories.get(i);
labelCount = memorySize;
histogram = getNodeHistogram(memory, labelCount);
nodeMembershipDegrees = calculateMembershipsFromHistogram(histogram, communities, labelCount);
if(nodeMembershipDegrees.length() > membershipMatrix.columns()) {
/*
* Adapts matrix size for new communities.
*/
membershipMatrix = membershipMatrix.resize(graph.nodeCount(), nodeMembershipDegrees.length());
}
membershipMatrix.setRow(i, nodeMembershipDegrees);
}
return new Cover(graph, membershipMatrix);
}
/*
* Creates a histogram of the occurrence frequency based on the labels in the node memory.
* Manipulates labelCount to track the total number of labels represented in the histogram.
*/
protected Map<Integer, Integer> getNodeHistogram(List<Integer> memory, int labelCount) {
Map<Integer, Integer> histogram = new HashMap<Integer, Integer>();
Integer maxCount = 0;
/*
* Creates the histogram.
*/
int count;
for (int label : memory) {
if(histogram.containsKey(label)) {
count = histogram.get(label).intValue();
histogram.put(label, ++count);
if(count > maxCount) {
maxCount = count;
}
}
else {
histogram.put(label, 1);
}
}
/*
* Removes labels whose occurrence frequency is below the probability threshold.
*/
Map.Entry<Integer, Integer> entry;
for(Iterator<Map.Entry<Integer, Integer>> it = histogram.entrySet().iterator(); it.hasNext(); ) {
entry = it.next();
count = entry.getValue();
if((double)count / (double)memorySize < probabilityThreshold && count < maxCount) {
it.remove();
labelCount -= count;
}
}
return histogram;
}
/*
* Returns a vector of the membership degrees of a single node, calculated from its histogram.
* Manipulates the communities list to identify communities.
*/
protected Vector calculateMembershipsFromHistogram(Map<Integer, Integer> histogram, List<Integer> communities, int labelCount) {
Vector membershipDegrees = new CompressedVector(communities.size());
int count;
for(Integer label : histogram.keySet()) {
count = histogram.get(label);
if(!communities.contains(label)){
/*
* Adapts vector size for new communities.
*/
communities.add(label);
membershipDegrees = membershipDegrees.resize(communities.size());
}
membershipDegrees.set(communities.indexOf(label), (double)count / (double)labelCount);
}
return membershipDegrees;
}
}