package i5.las2peer.services.ocd.algorithms;
import i5.las2peer.services.ocd.algorithms.utils.OcdAlgorithmException;
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.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.la4j.matrix.Matrix;
import org.la4j.matrix.sparse.CCSMatrix;
import org.la4j.vector.Vector;
import org.la4j.vector.dense.BasicVector;
import y.base.Node;
import y.base.NodeCursor;
/**
* Implements SignedDMID algorithm. Handles directed and signed graphs.
*
* @author YLi
*/
public class SignedDMIDAlgorithm implements OcdAlgorithm {
/**
* The number of iterations of network coordination game. Default value is
* 4. Must be in (1,POSITIVE_INFINITY)
*/
private double iterationStep = 4;
/**
* The weight between disassortativity and effective degree value is 0.5.
* Must be in (0, 1).
*/
private double disassortativityEffectiveDegreeWeight = 0.5;
/*
* PARAMETER NAMES
*/
protected static final String ITERATION_STEP_NAME = "iterationStep";
protected static final String DISASSORTATIVITY_EFFECTIVE_DEGREE_WEIGHT_NAME = "disassortativityEffectiveDegreeWeight";
/**
* Creates a standard instance of the algorithm. All attributes are assigned
* their default values.
*/
public SignedDMIDAlgorithm() {
}
@Override
public CoverCreationType getAlgorithmType() {
return CoverCreationType.SIGNED_DMID_ALGORITHM;
}
@Override
public Map<String, String> getParameters() {
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(ITERATION_STEP_NAME, Double.toString(iterationStep));
parameters.put(DISASSORTATIVITY_EFFECTIVE_DEGREE_WEIGHT_NAME,
Double.toString(disassortativityEffectiveDegreeWeight));
return parameters;
}
@Override
public void setParameters(Map<String, String> parameters) throws IllegalArgumentException {
if (parameters.containsKey(ITERATION_STEP_NAME)) {
iterationStep = Double.parseDouble(parameters.get(ITERATION_STEP_NAME));
if (iterationStep < 2) {
throw new IllegalArgumentException();
}
parameters.remove(ITERATION_STEP_NAME);
}
if (parameters.containsKey(DISASSORTATIVITY_EFFECTIVE_DEGREE_WEIGHT_NAME)) {
disassortativityEffectiveDegreeWeight = Double
.parseDouble(parameters.get(DISASSORTATIVITY_EFFECTIVE_DEGREE_WEIGHT_NAME));
parameters.remove(DISASSORTATIVITY_EFFECTIVE_DEGREE_WEIGHT_NAME);
if (disassortativityEffectiveDegreeWeight < 0 || disassortativityEffectiveDegreeWeight > 1) {
throw new IllegalArgumentException();
}
}
if (parameters.size() > 0) {
throw new IllegalArgumentException();
}
}
@Override
public Set<GraphType> compatibleGraphTypes() {
Set<GraphType> compatibilities = new HashSet<GraphType>();
compatibilities.add(GraphType.NEGATIVE_WEIGHTS);
compatibilities.add(GraphType.DIRECTED);
return compatibilities;
}
@Override
public Cover detectOverlappingCommunities(CustomGraph graph) throws OcdAlgorithmException, InterruptedException {
Vector leadershipVector = getLeadershipVector(graph);
List<Node> leaders = leaderFindingPhase(graph, leadershipVector);
return labelPropagationPhase(graph, leaders, leadershipVector);
}
/*
* Returns the list of global leaders.
*
* @param graph The graph whose leaders will be detected.
*
* @return A list containing all nodes which are global leaders.
*/
protected List<Node> leaderFindingPhase(CustomGraph graph, Vector leadershipVector)
throws OcdAlgorithmException, InterruptedException {
Map<Node, Integer> localLeader = getLocalLeader(graph, leadershipVector);
return getGlobalLeader(localLeader);
}
/*
* Returns a list of global leaders.
*
* @param localLeader The mapping from nodes to their follower degrees.
*
* @return A list containing all nodes which are considered to be global
* leaders.
*/
protected List<Node> getGlobalLeader(Map<Node, Integer> localLeader) throws InterruptedException {
double averageFollowerDegree = 0;
for (int followerDegree : localLeader.values()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
averageFollowerDegree += followerDegree;
}
averageFollowerDegree /= localLeader.size();
List<Node> globalLeaders = new ArrayList<Node>();
for (Map.Entry<Node, Integer> entry : localLeader.entrySet()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
//If only two local leaders are found, they become both global leaders
if (localLeader.size() < 3) {
globalLeaders.add(entry.getKey());
} else {
if (entry.getValue() >= averageFollowerDegree) {
globalLeaders.add(entry.getKey());
}
}
}
return globalLeaders;
}
/*
* Returns the mapping from the local leaders to the corresponding follower degrees.
*
* @param graph The graph whose local leaders will be detected.
*
* @param leadershipVector The leadership vector.
*
* @return A mapping from the local leaders to the corresponding follower degrees.
*
*/
protected Map<Node, Integer> getLocalLeader(CustomGraph graph, Vector leadershipVector)
throws InterruptedException {
Map<Node, Integer> followerMap = new HashMap<Node, Integer>();
NodeCursor nodes = graph.nodes();
Node node;
/*
* Iterates over all nodes to detect their local leader
*/
while (nodes.ok()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
node = nodes.node();
double leadershipValue = leadershipVector.get(node.index());
boolean beingLeader = true;
int followerDegree = 0;
Set<Node> neighbours = graph.getNeighbours(node);
for (Node neighbour : neighbours) {
if (leadershipValue < leadershipVector.get(neighbour.index())) {
beingLeader = false;
break;
}
/*
* If leadershipvalue bigger than all of its
* neighbours->calculate follower degree As the node can
* positively point to its neighbour and be positively pointed
* to by its neighbour, the neighour should not be counted twice
*/
boolean ignoreReverse = false;
if (node.getEdgeFrom(neighbour) != null) {
if (graph.getEdgeWeight(node.getEdgeFrom(neighbour)) > 0) {
followerDegree++;
ignoreReverse = true;
}
}
if (ignoreReverse == false) {
if (node.getEdgeTo(neighbour) != null) {
if (graph.getEdgeWeight(node.getEdgeTo(neighbour)) > 0) {
followerDegree++;
ignoreReverse = true;
}
}
}
}
if (beingLeader == true) {
followerMap.put(node, followerDegree);
}
nodes.next();
}
return followerMap;
}
/*
* Returns the leadership value vector.
*
* @param graph The graph under observation.
*
* @return The leadership value vector.
*/
protected Vector getLeadershipVector(CustomGraph graph) throws InterruptedException {
int nodeCount = graph.nodeCount();
Matrix nodeEDandDASS = new CCSMatrix(nodeCount, 3);
Vector leadershipVector = new BasicVector(nodeCount);
NodeCursor nodes = graph.nodes();
Node node;
double effectiveDegreeValue;
while (nodes.ok()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
node = nodes.node();
if (graph.getPositiveInDegree(node) + Math.abs(graph.getNegativeInDegree(node)) != 0) {
if (graph.getPositiveInDegree(node) - Math.abs(graph.getNegativeInDegree(node)) < 0) {
effectiveDegreeValue = 0;
} else {
effectiveDegreeValue = (double) (graph.getPositiveInDegree(node)
- Math.abs(graph.getNegativeInDegree(node)))
/ (double) (graph.getPositiveInDegree(node) + Math.abs(graph.getNegativeInDegree(node)));
}
} else {
effectiveDegreeValue = 0;
}
nodeEDandDASS.set(node.index(), 0, effectiveDegreeValue);
// Disassortativity, put denominator and nominator in different matrix columns.
Set<Node> neighbours = graph.getNeighbours(node);
double nodeDegree = graph.getAbsoluteNodeDegree(node);
double neighbourDegree = 0;
for (Node neighbour : neighbours) {
neighbourDegree = graph.getAbsoluteNodeDegree(neighbour);
nodeEDandDASS.set(node.index(), 1, nodeEDandDASS.get(node.index(), 1) + nodeDegree + neighbourDegree);
nodeEDandDASS.set(node.index(), 2, nodeEDandDASS.get(node.index(), 2) + nodeDegree - neighbourDegree);
}
nodes.next();
}
for (int i = 0; i < nodeCount; i++) {
leadershipVector.set(i,
(1 - disassortativityEffectiveDegreeWeight) * nodeEDandDASS.get(i, 0)
+ disassortativityEffectiveDegreeWeight * (double) nodeEDandDASS.get(i, 2)
/ (double) nodeEDandDASS.get(i, 1));
}
return leadershipVector;
}
/*
* Executes the label propagation phase.
*
* @param graph The graph which is being analyzed.
*
* @param leaders The list of detected global leader nodes.
*
* @param threshold for adopting a new behavior,which equals to LLD in this
* algorithm.
*
* @return A cover containing the detected communities.
*
*/
protected Cover labelPropagationPhase(CustomGraph graph, List<Node> leaders, Vector ProfitabilityThreshold)
throws InterruptedException {
/*
* Executes the label propagation until all nodes are assigned to at
* least one community
*/
Map<Node, Map<Node, Integer>> communities = new HashMap<Node, Map<Node, Integer>>();
Map<Node, Integer> communityMemberships;
for (Node leader : leaders) {
communityMemberships = executeLabelPropagation(graph, leader, ProfitabilityThreshold);
communityMemberships.remove(leader);
communities.put(leader, communityMemberships);
}
return getMembershipDegrees(graph, communities);
}
/*
* Executes the label propagation for a single leader to identify its
* community members.
*
* @param graph The graph which is being analyzed.
*
* @param leader The leader node whose community members will be identified.
*
* @param profitabilityThreshold The threshold value that determines whether
* it is profitable for a node to join the community of the leader / assume
* its behavior. In this algorithm, it is equal to the LLD value.
*
* @return A mapping containing the iteration count for each node that is a
* community member. The iteration count indicates, in which iteration the
* corresponding node has joined the community.
*/
protected Map<Node, Integer> executeLabelPropagation(CustomGraph graph, Node leader, Vector profitabilityThreshold)
throws InterruptedException {
Map<Node, Integer> memberships = new HashMap<Node, Integer>();
Map<Node, Integer> membershipsToAdd = new HashMap<Node, Integer>();
Set<Node> NodesAssumingNewLabel = new HashSet<Node>();
Set<Node> NodesNewLabel = new HashSet<Node>();
NodesNewLabel.add(leader);
int iterationCount = 1;
memberships.put(leader, iterationCount);
while (NodesNewLabel.size() != 0 & iterationCount < iterationStep) {
NodesAssumingNewLabel.clear();
iterationCount++;
Set<Node> nodesAlreadyStudied = new HashSet<Node>();
for (Node NodeNewLabel : NodesNewLabel) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
Set<Node> neighbours = graph.getNeighbours(NodeNewLabel);
for (Node neighbour : neighbours) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (nodesAlreadyStudied.contains(neighbour) | memberships.keySet().contains(neighbour)) {
continue;
}
nodesAlreadyStudied.add(neighbour);
int posNodesWithNewLabel = getPosNodesWithNewLabel(graph, neighbour, memberships.keySet());
int negNodesWithNewLabel = getNegNodesWithNewLabel(graph, neighbour, memberships.keySet());
double profitability;
if ((posNodesWithNewLabel + negNodesWithNewLabel) != 0
& posNodesWithNewLabel - negNodesWithNewLabel > 0) {
profitability = (double) (posNodesWithNewLabel - negNodesWithNewLabel)
/ (double) (posNodesWithNewLabel + negNodesWithNewLabel);
} else {
profitability = 0;
}
if (profitability >= profitabilityThreshold.get(neighbour.index())) {
NodesAssumingNewLabel.add(neighbour);
membershipsToAdd.put(neighbour, iterationCount);
}
}
}
memberships.putAll(membershipsToAdd);
NodesNewLabel.clear();
NodesNewLabel.addAll(NodesAssumingNewLabel);
}
return memberships;
}
/*
* Returns the number of positive neighbours with the new label of a given
* node.
*
* @param graph The graph where the node resides.
*
* @param node The node under observation.
*
* @param nodesNewLabel Set of nodes in the graph which have already assumed the
* new label.
*
* @return The set of positive neighbours with the new label of a give node.
*
*/
protected int getPosNodesWithNewLabel(CustomGraph graph, Node node, Set<Node> nodesNewLabel)
throws InterruptedException {
Set<Node> positiveNeighboursWithNewLabel = new HashSet<Node>();
Set<Node> positiveNeighbours = graph.getPositiveNeighbours(node);
for (Node positiveNeighbour : positiveNeighbours) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (nodesNewLabel.contains(positiveNeighbour)) {
positiveNeighboursWithNewLabel.add(positiveNeighbour);
}
}
return positiveNeighboursWithNewLabel.size();
}
/*
* Returns the number of negative neighbours with the new label of a given
* node.
*
* @param graph The graph where the node resides.
*
* @param node The node under observation.
*
* @param nodesNewLabel Set of nodes in the graph which have already assumed the
* new label.
*
* @return The set of negative neighbours with the new label of a give node.
*
*/
protected int getNegNodesWithNewLabel(CustomGraph graph, Node node, Set<Node> nodesNewLabel)
throws InterruptedException {
Set<Node> negativeNeighboursWithNewLabel = new HashSet<Node>();
Set<Node> negativeNeighbours = graph.getNegativeNeighbours(node);
for (Node negativeNeighbour : negativeNeighbours) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (nodesNewLabel.contains(negativeNeighbour)) {
negativeNeighboursWithNewLabel.add(negativeNeighbour);
}
}
return negativeNeighboursWithNewLabel.size();
}
/*
* Returns a cover containing the membership degrees of all nodes.
*
* @param graph The graph which is being analyzed.
*
* @param communities A mapping from the leader nodes to the iteration count
* mapping of their community members.
*
* @return A cover containing each node's membership degree
*/
protected Cover getMembershipDegrees(CustomGraph graph, Map<Node, Map<Node, Integer>> communities)
throws InterruptedException {
Matrix membershipMatrix = new CCSMatrix(graph.nodeCount(), communities.size());
int communityIndex = 0;
double membershipDegree;
for (Node leader : communities.keySet()) {
membershipMatrix.set(leader.index(), communityIndex, 1.0);
for (Map.Entry<Node, Integer> entry : communities.get(leader).entrySet()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
membershipDegree = 1.0 / Math.pow(entry.getValue(), 2);
membershipMatrix.set(entry.getKey().index(), communityIndex, membershipDegree);
}
communityIndex++;
}
Cover cover = new Cover(graph, membershipMatrix);
return cover;
}
}