/*
* @(#)SimpleCommunityDetectionReport.java
*
* Copyright 2010 by University of Pittsburgh, released under GPLv3.
*
*/
package report;
import java.util.*;
import core.*;
/**
*
Reports the community structure of the simulation scenario using the
* SIMPLE distributed community detection algorithm described in Distributed
* Community Detection in Delay Tolerant Networks by Pan Hui et al. (bibtex
* record is included below for convenience). A node using
* SIMPLE keeps a record of all the nodes it has met and the cummulative contact
* duration it has had with each. Once this total contact duration for one of
* these nodes exceeds a configurable parameter, the node is added to the host's
* familiar set and local community. When two peers meet, they compair their
* familiar sets and local communities for commonalities and may decide to merge
* their local communities, which intuitively means that they've decided that
* they each tend to meet the same nodes often, suggesting they both are and
* should be part of the same local community.
*
*
* \@inproceedings{1366929,
* Address = {New York, NY, USA},
* Author = {Hui, Pan and Yoneki, Eiko and Chan, Shu Yan and Crowcroft, Jon},
* Booktitle = {MobiArch '07: Proceedings of 2nd ACM/IEEE international workshop
* on Mobility in the evolving internet architecture},
* Doi = {http://doi.acm.org/10.1145/1366919.1366929},
* Isbn = {978-1-59593-784-8},
* Location = {Kyoto, Japan},
* Pages = {1--8},
* Publisher = {ACM},
* Title = {Distributed community detection in delay tolerant networks},
* Year = {2007}
* }
*
*
* @author PJ Dillon, University of Pittsburgh
*
*/
public class SimpleCommunityDetectionReport extends Report implements
ConnectionListener
{
/** Threshold value for adding a host to the local community -setting id
* {@value}
*/
public static final String LAMBDA_SETTING = "lambda";
/** Threshold value for merging the local community with a peer -setting id
* {@value}
*/
public static final String GAMMA_SETTING = "gamma";
/** Total contact time threshold for adding a node to the familiar set
* -setting id {@value}
*/
public static final String FAMILIAR_SETTING = "familiarThreshold";
protected Map> familiars;
protected Map> localCommunities;
protected Map startTimestamps;
protected Map> connHistories;
protected double lambda;
protected double gamma;
protected double familiarThreshold;
public SimpleCommunityDetectionReport()
{
this.familiars = new HashMap>();
this.localCommunities = new HashMap>();
this.startTimestamps = new HashMap();
this.connHistories = new HashMap>();
Settings s = getSettings();
this.lambda = s.getDouble(LAMBDA_SETTING);
this.gamma = s.getDouble(GAMMA_SETTING);
this.familiarThreshold = s.getDouble(FAMILIAR_SETTING);
}
public void hostsConnected(DTNHost host1, DTNHost host2)
{
boolean addH2ToH1Community=false, addH1ToH2Community=false;
Set host1familiarSet;
Set host2familiarSet;
Set h1lc;
Set h2lc;
// get or create familiar and community sets
if(this.familiars.containsKey(host1))
host1familiarSet = this.familiars.get(host1);
else
{
host1familiarSet = new HashSet();
familiars.put(host1, host1familiarSet);
}
if(this.familiars.containsKey(host2))
host2familiarSet = this.familiars.get(host2);
else
{
host2familiarSet = new HashSet();
this.familiars.put(host2, host2familiarSet);
}
if(this.localCommunities.containsKey(host1))
h1lc = this.localCommunities.get(host1);
else
{
h1lc = new HashSet();
h1lc.add(host1);
this.localCommunities.put(host1, h1lc);
}
if(this.localCommunities.containsKey(host2))
h2lc = this.localCommunities.get(host2);
else
{
h2lc = new HashSet();
h2lc.add(host2);
this.localCommunities.put(host2, h2lc);
}
// Step 4 of alg: if not in familar set, begin recording contact duration
if(!host1familiarSet.contains(host2) || !host2familiarSet.contains(host1))
{
startTimestamps.put(new Pair(host1, host2), SimClock.getTime());
}
// Add peer to my local community if needed
if(!h1lc.contains(host2))
{
/*
* The algorithm calls for computing the size of the intersection of
* peer's familiarSet and this host's localCommunity. We divide that by
* the size of the peer's familiar set
*/
int count=0, peerFsize = host2familiarSet.size();
for(DTNHost h : host2familiarSet)
if(h1lc.contains(h))
count++;
if(addH2ToH1Community = ((double)count)/peerFsize > lambda)
{
h1lc.add(host2);
}
}
// Repeat for the other host
if(!h2lc.contains(host1))
{
int count = 0, myFsize = host1familiarSet.size();
for(DTNHost h : host1familiarSet)
if(h2lc.contains(h))
count++;
if(addH1ToH2Community = ((double)count)/myFsize > lambda)
{
h2lc.add(host1);
}
}
if(addH2ToH1Community || addH1ToH2Community)
{
// Decide if the communities have enough in common to merge them
Set commUnion = new HashSet(h1lc.size() +
h2lc.size() + 2);
commUnion.addAll(h1lc);
commUnion.addAll(h2lc);
commUnion.add(host1); // Just to make sure
commUnion.add(host2);
int count = 0;
for(DTNHost h : h1lc)
if(h2lc.contains(h))
count++;
if(addH2ToH1Community && count > this.gamma * commUnion.size())
{
h1lc.addAll(h2lc);
}
if(addH1ToH2Community && count > gamma * commUnion.size())
{
h2lc.addAll(h1lc);
}
}
}
public void hostsDisconnected(DTNHost host1, DTNHost host2)
{
double time, etime;
if(familiars.containsKey(host1) && familiars.get(host1).contains(host2) &&
familiars.containsKey(host2) && familiars.get(host2).contains(host1))
return;
Pair p = new Pair(host1, host2);
// record connection length in connection history
time = startTimestamps.get(p);
etime = SimClock.getTime();
List history;
if(!connHistories.containsKey(p))
{
history = new LinkedList();
connHistories.put(p, history);
}
else
history = connHistories.get(p);
if(etime - time > 0)
history.add(new Duration(time, etime));
Iterator i = history.iterator();
time = 0;
while(i.hasNext())
{
Duration d = i.next();
time += d.end - d.start;
}
// if the peers' total connection history crossed the threshold, add
// as familiars
if(time > this.familiarThreshold)
{
familiars.get(host1).add(host1);
familiars.get(host2).add(host1);
localCommunities.get(host1).add(host2);
localCommunities.get(host2).add(host1);
}
startTimestamps.remove(p);
}
@Override
public void done()
{
// Find only the unique communities
// (some hosts may record the same community)
List> communities = new LinkedList>();
for(Map.Entry> entry :
this.localCommunities.entrySet())
{
Set comm = entry.getValue();
boolean alreadyHaveCommunity = false;
for(Set c : communities)
{
if(c.containsAll(comm) && comm.containsAll(c))
{
alreadyHaveCommunity = true;
}
}
if(!alreadyHaveCommunity && comm.size() > 0)
communities.add(comm);
}
// print the size and content of each community
for(Set c : communities)
write("" + c.size() + ' ' + c);
super.done();
}
/**
* A helper class for the connection histories map that stores a pair of
* DTNHosts and ensures an appropriate contract of equals.
*
* @author PJ Dillon, University of Pittsburgh
*
*/
private class Pair
{
DTNHost h1;
DTNHost h2;
public Pair(DTNHost host1, DTNHost host2) {h1 = host1; h2 = host2;}
@Override
public int hashCode()
{
return h1.hashCode() + h2.hashCode();
}
@Override
public boolean equals(Object obj)
{
if(obj == null) return false;
if(obj instanceof Pair)
{
Pair p = (Pair)obj;
return (p.h1 == this.h1 && p.h2 ==this.h2) ||
(p.h1 == this.h2 && p.h2 == this.h1);
}
return false;
}
}
/**
* Helper class to store start and end timestamps for a connection.
*
* @author PJ Dillon, University of Pittsburgh
*
*/
private class Duration
{
double start;
double end;
public Duration(double s, double e) {start = s; end = e;}
}
}