package i5.las2peer.services.ocd.benchmarks;
import i5.las2peer.services.ocd.adapters.coverInput.CoverInputAdapter;
import i5.las2peer.services.ocd.adapters.coverInput.NodeCommunityListsCoverInputAdapter;
import i5.las2peer.services.ocd.adapters.graphInput.GraphInputAdapter;
import i5.las2peer.services.ocd.adapters.graphInput.UnweightedEdgeListGraphInputAdapter;
import i5.las2peer.services.ocd.graphs.Cover;
import i5.las2peer.services.ocd.graphs.CoverCreationLog;
import i5.las2peer.services.ocd.graphs.CoverCreationType;
import i5.las2peer.services.ocd.graphs.CustomGraph;
import i5.las2peer.services.ocd.graphs.GraphType;
import y.base.Edge;
import y.base.EdgeCursor;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecuteResultHandler;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.lang3.SystemUtils;
import org.la4j.matrix.Matrix;
/**
* The signed LFR benchmark model for signed and directed graphs. Makes use of
* an LFR benchmark application written by Lancichinetti.
*
* @author YLi
*
*/
public class SignedLfrBenchmark implements GroundTruthBenchmark {
/*
* Path of the directory reserved for the signed LFR benchmark application.
*/
private static final String SignedLfrDirectoryPath = "ocd/signedLfr/";
/*
* Used for synchronization purposes. Executes the benchmark graph
* calculation.
*/
private static DefaultExecutor executor = new DefaultExecutor();
/*
* Path of the file holding an application developed by Lancichinetti that
* calculates LFR benchmark graphs. For Windows.
*/
private static String windowsBenchmarkGeneratorPath = SignedLfrDirectoryPath + "SignedLfrBenchmarkWindows.exe";
/*
* Path of the file holding an application developed by Lancichinetti that
* calculates LFR benchmark graphs. For Linux.
*/
private static String linuxBenchmarkGeneratorPath = "./SignedLfrBenchmarkLinux";
/*
* Path of the file containing the calculated benchmark graph.
*/
private static final String graphPath = SignedLfrDirectoryPath + "network.dat";
/*
* Path of the file containing the ground truth cover of the benchmark
* graph.
*/
private static final String coverPath = SignedLfrDirectoryPath + "community.dat";
/**
* The node count of the benchmark graphs. The default value is 1000. Must
* be greater than 0.
*/
private int n = 1000;
/**
* The average node degree of the benchmark graphs. The default value is 20.
* Must be greater than 0.
*/
private int k = 20;
/**
* The maximum node degree of the benchmark graphs. The default value is 50.
* Must be greater or equal k.
*/
private int maxk = 50;
/**
* The topological mixing parameter which determines how many edges a node
* shares with nodes of other communities. The default value is 0.2. Must be
* in [0, 1].
*/
private double mu = 0.2;
/**
* The exponent for the probability distribution of the node degrees. The
* default value is -2.
*/
private double t1 = -2;
/**
* The exponent for the probability distribution of community sizes. The
* default value is -1.
*/
private double t2 = -1;
/**
* The minimum community size of the benchmark graphs. The default value is
* 10. Must be greater than 0.
*/
private int minc = 10;
/**
* The maximum community size of the benchmark graphs. The default value is
* 50. Must be greater or equal minc.
*/
private int maxc = 50;
/**
* The number of overlapping nodes in the benchmark graphs. The default
* value is 100. Must be greater or equal 0.
*/
private int on = 100;
/**
* The number of communities an overlapping node belongs to. The default
* value is 2. Must be greater or equal 2.
*/
private int om = 2;
/**
* The fraction of intra-edges which are negative. The default value is
* 0.05.
*/
private double neg = 0.05;
/**
* The fraction of inter-edges which are positive. The default value is
* 0.05.
*/
private double pos = 0.05;
@Override
public Map<String, String> getParameters() {
Map<String, String> parameters = new HashMap<String, String>();
parameters.put(N_NAME, Integer.toString(n));
parameters.put(K_NAME, Integer.toString(k));
parameters.put(MAXK_NAME, Integer.toString(maxk));
parameters.put(MU_NAME, Double.toString(mu));
parameters.put(T1_NAME, Double.toString(t1));
parameters.put(T2_NAME, Double.toString(t2));
parameters.put(MINC_NAME, Integer.toString(minc));
parameters.put(MAXC_NAME, Integer.toString(maxc));
parameters.put(ON_NAME, Integer.toString(on));
parameters.put(OM_NAME, Integer.toString(om));
parameters.put(NEG_NAME, Double.toString(neg));
parameters.put(POS_NAME, Double.toString(neg));
return parameters;
}
@Override
public void setParameters(Map<String, String> parameters) {
if (parameters.containsKey(N_NAME)) {
n = Integer.parseInt(parameters.get(N_NAME));
parameters.remove(N_NAME);
if (n <= 0) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(K_NAME)) {
k = Integer.parseInt(parameters.get(K_NAME));
parameters.remove(K_NAME);
if (k <= 0) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(MAXK_NAME)) {
maxk = Integer.parseInt(parameters.get(MAXK_NAME));
parameters.remove(MAXK_NAME);
if (maxk < k) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(MU_NAME)) {
mu = Double.parseDouble(parameters.get(MU_NAME));
parameters.remove(MU_NAME);
if (mu < 0 || mu > 1) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(T1_NAME)) {
t1 = Double.parseDouble(parameters.get(T1_NAME));
parameters.remove(T1_NAME);
}
if (parameters.containsKey(T2_NAME)) {
t2 = Double.parseDouble(parameters.get(T2_NAME));
parameters.remove(T2_NAME);
}
if (parameters.containsKey(MINC_NAME)) {
minc = Integer.parseInt(parameters.get(MINC_NAME));
parameters.remove(MINC_NAME);
if (minc < 1) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(MAXC_NAME)) {
maxc = Integer.parseInt(parameters.get(MAXC_NAME));
parameters.remove(MAXC_NAME);
if (maxc < minc) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(ON_NAME)) {
on = Integer.parseInt(parameters.get(ON_NAME));
parameters.remove(ON_NAME);
if (on < 0 || on > n) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(OM_NAME)) {
om = Integer.parseInt(parameters.get(OM_NAME));
parameters.remove(OM_NAME);
if (om < 2) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(POS_NAME)) {
pos = Double.parseDouble(parameters.get(POS_NAME));
parameters.remove(POS_NAME);
if (pos < 0 || pos > 1) {
throw new IllegalArgumentException();
}
}
if (parameters.containsKey(POS_NAME)) {
neg = Double.parseDouble(parameters.get(NEG_NAME));
parameters.remove(NEG_NAME);
if (neg < 0 || neg > 1) {
throw new IllegalArgumentException();
}
}
if (parameters.size() > 0) {
throw new IllegalArgumentException();
}
}
/**
* Creates a standardized instance of the benchmark model.
*/
public SignedLfrBenchmark() {
}
/*
* PARAMETER NAMES
*/
protected final String N_NAME = "n";
protected final String K_NAME = "k";
protected final String MAXK_NAME = "maxk";
protected final String MU_NAME = "mu";
protected final String T1_NAME = "t1";
protected final String T2_NAME = "t2";
protected final String MINC_NAME = "minc";
protected final String MAXC_NAME = "maxc";
protected final String ON_NAME = "on";
protected final String OM_NAME = "om";
protected final String POS_NAME = "pos";
protected final String NEG_NAME = "neg";
/**
* Creates a customized instance of the benchmark model. The parameters must
* be values which are valid for the LFR model.
*
* @param n
* Sets n.
* @param k
* Sets k.
* @param maxk
* Sets maxk.
* @param mu
* Sets mu.
* @param t1
* Sets t1.
* @param t2
* Sets t2.
* @param minc
* Sets minc.
* @param maxc
* Sets maxc.
* @param on
* Sets on.
* @param om
* Sets om.
*/
public SignedLfrBenchmark(int n, int k, int maxk, double mu, double t1, double t2, int minc, int maxc, int on,
int om, double pos, double neg) {
this.minc = minc;
this.mu = mu;
this.n = n;
this.k = k;
this.maxk = maxk;
this.on = on;
this.maxc = maxc;
this.om = om;
this.t1 = t1;
this.t2 = t2;
this.pos = pos;
this.neg = neg;
}
@Override
public Cover createGroundTruthCover() throws OcdBenchmarkException, InterruptedException {
synchronized (executor) {
try {
String executorFilename;
if (SystemUtils.IS_OS_WINDOWS) {
executorFilename = windowsBenchmarkGeneratorPath;
} else if (SystemUtils.IS_OS_LINUX) {
executorFilename = linuxBenchmarkGeneratorPath;
}
/*
* Benchmark not implemented for this operating system.
*/
else {
throw new OcdBenchmarkException();
}
CommandLine cmdLine = new CommandLine(executorFilename);
cmdLine.addArgument("-N");
cmdLine.addArgument(Integer.toString(this.n));
cmdLine.addArgument("-k");
cmdLine.addArgument(Integer.toString(this.k));
cmdLine.addArgument("-maxk");
cmdLine.addArgument(Integer.toString(this.maxk));
cmdLine.addArgument("-mu");
cmdLine.addArgument(Double.toString(this.mu));
cmdLine.addArgument("-minc");
cmdLine.addArgument(Integer.toString(this.minc));
cmdLine.addArgument("-maxc");
cmdLine.addArgument(Integer.toString(this.maxc));
cmdLine.addArgument("-on");
cmdLine.addArgument(Integer.toString(this.on));
cmdLine.addArgument("-om");
cmdLine.addArgument(Integer.toString(this.om));
cmdLine.addArgument("-t1");
cmdLine.addArgument(Double.toString(-this.t1));
cmdLine.addArgument("-t2");
cmdLine.addArgument(Double.toString(-this.t2));
File workingDirectory = new File(SignedLfrDirectoryPath);
File networkFile = new File(graphPath);
if (networkFile.exists()) {
networkFile.delete();
}
executor.setWorkingDirectory(workingDirectory);
DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler();
executor.execute(cmdLine, resultHandler);
resultHandler.waitFor();
if (resultHandler.getExitValue() != 0) {
System.out.println(resultHandler.getException());
throw new OcdBenchmarkException("LFR Process exit value: " + resultHandler.getExitValue());
}
GraphInputAdapter graphAdapter = new UnweightedEdgeListGraphInputAdapter(new FileReader(graphPath));
CustomGraph graph = graphAdapter.readGraph();
graph.addType(GraphType.DIRECTED);
graph.addType(GraphType.NEGATIVE_WEIGHTS);
CoverInputAdapter coverAdapter = new NodeCommunityListsCoverInputAdapter(new FileReader(coverPath));
Cover cover = coverAdapter.readCover(graph);
Cover signedCover = setWeightSign(cover, pos, neg);
cover.setCreationMethod(new CoverCreationLog(CoverCreationType.GROUND_TRUTH,
new HashMap<String, String>(), new HashSet<GraphType>()));
return signedCover;
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
if (Thread.interrupted()) {
throw new InterruptedException();
}
throw new OcdBenchmarkException(e);
}
}
}
/*
* Returns a cover with the same membership matrix but the signed graph.
*
* @param cover The ground truth of the LFR model.
*
* @param neg The fraction of intra-edges which are negative.
*
* @param pos The fraction of inter-edges which are positive.
*
* @return The adapted cover.
*/
protected Cover setWeightSign(Cover cover, double pos, double neg) throws InterruptedException {
CustomGraph graph = cover.getGraph();
Matrix membership = cover.getMemberships();
List<Edge> intraEdgeList = new ArrayList<Edge>();
List<Edge> interEdgeList = new ArrayList<Edge>();
List<Edge> intraNegativeList = new ArrayList<Edge>();
int communityCount = membership.columns();
EdgeCursor edges = graph.edges();
Edge edge;
/*
* negate the weight of all edges connecting two nodes in different
* communities.
*/
while (edges.ok()) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
edge = edges.edge();
boolean beingIntraEdge = false;
/*
* If two nodes are residing in at least one common community, the
* edge connecting them is regarded as an intra-edge.
*/
for (int i = 0; i < communityCount; i++) {
if (membership.get(edge.source().index(), i) * membership.get(edge.target().index(), i) != 0) {
intraEdgeList.add(edge);
beingIntraEdge = true;
break;
}
}
if (beingIntraEdge == false) {
interEdgeList.add(edge);
graph.setEdgeWeight(edge, graph.getEdgeWeight(edge) * (-1));
}
edges.next();
}
/*
* randomly negate the weight of intra-edges depending on the parameter
* neg.
*/
int intraEdgeCount = intraEdgeList.size();
int intraEdgesNegate = (int) Math.round(intraEdgeCount * neg);
for (int i = 0; i < intraEdgesNegate; i++) {
Edge positiveEdge = null;
Boolean edgeRepeat = true;
while (edgeRepeat) {
positiveEdge = intraEdgeList.get((int) (Math.random() * (intraEdgeCount)));
if (!intraNegativeList.contains(positiveEdge)) {
edgeRepeat = false;
}
}
graph.setEdgeWeight(positiveEdge, graph.getEdgeWeight(positiveEdge) * (-1));
intraNegativeList.add(positiveEdge);
}
/*
* randomly negate the weight of inter-edges depending on the parameter
* pos.
*/
int interEdgeCount = interEdgeList.size();
int interEdgesNegate = (int) Math.round(interEdgeCount * pos);
Edge negativeEdge;
for (int i = 0; i < interEdgesNegate; i++) {
negativeEdge = interEdgeList.get((int) (Math.random() * (interEdgeCount)));
graph.setEdgeWeight(negativeEdge, graph.getEdgeWeight(negativeEdge) * (-1));
}
return cover;
}
}