Author: vive
Date: 2009-02-11 13:53:49 +0000 (Wed, 11 Feb 2009)
New Revision: 25585

Added:
   trunk/apps/simsalabim/AggregateCommand.java
   trunk/apps/simsalabim/BasicDataSpace.java
   trunk/apps/simsalabim/BuildTask.java
   trunk/apps/simsalabim/CDataAggregator.java
   trunk/apps/simsalabim/CircleKey.java
   trunk/apps/simsalabim/Darknet.java
   trunk/apps/simsalabim/DarknetNode.java
   trunk/apps/simsalabim/DarknetRoute.java
   trunk/apps/simsalabim/Data.java
   trunk/apps/simsalabim/DataAggregator.java
   trunk/apps/simsalabim/DataSpace.java
   trunk/apps/simsalabim/EventLogger.java
   trunk/apps/simsalabim/Feedback.java
   trunk/apps/simsalabim/HBuildTask.java
   trunk/apps/simsalabim/HDualStats.java
   trunk/apps/simsalabim/HJoinTask.java
   trunk/apps/simsalabim/HammingKey.java
   trunk/apps/simsalabim/HistDataAggregator.java
   trunk/apps/simsalabim/InfoCommand.java
   trunk/apps/simsalabim/InfoObject.java
   trunk/apps/simsalabim/JoinTask.java
   trunk/apps/simsalabim/Key.java
   trunk/apps/simsalabim/KeyFactory.java
   trunk/apps/simsalabim/LimitedDataSpace.java
   trunk/apps/simsalabim/MBuildTask.java
   trunk/apps/simsalabim/MJoinTask.java
   trunk/apps/simsalabim/Main.java
   trunk/apps/simsalabim/MersenneTwister.java
   trunk/apps/simsalabim/Node.java
   trunk/apps/simsalabim/Person.java
   trunk/apps/simsalabim/PopularityDataSpace.java
   trunk/apps/simsalabim/PositionKey.java
   trunk/apps/simsalabim/RandomEngine.java
   trunk/apps/simsalabim/RunTask.java
   trunk/apps/simsalabim/Script.java
   trunk/apps/simsalabim/ScriptCommand.java
   trunk/apps/simsalabim/ScriptCommandType.java
   trunk/apps/simsalabim/ScriptException.java
   trunk/apps/simsalabim/Settings.java
   trunk/apps/simsalabim/SimulationException.java
   trunk/apps/simsalabim/Simulator.java
   trunk/apps/simsalabim/StringEventLogger.java
   trunk/apps/simsalabim/TestSimulator.java
   trunk/apps/simsalabim/TreeKey.java
   trunk/apps/simsalabim/XorKey.java
   trunk/apps/simsalabim/darknet/
   trunk/apps/simsalabim/darknet/Darknet.java
   trunk/apps/simsalabim/darknet/DarknetNode.java
   trunk/apps/simsalabim/darknet/DarknetRoute.java
   trunk/apps/simsalabim/darknet/Person.java
   trunk/apps/simsalabim/rembre/
   trunk/apps/simsalabim/rembre/Rembre.java
   trunk/apps/simsalabim/rembre/RembreNode.java
   trunk/apps/simsalabim/rembre/RembreRoute.java
   trunk/apps/simsalabim/rembre/test.java
   trunk/apps/simsalabim/utils/
   trunk/apps/simsalabim/utils/Contains.java
   trunk/apps/simsalabim/utils/DataStore.java
   trunk/apps/simsalabim/utils/Heap.java
   trunk/apps/simsalabim/utils/LRUHash.java
   trunk/apps/simsalabim/utils/RandTester.java
   trunk/apps/simsalabim/utils/RoutingTable.java
Log:
simsalabim: Freenet 0.7 simulator

Added: trunk/apps/simsalabim/AggregateCommand.java
===================================================================
--- trunk/apps/simsalabim/AggregateCommand.java                         (rev 0)
+++ trunk/apps/simsalabim/AggregateCommand.java 2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,58 @@
+package simsalabim;
+
+import java.util.StringTokenizer;
+
+/**
+ * AggregateCommand <aggname> <aggtype> ...
+ * 
+ * <aggtype> one of "collector"
+ */
+public class AggregateCommand implements ScriptCommand {
+
+       public static class Type extends ScriptCommandType {
+               public Type() {
+                       super("AggregateCommand");
+               }
+
+               public ScriptCommand newCommand(StringTokenizer params)
+                               throws ScriptException {
+                       return new AggregateCommand(params);
+               }
+       }
+
+       public DataAggregator da;
+
+       public AggregateCommand(StringTokenizer params) throws ScriptException {
+               // first param is name
+               String name = params.nextToken();
+               // next is type
+               String type = params.nextToken();
+
+               if (type.equals("collector")) {
+                       if (params.hasMoreTokens()) {
+                               da = new CDataAggregator(name, 
params.nextToken());
+                       } else {
+                               da = new CDataAggregator(name);
+                       }
+               }
+               if (type.equals("histogram")) {
+                       double start = Double.parseDouble(params.nextToken());
+                       double step = Double.parseDouble(params.nextToken());
+                       double end = Double.parseDouble(params.nextToken());
+                       if (params.hasMoreTokens()) {
+                               da = new HistDataAggregator(name, start, step, 
end, params
+                                               .nextToken());
+                       } else {
+                               da = new HistDataAggregator(name, start, step, 
end);
+                       }
+               } else
+                       throw new ScriptException("No agg type as: " + type);
+       }
+
+       public void execute(Simulator god) {
+               god.aggregateNodeData(da);
+
+               da.complete();
+       }
+
+}

Added: trunk/apps/simsalabim/BasicDataSpace.java
===================================================================
--- trunk/apps/simsalabim/BasicDataSpace.java                           (rev 0)
+++ trunk/apps/simsalabim/BasicDataSpace.java   2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,51 @@
+package simsalabim;
+import java.util.*;
+
+public class BasicDataSpace<K extends Key> implements DataSpace<K> {
+
+
+    private HashMap<K, Data<K>> datamap = new HashMap<K, Data<K>>();
+    private Vector<Data<K>> datav = new Vector<Data<K>>();
+
+    private long minsize, sizerange;
+
+    public BasicDataSpace(long minsize, long maxsize) {
+        this.minsize = minsize;
+        this.sizerange = maxsize - minsize;
+    }
+
+
+    public Data<K> newData(Simulator god, K key) {
+        long size = minsize + (long) (god.random().nextDouble() * sizerange);
+        Data<K> d = new Data<K>(god, key, size);
+        datamap.put(key, d);
+        datav.add(d);
+        return d;
+    }
+
+
+    public Data<K> getData(K key) {
+        return datamap.get(key);
+    }
+
+    public Iterator<Data<K>> allData() {
+        return datav.iterator();
+    }
+
+    public int size() {
+       return datav.size();
+    }
+    
+    public String info() {
+       return "BasicDataSpace size " + datav.size();
+    }
+    
+    
+    /**
+        * Currently uniform.
+        */
+    public K chooseKey(RandomEngine re) {
+        return datav.isEmpty() ? null : datav.get(re.choose(datav.size()) - 
1).key();
+    }
+
+}

Added: trunk/apps/simsalabim/BuildTask.java
===================================================================
--- trunk/apps/simsalabim/BuildTask.java                                (rev 0)
+++ trunk/apps/simsalabim/BuildTask.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,146 @@
+package simsalabim;
+
+/**
+ * Task which builds a simulates a living network.
+ */
+
+public class BuildTask extends RunTask {
+
+       private double joinRate;
+
+       private double leaveRate;
+
+       private double searchRate;
+
+       private double placeRate;
+
+       private long poolSize;
+
+       private long netSize = 1;
+
+       private long tsearches = 0;
+
+       private long ssearches = 0;
+
+       private long tplaces = 0;
+
+       private long splaces = 0;
+
+       private long tsearchcost = 0;
+
+       private long tplacecost = 0;
+
+       /**
+        * The rates are per node per clocktick. They should be low (the sum of 
all
+        * the rates times poolSize should be less than << 1.
+        * 
+        * @param runTill
+        *            Run until this clockbeat
+        * @param searchRate
+        *            The rate at which each active node iniates searches
+        * @param placeRate
+        *            The rate at which each active node iniates placings.
+        * @param poolSize
+        *            The size of the pool of available nodes.
+        * @param joinRate
+        *            The rate at which each inactive node joins.
+        * @param leaveRate
+        *            The rate at which each
+        */
+       public BuildTask(long runTill, double searchRate, double placeRate,
+                       int poolSize, double joinRate, double leaveRate) {
+
+               super(runTill);
+
+               this.searchRate = searchRate;
+               this.placeRate = placeRate;
+               this.poolSize = poolSize;
+               this.joinRate = joinRate;
+               this.leaveRate = leaveRate;
+
+       }
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public BuildTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               // System.err.println(Main.tabbed(s));
+
+               if (s.length < 6)
+                       throw new ScriptException("BuildTask requires six 
parameters, not "
+                                       + s.length);
+
+               this.searchRate = Double.parseDouble(s[1]);
+               this.placeRate = Double.parseDouble(s[2]);
+
+               this.poolSize = Long.parseLong(s[3]);
+               this.joinRate = Double.parseDouble(s[4]);
+               this.leaveRate = Double.parseDouble(s[5]);
+
+               // System.err.println(searchRate + " " + placeRate);
+
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "BuildTask Complete. " + tsearches + " 
searches ("
+                               + ((float) ssearches) / tsearches + " succ, "
+                               + ((double) tsearchcost) / tsearches + " 
cost)." + tplaces
+                               + " places (" + ((float) splaces) / tplaces + " 
succ, "
+                               + ((double) tplacecost) / tplaces + " cost).");
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(RandomEngine re) {
+               double rate = (poolSize - netSize) * joinRate + netSize
+                               * (searchRate + placeRate + leaveRate);
+               // try { Thread.sleep(100); } catch (Exception e) {}
+               // System.err.println("rate: " + rate + " netsize: " + netSize);
+               // Exponentially distributed.
+               return Math.max((long) (Math.log(re.nextDouble()) / (rate * 
-1)), 1);
+
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(RandomEngine re) {
+               double norm = (poolSize - netSize) * joinRate + netSize
+                               * (searchRate + placeRate + leaveRate);
+
+               double val = re.nextDouble() * norm;
+
+               double joinP = (poolSize - netSize) * joinRate;
+               double searchP = netSize * searchRate;
+               double placeP = netSize * placeRate;
+
+               if (val <= joinP) {
+                       netSize++;
+                       return JOIN;
+               } else if (val <= searchP + joinP) {
+                       return SEARCH;
+               } else if (val <= searchP + joinP + placeP) {
+                       return PLACE;
+               } else {
+                       netSize--;
+                       return LEAVE;
+               }
+       }
+
+       public void feedback(int task, boolean success, double[] cost) {
+               if (task == SEARCH) {
+                       tsearches++;
+                       ssearches += success ? 1 : 0;
+                       tsearchcost += cost[0];
+               } else if (task == PLACE) {
+                       tplaces++;
+                       splaces += success ? 1 : 0;
+                       tplacecost += cost[0];
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/CDataAggregator.java
===================================================================
--- trunk/apps/simsalabim/CDataAggregator.java                          (rev 0)
+++ trunk/apps/simsalabim/CDataAggregator.java  2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,91 @@
+package simsalabim;
+import java.util.Vector;
+import java.util.Enumeration;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class CDataAggregator extends DataAggregator {
+
+    private Vector<Number> datac = new Vector<Number>();
+
+    private PrintStream out;
+    private String fname;
+
+    public CDataAggregator(String name) {
+        super(name);
+        out = System.out;
+    }
+
+    public CDataAggregator(String name, String fname) throws ScriptException {
+        super(name);
+        this.fname = fname;
+    }
+
+
+    public void next(long data) {
+        datac.add(new Long(data));
+    }
+
+    public void next(double data) {
+        datac.add(new Double(data));
+    }
+
+    public Long[] getLongData() {
+        Long[] r = new Long[datac.size()];
+        datac.copyInto(r);
+        return r;
+    }
+
+    public Double[] getDoubleData() {
+        Double[] r = new Double[datac.size()];
+        datac.copyInto(r);
+        return r;
+    }
+
+    public void complete() {
+        PrintStream ps;
+        
+        if (fname != null) {
+            try {
+                ps = new PrintStream(new FileOutputStream(fname));
+            } catch (IOException e) {
+                throw new SimulationException("File error : " + e);
+            }
+        } else {
+            ps = this.out;
+        }
+
+        print(ps);
+
+        if (fname != null)
+            ps.close();
+    }
+
+    public void print(PrintStream ps) {
+        ps.println("#Created by Simsalabim. Data: " + name());
+        ps.println("#name: " + name());
+        ps.println("#type: matrix");
+        ps.println("#rows: " + datac.size());
+        ps.println("#columns: 1");
+        for (Enumeration e = datac.elements() ; e.hasMoreElements() ;) {
+            ps.println(e.nextElement().toString());
+        }
+    }
+    
+    public void print(PrintWriter pw) {
+        pw.println("#Created by Simsalabim. Data: " + name());
+        pw.println("#name: " + name());
+        pw.println("#type: matrix");
+        pw.println("#rows: " + datac.size());
+        pw.println("#columns: 1");
+       
+        for (Enumeration e = datac.elements() ; e.hasMoreElements() ;) {
+            pw.println(e.nextElement().toString());
+        }
+    }
+    
+
+
+}

Added: trunk/apps/simsalabim/CircleKey.java
===================================================================
--- trunk/apps/simsalabim/CircleKey.java                                (rev 0)
+++ trunk/apps/simsalabim/CircleKey.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,102 @@
+
+package simsalabim;
+/**
+ * A key which represents a position in a circular [0,1] keyspace
+ * 
+ * @author ossa
+ * 
+ */
+
+public class CircleKey extends PositionKey<CircleKey> {
+
+       public static KeyFactory<CircleKey> getFactory() {
+               return new KeyFactory<CircleKey>() {
+                       public CircleKey newKey(long val) {
+                               val &= 0x7FFFFFFFFFFFFFFFl; // makes positive
+                               double dv = ((double) val) / ((double) 
Long.MAX_VALUE);
+                               return new CircleKey(dv);
+                       }
+               };
+       }
+       
+       
+       double r;
+
+       public CircleKey(double r) {
+               this.r = r;
+       }
+
+       /**
+        * Returns a floating point value designating the nodes position in the
+        * keyspace.
+        */
+       public double ident() {
+               return r;
+       }
+
+
+       /**
+        * Key value expressed in it's natural form as a string.
+        */
+       public String stringVal() {
+               return "" + r;
+       }
+
+
+
+       /**
+        * It is a good idea that keys have these.
+        */
+       public int hashCode() {
+               long v = Double.doubleToLongBits(r);
+               return (int)(v ^ (v >>> 32));
+       }
+
+       public double dist(CircleKey pk) {              
+               return dist(pk.r);
+       }
+       
+       private double dist(double p) {
+               double posb = Math.max(r, p);
+               double poss = Math.min(r, p);
+               return Math.min(posb - poss, 1.0 - posb + poss);
+       }
+
+       public String toString() {
+               return "DNK" + stringVal();
+       }
+       
+       
+       public static double calcMinPoint(CircleKey[] ck, int size) {
+               double best = Double.MAX_VALUE;
+               for (int i = 0 ; i < ck.length ; i++) {
+                       double t = ck[i].r;
+                       double sum = 0.0;
+                       for (int j = 0 ; j < ck.length ; j++) {
+                               double tr = ck[j].r - t;
+                               if (tr < 0) {
+                                       tr += size;
+                               }
+                               sum += tr;
+                       }
+                       
+                       double pos = sum / ck.length + t;
+                       if (pos > size)
+                               pos -= size;
+                       
+                       double val = squareDist(pos, ck);
+                       if (val < best)
+                               best = val;
+               }
+               return best;
+       }
+       
+       public static double squareDist(double x, CircleKey[] ck) {
+               double sum = 0.0;
+               for (int i = 0 ; i < 0 ; i++) {
+                       sum += ck[i].dist(x);
+               }
+               return Math.sqrt(sum);
+       }
+
+}

Added: trunk/apps/simsalabim/Darknet.java
===================================================================
--- trunk/apps/simsalabim/Darknet.java                          (rev 0)
+++ trunk/apps/simsalabim/Darknet.java  2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,842 @@
+package simsalabim;
+import java.util.*;
+import java.io.*;
+/**
+ * Simulates a Darknet using the algorithm described in
+ * 
+ */
+
+public class Darknet extends Simulator<DarknetNode, CircleKey> {
+
+       int INITIAL_SWITCHES;
+       int MAINT_SWITCHES;
+       int HTL;
+       int STORE_SIZE;
+       int CACHE_SIZE;
+       int N_BEST;
+       int RAND_STEPS;
+       int OPENNET_MIN_SUCCESS_BETWEEN_DROP_CONNS;
+       float OPENNET_RESET_PATH_FOLDING_PROB;
+       int OPENNET_DROP_MIN_AGE;              // Has to be tuned for churn... 
+       int OPENNET_MIN_TIME_BEFORE_OFFERS;
+       int OPENNET_MINPEERS;
+       int OPENNET_MAXPEERS;
+       int MAXPEERS;                          // Max num of online peers for a 
node (a person may know more than this)
+       int RANDOM_GROWTH_MODEL;               // How to add nodes, growing 
from the underlying social network
+       int VARDIST_RESOLUTION;                // Resolution of computing 
distribution distance for locations
+       float OPENNET_NODES_SWAP_PROB;         // Should nodes in opennet eval 
swaps?
+       float HYBRID_NODES_SWAP_PROB;          // Should hybrid nodes eval 
swaps?
+       float LEAVE_PERM_PROB;                 // Whether to leave permanent or 
stay dormant
+
+       float DARKLINKS_ONLY_PROB;
+       float RECACHE_PROB;
+       float RELINK_PROB;
+
+       boolean first_split = false;           // Did the network undergo a 
first split? Debugging switching.
+
+       HashMap<Integer, Person> people;
+       TreeSet<Person> ts = new TreeSet<Person>();
+       HashMap<Person, DarknetNode> dormant = new HashMap<Person, 
DarknetNode>();
+       // For several graph partitions
+       int person_id_offset = 0;
+       Person[] people1;
+       Person[] people2;       
+       
+       int size;
+       int n_darknet_total;
+       int n_opennet_total;
+       int n_opennet_active;
+       int n_darknet_active;
+
+       /*
+        * Use (load) single graph:
+        *
+        * Use dual graphs (as the Kleinberg model):
+        *
+        * Darknet dual size1 outdeg1 size2 outdeg2 cut
+        * where
+        *     size = #nodes
+        *     outdeg = #edges (undirected) added to each node in each network
+        *     cut = #edges between networks
+        */
+       public Darknet(RandomEngine re, DataSpace ds, Settings st, EventLogger 
el,
+                       String args) {
+               super(re, ds, st, el);
+               setParameters();
+
+               StringTokenizer stoke = new StringTokenizer(args,", ");
+
+               if (stoke.countTokens() < 1) {
+                       throw new RuntimeException("No parameters given.");
+               }
+               people = new HashMap<Integer,Person>();
+
+               String name = stoke.nextToken();
+               if (name.equals("dual")) {
+                       if (stoke.countTokens() != 6)
+                               throw new RuntimeException("dual graphs: not 
enough arguments");
+                       
+                       int size1 = Integer.parseInt(stoke.nextToken());
+                       int degree1 = Integer.parseInt(stoke.nextToken());
+                       int size2 = Integer.parseInt(stoke.nextToken());
+                       int degree2 = Integer.parseInt(stoke.nextToken());
+                       Integer startId = Integer.parseInt(stoke.nextToken());
+                       int cut = Integer.parseInt(stoke.nextToken());
+                       people1 = swgraph(size1, 0, degree1);
+                       people2 = swgraph(size2, size1, degree2);
+                       joinNetworks(cut);
+               }
+               else if (name.equals("kleinberg")) {                    
+                       if (stoke.countTokens() != 2)
+                               throw new RuntimeException("kleinberg: requires 
size and degree");
+                       people1 = swgraph(Integer.parseInt(stoke.nextToken()), 
0, Integer.parseInt(stoke.nextToken()));
+                       for(int i=0; i<people1.length; i++) {
+                               people.put(new Integer(i), people1[i]);
+                       }
+               }
+               else if (name.equals("load")) {
+                       try {
+                               readGraph(new File(stoke.nextToken()), 
Integer.parseInt(stoke.nextToken()));
+                       } catch (IOException e) {
+                               throw new RuntimeException("Error reading 
darknet graph: " + e);
+                       }
+               }
+               else if (name.equals("loaddual")) {
+                       if (stoke.countTokens() != 5)
+                               throw new RuntimeException("loaddual requires: 
graph1 startid1 graph2 startid2 cut");
+                       try {
+                               people1 = readGraph2(false, new 
File(stoke.nextToken()), Integer.parseInt(stoke.nextToken()));
+                               people2 = readGraph2(false, new 
File(stoke.nextToken()), Integer.parseInt(stoke.nextToken()));
+                       } catch (IOException e) {
+                               throw new RuntimeException("Error reading dual 
darknet graphs: " + e);
+                       }
+
+                       int cut = Integer.parseInt(stoke.nextToken());
+                       joinNetworks(cut);
+               } else {
+                       throw new RuntimeException("Darknet: options 
load,kleinberg,dual");
+               }
+
+               if (RANDOM_GROWTH_MODEL == 1) // All nodes become available
+                       for (Person p: people.values())
+                               ts.add(p);
+
+               chooseTypes(people);          // Pick opennet/darknet/hybrid 
nodes
+               System.err.println("Read graph of size " + people.size());
+               System.err.println("Average degree: " + avgDeg());
+
+               logCreation();
+       }
+
+       public float avgDeg() {
+               int n = 0;
+               for (Person p: people.values()) {
+                       n += p.degree();
+               }
+
+               return ((float) n / people.size());
+       }
+
+       /**
+        * How many nodes initiate maintenance (swapping) requests
+        */       
+       public int maintSize() {
+               return n_darknet_active;
+       }
+
+       /*
+        * Partition the positions of two disjoint networks into [0,0.5] and 
[0.5,1]
+        * Ignoring P({pick 0.5}) > 0
+        */
+
+       public void dualSplitPositions() {
+
+           if (!first_split) {
+               first_split = true;
+                   System.err.println("WARNING, modifying node locations!");
+                   for (Person p: people1) {
+                       p.getNode().pos.r = 0.5*re.nextDouble();
+                   }
+
+                   for (Person p: people2) {
+                       p.getNode().pos.r = 0.5 + 0.5*re.nextDouble();
+                   }
+           }
+       }
+
+       /*
+        * @return: array of the random cut between networks as (src,dst) pairs
+        */
+       public Person[][] joinNetworks(int cutsize) {
+           for (int i=0; i<people1.length; i++) {
+               Integer id1 = new Integer(i);
+               people.put(new Integer(people1[i].id) ,people1[i]);
+               people1[i].setNetwork(1);
+           }
+           for (int i=0; i<people2.length; i++) {              
+               people.put(new Integer(people2[i].id), people2[i]);
+               people2[i].setNetwork(2);
+           }
+
+           Person [][] cut = new Person [cutsize][2];
+           
+           for (int i=0; i<cutsize; i++) {
+               boolean found = false;
+               while (!found) {
+                   Person src = people1[re.nextInt(people1.length)];
+                   Person dst = people2[re.nextInt(people2.length)];
+                
+                   if (!src.linksTo(dst) && !dst.linksTo(src)) {
+                       src.addOut(dst);
+                       dst.addOut(src);
+                       found = true;
+                       cut[i][0] = src;
+                       cut[i][1] = dst;
+                   }
+               }
+           }
+           return cut;
+       }
+       
+       /*
+        * Generate a Kleinberg small-world of size, with a fixed number of 
links added to
+        * each node (resulting in a random number of links for nodes)
+        */
+       public Person[] swgraph(int size, int offset, int outdeg) {
+
+              Person [] people = new Person[size];
+              for (int i=0; i<size; i++) {
+                  people[i] = new Person(i+offset);
+              }
+
+              for (int i=0; i<size; i++) {
+                  Person source = people[i];
+
+                  for (int j=0; j<outdeg; j++) {
+                      boolean found = false;
+                      while (!found) {
+                            double u = re.nextDouble();
+                            int dist = (int) 
Math.floor(Math.exp(u*Math.log(size/2)));
+                            int dest = i + ((re.nextDouble() < 0.5) ? -dist : 
dist);
+                            if (dest < 0) dest = dest + size;
+                            if (dest > size-1) dest = dest - size;
+                            Person target = people[dest];
+
+                            if (!source.linksTo(target) && 
!target.linksTo(source)) {
+                               source.addOut(target);
+                               target.addOut(source);
+                               found = true;
+                            }
+                      }
+                  }
+              }
+              System.err.println("Created ideal Kleinberg graph (w.o. local 
connections) of size " + size);
+              return people;
+       }
+               
+        protected void setParameters() {
+               // Settings
+               INITIAL_SWITCHES = st.getInt("dnInitialSwitches",100);
+               MAINT_SWITCHES = st.getInt("dnMaintSwitches",100);
+               RAND_STEPS = st.getInt("dnRandSteps",10);
+               HTL = st.getInt("dnHTL",20);
+               STORE_SIZE = st.getInt("dnStoreSize",50);
+               CACHE_SIZE = st.getInt("dnCacheSize",STORE_SIZE);               
  // Default: same size as store
+               VARDIST_RESOLUTION = st.getInt("varDistResolution", 100);
+               N_BEST = st.getInt("dnNBest",3);
+               MAXPEERS = st.getInt("dnMaxPeers",50);
+               RECACHE_PROB = (float) st.getDouble("dnRecacheProb",0.0);       
  // Whether to recache data after each successful request
+               RELINK_PROB = (float) st.getDouble("dnRelinkProb",1.0);
+               DARKLINKS_ONLY_PROB = (float) 
st.getDouble("dnLinksOnlyProb",1.0);
+               if ((DARKLINKS_ONLY_PROB>1) || (DARKLINKS_ONLY_PROB<0))
+                  throw new Error("DARKLINKS_ONLY_PROB (dnLinksOnlyProb) needs 
to be in [0,1]");
+
+               OPENNET_NODES_SWAP_PROB = (float) st.getDouble("onSwapProb", 
0.0);
+               if (!(OPENNET_NODES_SWAP_PROB==0 || 
(OPENNET_NODES_SWAP_PROB==1)))
+                  throw new Error("OPENNET_NODES_SWAP_PROB (onSwapProb) needs 
to be 0 or 1");
+
+               HYBRID_NODES_SWAP_PROB = (float) st.getDouble("hnSwapProb", 
0.0);
+               if (!(HYBRID_NODES_SWAP_PROB==0 || HYBRID_NODES_SWAP_PROB==1))
+                  throw new Error("HYBRID_NODES_SWAP_PROB (hnSwapProb) needs 
to be in 0 or 1");                
+
+               RANDOM_GROWTH_MODEL = (int) st.getInt("randomGrowth",0);        
  // Default growth is F2F, else persons with nodes are picked at random
+               if (!(RANDOM_GROWTH_MODEL==0 || RANDOM_GROWTH_MODEL==1))
+
+               LEAVE_PERM_PROB = (float) st.getDouble("dnLeavePermProb",0.1);
+               if ((LEAVE_PERM_PROB>1.0) || (LEAVE_PERM_PROB<0.0))
+                   throw new Error("LEAVE_PERM_PROB (dnLeavePermProb) needs to 
be in [0,1]");
+
+               // As in the freenet implementation
+               OPENNET_MIN_SUCCESS_BETWEEN_DROP_CONNS = 
st.getInt("onMinSuccess",10); // Min successful requests between folding 
(Freenet 0.7)
+               OPENNET_RESET_PATH_FOLDING_PROB = (float) 
st.getDouble("onResetFoldProb",0.05); // Probability to steal folding reference 
(Freenet 0.7)
+               OPENNET_DROP_MIN_AGE = st.getInt("onDropMinAge",30);            
  // FIXME implement: Min age for node before we can drop it (Freenet 0.7)
+               OPENNET_MIN_TIME_BEFORE_OFFERS = 
st.getInt("onMinTimeOffers",30); // FIXME implement: Min age between we give 
offers (Freenet 0.7)
+               OPENNET_MINPEERS = st.getInt("onMinPeers",10);
+               OPENNET_MAXPEERS = st.getInt("onMaxPeers",20);
+
+               if (OPENNET_RESET_PATH_FOLDING_PROB > 1 || 
OPENNET_RESET_PATH_FOLDING_PROB < 0)
+                       throw new Error("OPENNET_RESET_PATH_FOLDING_PROB 
(onResetFoldProb) needs to be in [0,1]");
+       }
+
+       protected void logCreation() {
+               ev.message(time(), "Created Darknet Simulator. SWITCHES: " + 
MAINT_SWITCHES + 
+                               " RANDSTEPS: " + RAND_STEPS + " HTL: " 
+                               + HTL + " STORESIZE: " + STORE_SIZE + ".");
+       }
+
+       public DarknetNode newNode(int num) {
+
+               Person p;
+               
+               if (RANDOM_GROWTH_MODEL==0)
+                       p = growNetwork();
+               else
+                       p = growNetworkRandom();
+
+               if (p == null)
+                       throw new SimulationException("Poplulation smaller than 
network wants to be.");
+               
+               DarknetNode n;
+               if (!dormant.containsKey(p)) {
+                       double r = re.nextDouble();
+                       n = new DarknetNode(this, num, p, new CircleKey(r));
+               } else {
+                       n = dormant.get(p);                              // 
Zombie
+                       dormant.remove(p);
+               }
+
+               return n;
+
+       }
+
+       public CircleKey newKey() {
+               return new CircleKey(re.nextDouble());
+       }
+
+       // Not depending on oldNode (linking to a living node) at the moment... 
+       public boolean join(DarknetNode newNode, DarknetNode oldNode, Feedback 
fb) {
+               int cost = newNode.join();
+
+               if (newNode.isOpennet()) {
+                  opennetBootstrap(newNode, OPENNET_MINPEERS);
+                  n_opennet_active++;
+               }
+               else if(newNode.isDarknet()) {
+                  n_darknet_active++;
+               } else {
+                       throw new RuntimeException("join: Node of unknown type 
joins network");
+               }
+
+               size++; // After bootstrap
+               fb.feedback(RunTask.JOIN, true, new double[] {cost});
+               return true;
+       }
+       
+       /*
+        * Simulate a seednodes that give connections uniformly at random to a 
new opennet peer
+        */
+       public int opennetBootstrap(DarknetNode newNode, int npeers) {
+
+              int found = 0;
+              for (int i = 0; i < npeers && newNode.needsOpennet(); i++) {
+                  DarknetNode prospect = chooseOpennetNode();
+                  if (prospect == null) {
+                     if (i==0) System.err.println("Warning: an opennet node 
not able to bootstrap at all, " + n_opennet_active + " active opennet nodes");
+                     break;  // Unavailable
+                  }
+                  
+                  if (prospect.needsOpennet()) {
+                     newNode.reLink(prospect);
+                     found++;
+                  }
+
+              }
+
+              if (found == 0 && newNode.needsOpennet())
+                      System.err.println("Warning: an opennet node, darkdegree 
" + newNode.neighbors.size() + " not able to connect");
+              return found;
+       }
+
+       public void maintenance(Feedback fb) {
+
+               int succ = 0;
+               int crossing = 0;
+               int crossing_succ = 0;
+               CircleKey pos = null;
+
+               for (int i = 0; i < MAINT_SWITCHES ; i++) {
+                       DarknetNode n = (DarknetNode) chooseNode();
+                       DarknetNode target = null;
+
+                       if (n != null) {
+                          pos = n.pos;
+
+                          if (n.isDarknet())
+                             target = n.makeSwitch();
+                          else if (n.isOpennet() && OPENNET_NODES_SWAP_PROB==1)
+                             target = n.makeSwitch();
+                       }
+
+                       if (n!=null && target!=null) {
+                               if (pos == target.pos)
+                                       succ++;
+
+                               if (n.person().network() != 
target.person().network()) {
+                                       crossing++;
+                                       if (pos == target.pos)
+                                               crossing_succ++;
+                               }
+                       }
+
+               }
+               fb.feedback(RunTask.MAINTENANCE, true, new double[] 
{MAINT_SWITCHES, succ, crossing, crossing_succ});
+       }
+
+
+       /**
+        * Some statistics about network partitions
+        */
+       public void dualstats(Feedback fb) {
+
+              if (people1==null || people2==null)
+                 throw new SimulationException("Attempting dualstats() without 
a partition");
+
+              double first,second,complete;
+              first = second = 0;
+
+              for (int i=0; i<people1.length; i++) {
+                  if (people1[i].isInNetwork())
+                     first++;
+              }
+              for (int i=0; i<people2.length; i++) {
+                  if (people2[i].isInNetwork())
+                     second++;
+              }
+
+              fb.feedback(RunTask.DUALSTATS, true, new double[] 
{first,second,varDist(people1),varDist(people2),varDist(),varDist(people1,people2)});
+              // Total variation distance between uniform and sampling
+              // return new double[] 
{first,second,varDist(people1),varDist(people2),varDist(people1,people2))};
+              
+       }
+
+       /**
+        * Variation distance (for whole network)
+        */
+       public double varDist() {
+               int[] quant = new int[VARDIST_RESOLUTION];
+               int n = 0;
+
+               for (Person p: people.values()) {
+                       if (p.isInNetwork()) {
+                               n++;
+                               int i = (int) 
(p.getNode().pos.ident()*VARDIST_RESOLUTION);
+                               quant[i]++;
+                       }
+               }
+
+               double vd = 0;
+               for (int i=0; i<VARDIST_RESOLUTION; i++) {
+                       double delta = Math.abs(((double)quant[i]/n) - 
((double)1/VARDIST_RESOLUTION));
+                       vd = vd + delta;
+               }
+
+               vd = vd/2;
+               if ((vd<0) || (vd>1))
+                       throw new SimulationException("varDist(): error 
computing total variation distance for whole population");
+
+               return vd;
+       }
+
+       /**
+        * Variation distance (for distribution distance vs uniform 
distribution)
+        * Counting positions currently in network
+        */
+
+       public double varDist(Person[] population) {
+           int[] quant = new int[VARDIST_RESOLUTION];
+           int n = 0;
+
+           for (Person p: population) {
+               if (p.isInNetwork()) {
+                   n++;                    
+                   int i = (int) (p.getNode().pos.ident()*VARDIST_RESOLUTION);
+                   quant[i]++;
+               }
+           } 
+
+           double vd = 0;
+           for (int i=0; i<VARDIST_RESOLUTION; i++) {
+               double delta = Math.abs(((double)quant[i]/n) - 
((double)1/VARDIST_RESOLUTION));
+               vd = vd + delta;
+           }
+
+           vd = vd/2;
+           if ((vd < 0) || (vd>1)) 
+              throw new SimulationException("varDist(): error computing total 
variation distance");
+
+           return vd;
+       }
+
+       /**
+        * Variation distance where one of the networks is seen as the reference
+        */
+
+       public double varDist(Person[] first, Person[] second) {
+               int[] quant1 = new int[VARDIST_RESOLUTION];
+               int[] quant2 = new int[VARDIST_RESOLUTION];
+               int n1=0,n2=0;
+
+               for (Person p: first) {
+                       if (p.isInNetwork()) {
+                               n1++;
+                               int i = (int) 
(p.getNode().pos.ident()*VARDIST_RESOLUTION);
+                               quant1[i]++;
+                       }
+               }
+
+               for (Person p: second) {
+                       if (p.isInNetwork()) {
+                               n2++;
+                               int i = (int) 
(p.getNode().pos.ident()*VARDIST_RESOLUTION);
+                               quant2[i]++;
+                       }
+               }
+
+               double vd = 0;
+               for (int i=0; i<VARDIST_RESOLUTION; i++) {
+                       double delta = Math.abs(((double)quant1[i]/n1) - 
((double)quant2[i]/n2));
+                       vd = vd + delta;
+               }
+
+               vd = vd/2;
+               if ((vd < 0) || (vd>1))
+                       throw new SimulationException("varDist(.,.): error 
computing mutual variation distance");
+
+               return vd;
+       }
+
+
+       /*
+        * Some statistics about available keys
+        */
+
+       public int[] keystats() {
+           int activeKeys=0,sleepingKeys=0;
+
+           for (Node n: nodes)
+               activeKeys = activeKeys + n.nKeys();
+
+           for (Node n: dormant.values())
+               sleepingKeys = sleepingKeys + n.nKeys();
+
+           return new int[] {activeKeys, sleepingKeys};
+       }
+
+       public Data search(CircleKey k, DarknetNode n, boolean ghost, Feedback 
fb) {
+               DarknetRoute route = n.findRoute(k, null);
+               Data d = route.findData(k);
+
+               if (d != null) {
+                       if (RECACHE_PROB>0 && re.nextFloat()<RECACHE_PROB)
+                               route.storeData(d, false, true);
+                       else
+                               route.storeData(d, false, false);
+               }
+               
+               // Freenet 0.7: path folding happens only on successful requests
+               if (d != null)
+                       route.reLink(re, RELINK_PROB, 
OPENNET_RESET_PATH_FOLDING_PROB);
+
+               int srcNet = n.person().network(), origNet = 
ds.getData(k).sourceNetwork();
+
+               fb.feedback(RunTask.SEARCH, d != null, new double[] 
{route.size(),   d != null ? route.size() : 0,   srcNet,   origNet});
+               return d;
+       }
+
+       public boolean place(Data<CircleKey> d, DarknetNode n, Feedback fb) {
+               // Remember source
+               d.sourceNetwork(n.person().network());
+               DarknetRoute route = n.findRoute(d.key(), null);
+
+               route.storeData(d, true, true);
+               // Freenet 0.7: Inserts not leading to path folding
+               // route.reLink(re, RELINK_PROB);
+
+               fb.feedback(RunTask.PLACE, true, new double[] {route.size()});
+               return true;
+       }
+
+       public void leave(DarknetNode dn, Feedback fb, boolean permanent) {
+               size--;
+               if (dn.isOpennet()) {
+                  n_opennet_active--;
+               }
+               else if (dn.isDarknet()) {
+                  n_darknet_active--;
+               }
+               else 
+                  throw new RuntimeException("leave(): unknown type");
+
+               // Put node among zombies
+               if (!permanent) {
+                   if (dormant.containsKey(dn.person()) || 
dormant.containsValue(dn))
+                       throw new SimulationException("Person already with 
dormant node, or node already dormant");
+                   dormant.put(dn.person(), dn);
+               }
+
+               dn.leave(permanent);
+               shrinkNetwork(dn.person());
+               fb.feedback(RunTask.LEAVE, true, new double[] {1});
+       }
+       
+       /**
+        * Does the node leave permanently, or just become dormant?
+        */
+       public boolean leavePermanent(DarknetNode n) {
+           if (LEAVE_PERM_PROB == 1)
+               return true;
+           if (LEAVE_PERM_PROB == 0)
+               return false;
+           if (re.nextDouble() < LEAVE_PERM_PROB)
+               return true;
+           else
+               return false;
+       }
+
+       /*
+        *  Pick an opennet node, uniformly at random
+        */
+       public DarknetNode chooseOpennetNode() {
+              if (n_opennet_active == 0)
+                 return null;
+              int j = re.nextInt(n_opennet_active);
+              int k = -1;
+
+              for (int i=0; i < size; i++) {
+                  if (nodes.get(i).isOpennet())
+                     k++;
+                     
+                  if (k==j)
+                     return nodes.get(i);
+              }
+
+              throw new SimulationException("chooseOpennetNode(): sampling 
error");
+       }
+
+
+       protected void readGraph(File s, int startId) throws IOException {
+               BufferedReader br = new BufferedReader(new FileReader(s));
+               String line;
+               System.err.println("Parsing...");
+               int i = 0;
+               while ((line = br.readLine()) != null) {
+                       if (++i % 100000 == 0)
+                               System.err.println("Line: " + i);
+                       StringTokenizer st = new StringTokenizer(line, "\t");
+                       if (st.nextToken().equals("E")) {
+                               Integer id1 = new Integer(st.nextToken());
+                               Integer id2 = new Integer(st.nextToken());
+
+                               Person n1 = people.get(id1);
+                               if (n1 == null) {
+                                       n1 = new Person(id1);
+                                       people.put(id1, n1);
+                               }
+                               Person n2 = people.get(id2);
+                               if (n2 == null) {
+                                       n2 = new Person(id2);
+                                       people.put(id2, n2);
+                               }
+
+                               // There is duplicate edge protection, and we 
are only
+                               // interested
+                               // in undirected graphs
+                               n1.addOut(n2);
+                               n2.addOut(n1);
+                       }
+               }
+
+               System.err.println("Read " + people.size() + " people into the 
network.");
+
+               Person p = people.get(startId);
+               if (p == null)
+                       throw new RuntimeException("The starting identity did 
not exist");
+               ts.add(p);
+       }
+
+
+       /**
+        * Reads a graph file containing the social network data.
+        * @direct: if true, then reading directly into final set of people
+        * @startId: the id in the graph that a growth process starts with
+        */
+       protected Person[] readGraph2(boolean direct, File s, int startId) 
throws IOException {
+                BufferedReader br = new BufferedReader(new FileReader(s));
+                String line;
+                HashMap <Integer,Person> temp = new HashMap<Integer, Person>();
+                HashMap <Integer,Integer> idmap = new HashMap<Integer, 
Integer>();
+                Integer start = null;
+
+                System.err.println("Parsing...");
+                int i = 0;
+                while ((line = br.readLine()) != null) {
+                        if (++i % 100000 == 0)
+                                System.err.println("Line: " + i);
+                        StringTokenizer st = new StringTokenizer(line, "\t");
+                        if (st.nextToken().equals("E")) {
+                                Integer id1raw = new Integer(st.nextToken());
+                                Integer id2raw = new Integer(st.nextToken());
+
+                                // Fix representation
+                                Integer id1 = idmap.get(id1raw);
+                                Integer id2 = idmap.get(id2raw);
+                                if (id1 == null) {
+                                        id1 = new Integer(person_id_offset++);
+                                        idmap.put(id1raw, id1);
+                                }
+                                if (id2 == null) {
+                                        id2 = new Integer(person_id_offset++);
+                                        idmap.put(id2raw, id2);
+                                }
+
+                                // Node to start from
+                                if ((start==null) && 
(startId==id1raw.intValue()))
+                                        start = id1;
+                                if ((start==null) && 
(startId==id2raw.intValue()))
+                                        start = id2;
+
+                                Person n1 = direct ? people.get(id1) : 
temp.get(id1);
+                                if (n1 == null) {
+                                        n1 = new Person(id1);
+                                        if (direct)
+                                                people.put(id1, n1);
+                                        else 
+                                                temp.put(id1, n1);
+                                }
+                                Person n2 = direct ? people.get(id2) : 
temp.get(id2);
+                                if (n2 == null) {
+                                        n2 = new Person(id2);
+                                        if (direct)
+                                                people.put(id2, n2);
+                                        else
+                                                temp.put(id2, n2);
+                                }
+
+                                // There is duplicate edge protection, and we 
are only
+                                // interested in undirected graphs
+                                n1.addOut(n2);
+                                n2.addOut(n1);
+                        }
+                }
+
+                System.err.println("Read " + (direct ? people.size() : 
temp.size()) + " people into the network.");
+                
+                if (start == null)
+                        throw new RuntimeException("The starting identity did 
not exist");
+                Person p = direct ? people.get(start) : temp.get(start);
+                ts.add(p);
+
+                if (!direct) {
+                        Person[] target = new Person[temp.size()];
+                        int j = 0;
+                        for (Person q: temp.values())
+                                target[j++] = q;
+
+                        return target;
+                }
+
+                return null;
+        }
+
+       /**
+        *
+        * Distribute opennet/darknode persons over the network
+        */
+
+        private void chooseTypes(HashMap<Integer, Person> people) {            
 
+                System.err.println("Picking darknet nodes with p = " + 
DARKLINKS_ONLY_PROB);            
+
+                for (Person p : people.values()) {
+                    if (re.nextDouble() < DARKLINKS_ONLY_PROB) {
+                       p.setDarknet();
+                       n_darknet_total++;
+                    } else {
+                       p.setOpennet();
+                       n_opennet_total++;
+                    }
+                }
+
+                System.err.println("Picked " + n_darknet_total + " darknet 
persons, and " + n_opennet_total + " opennet persons (uniformly random)");
+
+        }
+
+
+        /**
+                * Chooses the next person to join the network.
+                */
+        protected Person growNetwork() {
+                // System.err.println("TS SIZE: " + ts.size());
+                if (ts.isEmpty()) // no more people left
+                        return null; 
+                Person cand = ts.last();
+                // System.err.println("Score: " + cand.score);
+                ts.remove(cand);
+
+                for (Iterator<Person> it = cand.neighbors() ; it.hasNext() ; ) 
{
+                        Person p = it.next();
+
+                        if (ts.contains(p)) {
+                                ts.remove(p);
+                                p.score++;             
+                                ts.add(p);
+                        } else {
+                                p.score++;
+                                if (!p.isInNetwork()) {
+                                        ts.add(p);
+                                }
+                        }
+                }
+
+                return cand;
+        }       
+
+        /**
+          * Chooses the next person, uniformly at random rather than weighted.
+          */
+       protected Person growNetworkRandom() {
+           // System.err.println("TS SIZE: " + ts.size());
+                 if (ts.isEmpty())
+                    return null;
+
+                 int k = re.nextInt(ts.size());                  
+                 int i = 0;
+                 for (Iterator<Person> it = ts.iterator(); it.hasNext(); i++) {
+                     Person p = it.next();
+                     if (i==k) {
+                        ts.remove(p);
+                        return p;
+                     }
+                 }
+
+                 throw new RuntimeException("growNetworkRandom(): sample 
error");
+       }
+
+
+        /**
+                * Takes somebody out of the network.
+                */
+        protected void shrinkNetwork(Person p) {
+                for (Iterator<Person> it = p.neighbors() ; it.hasNext() ; ) {
+                        Person n = it.next();
+                        if (ts.contains(n)) {
+                                ts.remove(n);
+                                n.score--;             
+                                ts.add(n);
+                        } else {
+                                n.score--;
+                        }
+                }
+                ts.add(p);
+        }
+
+}

Added: trunk/apps/simsalabim/DarknetNode.java
===================================================================
--- trunk/apps/simsalabim/DarknetNode.java                              (rev 0)
+++ trunk/apps/simsalabim/DarknetNode.java      2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,504 @@
+package simsalabim;
+import java.util.*;
+import simsalabim.utils.*;
+
+public class DarknetNode extends Node<Darknet, CircleKey> {
+
+       protected LRUHash data; 
+       protected LRUHash cache;
+       protected Person user;
+
+       protected CircleKey pos;
+       protected LinkedList<DarknetNode> neighbors;
+       protected HashMap<DarknetNode,Boolean> neighbors_lookup;
+       protected HashMap<DarknetNode,Boolean> open_lookup;
+       protected LinkedList<DarknetNode> openNeighbors;
+
+       private boolean inNetwork = false;
+
+       private int numQueries = 0;
+
+       public DarknetNode(Darknet god, int num, Person user, CircleKey pos) {
+               super(god, num);
+               this.user = user;
+
+               this.pos = pos;
+
+               data = new LRUHash(god.STORE_SIZE);
+               cache = new LRUHash(god.CACHE_SIZE);
+               neighbors = new LinkedList<DarknetNode>();
+               openNeighbors = new LinkedList<DarknetNode>();
+               neighbors_lookup = new HashMap<DarknetNode,Boolean>();
+               open_lookup = new HashMap<DarknetNode,Boolean>();
+       }
+
+       public boolean isOpennet() {
+           return user.isOpennet();
+       }
+
+       public boolean isDarknet() {
+           return user.isDarknet();
+       }
+
+       /** 
+        * Need of opennet: always except when all slots filled by darknet peers
+        */
+       public boolean needsOpennet() {
+           if (!isOpennet())
+              throw new SimulationException("DarknetNode asked about opennet 
refs without being an opennet node");
+           return neighbors.size() < Math.min(god.OPENNET_MAXPEERS, 
god.MAXPEERS);
+       }
+
+       public boolean needsDarknet() {
+               return neighbors.size() < god.MAXPEERS;
+       }
+
+       public DarknetRoute findRoute(CircleKey k, DarknetRoute dnr) {
+
+               if (!inNetwork)
+                       throw new Error("Node not in Network routed to: "  + 
this);
+               if (!isActive())
+                       throw new Error("Node that is active routed to: " + 
this);
+
+               /*
+                * Routing with
+                * 1) Backtracking
+                * 2) HTL (fixed decrease per node, not probabilistic as in 
freenet)
+                *
+                * Cache/Store: when route has completed
+                */
+
+               if (dnr == null)
+                       dnr = new DarknetRoute(god.N_BEST, god.HTL);
+
+               dnr.atNode(this, k);
+               numQueries++;
+
+               if (data.contains(k) || cache.contains(k))              // 
terminate on success
+                       return dnr;
+
+               while (dnr.htl > 0) {
+                     double bd = Double.MAX_VALUE;
+                     DarknetNode cand = null;
+
+                     for (Iterator<DarknetNode> it = neighbors() ; 
it.hasNext() ;) {
+                         DarknetNode t = it.next();
+                         if (dnr.contains(t))
+                            continue;
+
+                         double cd = t.pos.dist(k);
+
+                         if (cd < bd) {
+                            bd = cd;
+                            cand = t;
+                         }
+                     }
+
+                     if (cand != null) {
+                         DarknetRoute branch = cand.findRoute(k, dnr);
+                         dnr.atNode(this,k);
+
+                         if (branch.findData(k) != null) {
+                             if (hasOpennetNeighbor(cand))
+                                     moveUpOpennet(cand);             // 
Freenet 0.7: Keep opennet nodes at LRU
+
+                             return dnr;
+                         }
+                     } else {
+                         return dnr;           // Dead end
+                     }
+               }
+
+               return dnr;
+               
+       }
+
+       /**
+        * To keep opennet peers sorted in most-recently used
+        */
+       public void moveUpOpennet(DarknetNode on) {
+               if (!hasOpennetNeighbor(on))
+                       throw new SimulationException("moveUpOpennet(): Lacking 
neighbor");
+
+               openNeighbors.remove(on);
+               openNeighbors.addFirst(on);             
+       }
+
+       public int nKeys() { return data.size(); }
+
+       /**
+        *  Connect to online darknet nodes up to a limit. With many available, 
pick at random.
+        */
+       public void refreshDarknet() {
+               int room = god.MAXPEERS - neighbors.size();
+               if (room == 0)
+                       return;
+               if (room < 0)
+                       throw new SimulationException("refreshDarknet(): 
exceeding MAXPEERS");
+
+               int avail = 0;
+               for (Iterator<Person> it = user.neighbors() ; it.hasNext() ; ) {
+                       Person p = it.next();
+                       if (p.isInNetwork() && !hasDarknetNeighbor(p.getNode()) 
&& p.getNode().needsDarknet())
+                               avail++;
+               }
+               if (avail == 0)
+                       return;
+
+               if (avail <= room) {
+                       for (Iterator<Person> it = user.neighbors() ; 
it.hasNext() ; ) {
+                               Person p = it.next();
+                               if (p.isInNetwork() && 
!hasDarknetNeighbor(p.getNode()) && p.getNode().needsDarknet()) {
+                                       makeNeighbors(p.getNode());
+                               }
+                       }
+               } 
+               else {
+                       // If more peers available than room, pick randomly
+                       for (int i=0; i<room; i++) {
+                               int ni = god.random().nextInt(avail-i);
+                               int nj = 0;
+                               Person p = null;
+
+                               for (Iterator<Person> it = user.neighbors(); 
it.hasNext() ; ) {
+                                       p = it.next();
+                                       if (p.isInNetwork() && 
!hasDarknetNeighbor(p.getNode()) && p.getNode().needsDarknet()) {
+                                               if (nj == ni)
+                                                       break;
+                                               nj++;
+                                       }
+                               }
+
+                               if (ni != nj) throw new 
SimulationException("Sample error: picking darknet peer");
+
+                               makeNeighbors(p.getNode());
+                       }
+               }
+       }
+
+       /**
+        * Drop opennet nodes if we have too many.
+        * Drop policy: LRU wrt. successful requests
+        */
+
+       public void policyOpennet() {
+               if (!isOpennet()) 
+                       throw new SimulationException("Non-opennet node 
imposing opennet policy");
+
+               int maxopen = Math.min(god.OPENNET_MAXPEERS, god.MAXPEERS);
+               if (openNeighbors.size() > Math.max(0, 
maxopen-neighbors.size())) {
+                       int drop = openNeighbors.size() - Math.max(0, 
maxopen-neighbors.size());
+
+                       for (int i=0; i<drop; i++) {
+                               DarknetNode on = openNeighbors.getLast();  // 
Freenet 0.7: LRU
+                               removeOpenNeighbor(on);
+                       }
+               }
+       }
+
+       public int join() {
+               inNetwork = true;
+               user.joinedNetwork(this);
+               refreshDarknet();
+
+               if (this.isDarknet()) {
+                       int nsw = Math.min(god.size, god.INITIAL_SWITCHES);
+                       for (int i = 0 ; i < nsw ; i++) {
+                           makeSwitch();
+                       }
+                       return nsw * god.RAND_STEPS;
+               }
+
+               return 0;
+       }
+
+       public DarknetNode makeSwitch() {
+
+               if (!this.isDarknet() && !(god.HYBRID_NODES_SWAP_PROB==1 || 
god.OPENNET_NODES_SWAP_PROB==1)) {
+                  throw new Error("makeSwitch() forbidden for non-darknet 
nodes");
+               }
+
+               DarknetNode n = getRandomNode();
+               if ((n != null) && (n.isDarknet() || 
god.HYBRID_NODES_SWAP_PROB==1 || god.OPENNET_NODES_SWAP_PROB==1)) {
+                       double mhVal = Math.min(1, Math.exp((logdist(pos) + 
n.logdist(n.pos) 
+                                       - logdist(n.pos) 
+                                       - n.logdist(pos))));
+
+                       if (god.random().nextDouble() < mhVal) {
+                               CircleKey mp = pos;
+                               pos = n.pos;
+                               n.pos = mp;
+                       } 
+               }
+               
+               return n;
+
+       }
+
+       public int leave(boolean permanent) {
+               inNetwork = false; //NB: person disappears before node, to 
avoid peers reconnecting
+               user.leftNetwork();
+               int n = neighbors.size() + openNeighbors.size();
+
+               for (Iterator<Person> it = user.neighbors() ; it.hasNext() ; ) {
+                       Person p = it.next();
+                       if (p.isInNetwork()) {
+                               removeNeighbor(p.getNode());
+                               p.getNode().refreshDarknet();
+                       }
+               }
+               
+               for (Iterator<DarknetNode> it = openNeighbors.iterator() ; 
it.hasNext() ; ) {
+                   DarknetNode open = it.next();
+                   open.openNeighbors.remove(this);
+               }
+               openNeighbors.clear();
+
+               // Dormant nodes dont remove data
+               if (permanent) {
+                   for (Iterator<Data> it = data.data() ; it.hasNext() ;) {
+                       it.next().removedFrom(this);
+                   }
+
+                   for (Iterator<Data> it = cache.data() ; it.hasNext() ;) {
+                       it.next().removedFrom(this);
+                   }
+               }
+
+               return n;
+       }
+
+       public boolean isSink(CircleKey k) {
+               for (DarknetNode n : neighbors) {
+                       if (k.dist(n.pos) < k.dist(pos))
+                               return false;
+               }
+               return true;
+       }
+
+       public boolean hasData(CircleKey k) {
+               return data.contains(k) || cache.contains(k);
+       }
+
+       public Data findData(CircleKey k) {
+               Data cached = cache.get(k);
+
+               return (cached != null) ? cached : data.get(k);
+       }
+
+       /**
+        * Always store in cache
+        * @sink: Whether to store in the long-term storage
+        */
+         
+       public void storeData(Data<CircleKey> d, boolean sink) {
+           if (sink) {
+               if (!data.contains(d.key()))
+                   d.addedTo(this);
+               Data r = data.put(d);
+               if (r != null)
+                       r.removedFrom(this);
+           }
+
+           if (!cache.contains(d.key()))
+               d.addedTo(this);
+           Data r = cache.put(d);
+           if (r != null)
+               r.removedFrom(this);
+
+       }
+
+       /**
+        * Stores at this node, and depth generation neighbors.
+        */
+       public void explosiveStore(Data d, int depth) {
+               storeData(d, true); // Default option to put into store/cache
+               if (depth > 0) {
+                       for (Iterator<DarknetNode> it = neighbors.iterator() ; 
it.hasNext() ;) {
+                               it.next().explosiveStore(d, depth - 1);
+                       }
+               }
+       }
+
+
+       public Iterator<DarknetNode> neighbors() {
+               return new DoubleIterator<DarknetNode>(neighbors.iterator(), 
+                               openNeighbors.iterator());
+       }
+
+       public Person person() {
+               return user;
+       }
+
+       // Get random node in the network (to later perform swap attempt)
+       protected DarknetNode getRandomNode() {
+               if (neighbors.size() == 0)
+                       return null; // this; using null to cause crash because 
this is
+                                                       // bad.
+
+               DarknetNode curr = this;
+               for (int i = 0 ; i < god.RAND_STEPS ; i++) {
+                       curr = 
curr.neighbors.get(god.random().nextInt(curr.neighbors.size()));
+               }
+               return curr;
+       }
+
+       public boolean hasDarknetNeighbor(DarknetNode dn) {
+               return neighbors_lookup.containsKey(dn);                
+       }
+
+       public boolean hasOpennetNeighbor(DarknetNode on) {
+               return open_lookup.containsKey(on);
+       }
+
+       /**
+        * Adding darknet peers may result in dropping opennet peers
+        */
+       protected void makeNeighbors(DarknetNode dn) {
+
+               if (this.needsDarknet() && dn.needsDarknet()) {
+                       if (!hasDarknetNeighbor(dn)) {
+                               neighbors.add(dn);
+                               neighbors_lookup.put(dn, null);
+                               if (isOpennet())
+                                       policyOpennet();
+                       }
+
+                       if (!dn.hasDarknetNeighbor(this)) {
+                               dn.neighbors.add(this);
+                               dn.neighbors_lookup.put(this, null);
+                               if (dn.isOpennet())
+                                       dn.policyOpennet();
+                       }
+               }
+       }
+
+       /**
+        * Add opennet connection with imposing policy
+        */
+       public void reLink(DarknetNode on) {
+               if (!hasOpennetNeighbor(on) && !hasDarknetNeighbor(on)) {
+                       makeOpenNeighbors(on);
+                       policyOpennet();
+                       on.policyOpennet();
+               }
+       }
+
+       protected void makeOpenNeighbors(DarknetNode on) {
+               if (!hasDarknetNeighbor(on)) {                  // Protection 
against dual darknet/opennet peers
+                       if (!hasOpennetNeighbor(on)) {
+                               openNeighbors.add(on);
+                               open_lookup.put(on, null);
+                       }
+                       
+                       if (!on.hasOpennetNeighbor(this)) {
+                               on.openNeighbors.add(this);
+                               on.open_lookup.put(this, null);
+                       }
+               }
+       }
+
+       protected void removeNeighbor(DarknetNode dn) {
+               neighbors.remove(dn);
+               neighbors_lookup.remove(dn);
+               dn.neighbors.remove(this);
+               dn.neighbors_lookup.remove(this);
+       }
+
+       protected void removeOpenNeighbor(DarknetNode on) {
+               openNeighbors.remove(on);
+               open_lookup.remove(on);
+               on.openNeighbors.remove(this);
+               on.open_lookup.remove(this);
+       }
+
+       /**
+        * Calculates the log distance to the neighbors of this node from 
newpos. If
+        * a neighbor has position newpos, then it is given my current position.
+        */
+       private double logdist(CircleKey newpos) {
+               double val = 0.0f;
+               for (Iterator<DarknetNode> it = neighbors.iterator() ; 
it.hasNext() ;) {
+                       DarknetNode dn = it.next();
+                       val += Math.log(dn.pos == newpos ? pos.dist(newpos) : 
+                               dn.pos.dist(newpos));
+               }
+               return val;
+       } 
+
+
+       public int fieldnum() {return 7;}
+
+       public String[] fields() {
+               return new 
String[]{"Num","Position","DarkDegree","OpenDegree","Queries", "Data", 
"LogDist"};
+       }
+
+       public String[] info() {
+               return new String[] {
+                               Integer.toString(num), pos.stringVal(), 
+                               Integer.toString(neighbors.size()), 
Integer.toString(openNeighbors.size()),
+                               Integer.toString(numQueries), 
Integer.toString(data.size()),
+                               Double.toString(logdist(pos))
+               };
+       }
+
+       public String toString() {
+               return "DN#" + num + " (" + pos + ")";
+       }
+
+
+       /**
+        * Feeds an aggregator called "LinkDistances" with the keyspace 
distance to
+        * each of my long links. Feeds an aggregator called "NodeTraffic" with 
the
+        * number of queries that passed me.
+        */
+       public void feedAggregator(DataAggregator da) {
+               super.feedAggregator(da);
+
+               if (da.name().equals("LinkDistances")) {
+                       for (DarknetNode n : neighbors)
+                               da.next(pos.dist(n.pos));
+               } else if (da.name().equals("NodeTraffic")) {
+                       da.next(numQueries);
+               } 
+       }
+
+
+
+       private class DoubleIterator<T> implements Iterator<T> {
+
+               private Iterator<T> first;
+               private Iterator<T> second;
+               private boolean f;
+
+               public DoubleIterator(Iterator<T> first, Iterator<T> second) {
+                       this.first = first;
+                       this.second = second;
+
+                       f = first.hasNext();            
+               }
+
+               public T next() {
+                       if (f) {
+                               T r = first.next();
+                               f = first.hasNext();
+                               return r;
+                       } else {
+                               return second.next();
+                       }
+               }
+
+               public boolean hasNext() {
+                       return f || second.hasNext();
+               }
+
+               public void remove() {
+                       if (f)
+                               first.remove();
+                       else
+                               second.remove();
+               }
+
+       }
+
+}

Added: trunk/apps/simsalabim/DarknetRoute.java
===================================================================
--- trunk/apps/simsalabim/DarknetRoute.java                             (rev 0)
+++ trunk/apps/simsalabim/DarknetRoute.java     2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,184 @@
+// The representation of a route
+// 
+// A route is represented by all the nodes passed from source to
+// destination, and then the sequence of nodes returning to source. 
+// This means that backtracking may be an explicit part of a route to the 
destination,
+// as well as the path from the target back to the source.
+//
+// Most operations here (such as storage) assume a successful route to work 
correctly
+
+package simsalabim;
+import simsalabim.utils.*;
+import java.util.*;
+
+
+public class DarknetRoute implements Iterable<DarknetNode>, 
Contains<DarknetNode> {
+
+       LinkedList<DarknetNode> route = new LinkedList<DarknetNode>();
+       HashMap<DarknetNode,Boolean> visited = new 
HashMap<DarknetNode,Boolean>(); // Space-Time tradeoff for DarknetNode routing
+
+       double closest = Double.MAX_VALUE;
+       int stepsSince = 0; // Obsolete
+       int htl = 0;
+
+       DarknetNode bestNodes[];
+       
+       private int nBest;
+       
+       public DarknetRoute(int nBest, int htl) {
+               this.nBest = nBest;
+               this.htl = htl;
+               bestNodes = new DarknetNode[nBest];
+       }
+       
+       public void atNode(DarknetNode dn, CircleKey k) {
+               route.add(dn);
+               visited.put(dn,null);
+               htl--;                     // Another hop for this route
+
+               int n = -1;
+               double md = 0;
+               for (int i = 0 ; i < nBest ; i++) {
+                       if (bestNodes[i] == null) {
+                               n = i;
+                               break;
+                       }
+                       double cd = k.dist(bestNodes[i].pos);
+                       if (cd > md) {
+                               n = i;
+                               md = cd;
+                       }
+               }
+
+               if (n >= 0 && (bestNodes[n] == null ||  
+                               k.dist(bestNodes[n].pos) > k.dist(dn.pos))) {
+                       bestNodes[n] = dn;
+               }
+
+       }
+
+
+       public int size() {
+               return route.size();
+       }
+
+       public Iterator<DarknetNode> iterator() {
+               return route.iterator();
+       }
+
+       public boolean contains(DarknetNode dn) {
+               // return route.contains(dn);
+               return visited.containsKey(dn);
+       }
+
+       public Data findData(CircleKey k) {
+               for (Iterator<DarknetNode> it = route.iterator() ; it.hasNext() 
;) {
+                       Data d  = it.next().findData(k);
+                       if (d != null)
+                               return d;
+               }
+               return null;
+       }
+
+       //public void storeData(Data d) {
+       //      for (Iterator<DarknetNode> it = route.iterator() ; it.hasNext() 
;) {
+       //              it.next().storeData(d);
+       //      }
+       //}
+
+       /**
+        * Store data in the route (always in cache, maybe also permanent)
+        *
+        * @d: data
+        * @sink: to attempt storage in sink
+        * @full: to store in the full (forward) path, else only in the return 
path
+        */
+       
+       public int storeData(Data<CircleKey> d, boolean sink, boolean full) {
+
+               int nsink = 0;
+
+               LinkedList<DarknetNode> target;
+               if (full)
+                  target = route;
+               else
+                  target = retpath();
+
+               for (DarknetNode n : target) {
+                       if (sink && n.isSink(d.key())) {
+                               n.storeData(d, true);
+                               nsink++;
+                       }
+                       else
+                               n.storeData(d, false);
+               }
+
+               return nsink;
+       }
+
+       //public void storeBest(Data d) {
+       //      for (int i = 0 ; i < nBest ; i++) {
+       //              if (bestNodes[i] != null)
+       //                      bestNodes[i].storeData(d);
+       //      }
+       //}
+
+       /**
+        * ASSUMES: a successful query route (source ... dest ... source)
+        *
+        * Returns the return path (dest to source)
+        */
+
+       public LinkedList<DarknetNode> retpath() {
+           LinkedList<DarknetNode> retpath = new LinkedList<DarknetNode>();
+
+           /* Obtain the return path */
+           ListIterator<DarknetNode> li = route.listIterator(route.size());
+           while (li.hasPrevious()) {
+               DarknetNode n = li.previous();
+               if (retpath.contains(n))
+                   break;
+               retpath.addFirst(n);
+           }
+           return retpath;
+       }
+
+
+       /**
+        * Relinks nodes open neighbors to one of the best nodes with given
+        * probability, if possible.
+        * ASSUMES: that this DarknetRoute contains WHOLE PATH (source ... dest 
... source)
+        */
+       public void reLink(RandomEngine re, float prob, float rewrite_prob) {
+               LinkedList<DarknetNode> retpath = retpath();
+
+               // Each node, when it wants to, may add its own ref to a 
back-propagating request
+
+               DarknetNode target = (retpath.getFirst().isOpennet() && 
retpath.getFirst().needsOpennet()) ? retpath.getFirst() : null;
+               ListIterator<DarknetNode> retitr = retpath.listIterator(1);
+               while (retitr.hasNext()) {
+                     DarknetNode current = retitr.next();
+                     if (!current.isOpennet() || !current.needsOpennet())
+                        continue;
+
+                     if (target == null) {
+                             target = current;         // Always seek 
connections
+                             continue;
+                     }
+                     else if (re.nextFloat() < rewrite_prob) { // Maybe steal 
the reference (as in Freenet 0.7)
+                             target = current;
+                     }
+                     else if (re.nextFloat() < prob) {
+                        current.reLink(target);
+
+                        // Maybe propagate ref backwards
+                        if (current.needsOpennet()) {
+                           target = current;
+                        } else {
+                           target = null;
+                        }
+
+                     }
+               }
+       }
+}

Added: trunk/apps/simsalabim/Data.java
===================================================================
--- trunk/apps/simsalabim/Data.java                             (rev 0)
+++ trunk/apps/simsalabim/Data.java     2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,113 @@
+package simsalabim;
+
+public class Data<K extends Key> implements InfoObject {
+
+    private long size;
+    private K key;
+
+    private long createTime;
+    private long placeTime = -1;
+    private long deathTime = -1;
+    private long forgetTime = -1;
+
+    private int storeCount = 0;
+    private int retrieveCount = 0;
+
+    // For network partitioning
+    private int sourceNetwork = 0;
+
+    public Data(Simulator god, K key, long size) {
+       this(key, size, god.time());
+    }
+
+    public Data(K key, long size, long createTime) {
+        this.key = key;
+        this.size = size;
+        this.createTime = createTime;
+    }
+
+    public K key() {
+        return key;
+    }
+
+    public long size() {
+        return size;
+    }
+
+    public void addedTo(Node n) {
+       if (deathTime != -1)
+               System.err.println("Added to node although deathtime set " + 
this);
+        if (placeTime == -1) {
+            if (storeCount != 0)
+                throw new SimulationException("Storage Count of data object " +
+                                              "not zero on insert!");
+            placeTime = n.time();
+        }
+        storeCount++;
+    }
+
+    public void removedFrom(Node n) {
+        if (storeCount == 0)
+            throw new SimulationException("Data removed more times than stored"
+                                          + ". Node: " + n);
+        storeCount--;
+        
+        if (storeCount == 0)
+            deathTime = n.time();
+    }
+    
+    public void forgotten(long time) {
+       this.forgetTime = time;
+    }
+    
+    public void retrieved() {
+       this.retrieveCount++;
+    }
+
+    public int fieldnum() {
+        return 8;
+    }
+
+    public void sourceNetwork(int id) {
+           sourceNetwork = id;
+    }
+
+    public int sourceNetwork() {
+           return sourceNetwork;
+    }
+
+    // Info fields
+    private static final String[] flds = {
+        "Key",
+        "Size",
+        "Created",
+        "Placed",
+        "Died",
+        "Forgotten",
+        "Node Count",
+        "RetrieveCount"
+    };
+
+    public String[] fields() {
+        return flds;
+    }
+
+    public String[] info() {
+        return new String[] {
+            key.stringVal(),
+            Long.toString(size),
+            Long.toString(createTime),
+            Long.toString(placeTime),
+            Long.toString(deathTime),
+            Long.toString(forgetTime),
+            Long.toString(storeCount),
+            Long.toString(retrieveCount)
+        };
+    }
+    
+    
+    public String toString() {
+        return "D/" + "K:" + key.toString() + "/S:" + size + "/";
+    }
+
+}

Added: trunk/apps/simsalabim/DataAggregator.java
===================================================================
--- trunk/apps/simsalabim/DataAggregator.java                           (rev 0)
+++ trunk/apps/simsalabim/DataAggregator.java   2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,33 @@
+package simsalabim;
+
+/**
+ * Collects data.
+ */
+public abstract class DataAggregator {
+
+       private String name;
+
+       public DataAggregator(String name) {
+               this.name = name;
+       }
+
+       public String name() {
+               return name;
+       }
+
+       public void next(int val) {
+               next((long) val);
+       }
+
+       public abstract void next(long val);
+
+       public void next(float val) {
+               next((double) val);
+       }
+
+       public abstract void next(double val);
+
+       public void complete() {
+       }
+
+}

Added: trunk/apps/simsalabim/DataSpace.java
===================================================================
--- trunk/apps/simsalabim/DataSpace.java                                (rev 0)
+++ trunk/apps/simsalabim/DataSpace.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,20 @@
+package simsalabim;
+import java.util.*;
+
+public interface DataSpace<K extends Key> {
+
+
+    // public Data<K> newData(Simulator<?, K> god, K key);
+    public Data<K> newData(Simulator god, K key);
+
+    public Data<K> getData(K key);
+
+
+    public Iterator<Data<K>> allData();
+
+    public K chooseKey(RandomEngine re);
+    
+    public int size();
+    
+    public String info();
+}

Added: trunk/apps/simsalabim/EventLogger.java
===================================================================
--- trunk/apps/simsalabim/EventLogger.java                              (rev 0)
+++ trunk/apps/simsalabim/EventLogger.java      2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,40 @@
+package simsalabim;
+
+/**
+ * En efficient system for output from the simulator.
+ */
+public abstract class EventLogger {
+
+       public abstract void nodeJoined(long time, Node n);
+
+       public abstract void nodeJoinError(long time, Node n, int error);
+
+       public abstract void dataPlaced(long time, Data d);
+
+       public abstract void dataPlaceError(long time, Data d, int error);
+
+       public abstract void dataFound(long time, Data d);
+
+       public abstract void dataNotFound(long time, Key k, int error);
+
+       public abstract void dataFindError(long time, Key k, int error);
+
+       public abstract void warning(long time, int error);
+
+       public abstract void message(long time, String message);
+
+       public String error(int errnum) {
+               if (errnum >= errors.length)
+                       return "Unknown";
+               else
+                       return errors[errnum];
+       }
+
+       private static final String[] errors = { "Boohoo, I lost my mommy.", // 
1
+                       "Iceage happened.", // 2
+                       "Oskar couldn't code to save his life.", // 3
+                       "Route established, but could not find data.", // 4
+                       "Routing failed." // 5
+       };
+
+}

Added: trunk/apps/simsalabim/Feedback.java
===================================================================
--- trunk/apps/simsalabim/Feedback.java                         (rev 0)
+++ trunk/apps/simsalabim/Feedback.java 2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,20 @@
+package simsalabim;
+
+/**
+ * A simple interface whereby a task can give feedback on it's process for
+ * study.
+ */
+public interface Feedback {
+
+       /**
+        * @param task
+        *            The task that has been run (@see RunTask for numbers).
+        * @param success
+        *            The success state.
+        * @param cost
+        *            The cost of the task (typically the number of nodes 
involved)
+        * @see RunTask
+        */
+       public abstract void feedback(int task, boolean success, double[] msg);
+
+}

Added: trunk/apps/simsalabim/HBuildTask.java
===================================================================
--- trunk/apps/simsalabim/HBuildTask.java                               (rev 0)
+++ trunk/apps/simsalabim/HBuildTask.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,231 @@
+package simsalabim;
+
+/**
+ * Task which builds a simulates a living network.
+ */
+
+public class HBuildTask extends RunTask {
+
+       private double joinRate;
+
+       private double leaveRate;
+
+       private double searchRate;
+
+       private double placeRate;
+
+       private double maintRate;
+
+       private long poolSize;
+
+       private long tsearches = 0;
+
+       private long ssearches = 0;
+
+       private long tplaces = 0;
+
+       private long splaces = 0;
+
+       private long tmaint = 0;
+
+       private long smaint = 0;
+
+       private long tjoin = 0;
+
+       private long tleave = 0;
+
+       private long tsearchcost = 0;
+
+       private long ssearchcost = 0;
+
+       private long tplacecost = 0;
+
+       private long splacecost = 0;
+
+       private long tmaintcost = 0;
+
+       private long tdualmaint = 0;
+       private long sdualmaint = 0;
+       private long sroutesteps = 0;   
+       private long tlongsearches = 0;
+       private long slongsearches = 0;
+       private long tshortsearches = 0;
+       private long sshortsearches = 0;
+
+       /**
+        * The rates are per node per clocktick. They should be low (the sum of 
all
+        * the rates times poolSize should be less than << 1.
+        * 
+        * @param runTill
+        *            Run until this clockbeat
+        * @param searchRate
+        *            The rate at which each active node initiates searches
+        * @param placeRate
+        *            The rate at which each active node initiates placings.
+        * @param poolSize
+        *            The size of the pool of available nodes.
+        * @param joinRate
+        *            The rate at which each inactive node joins.
+        * @param leaveRate
+        *            The rate at which each
+        * @param maintRate
+        *            The rate at which to run maintenance.
+        */
+       public HBuildTask(long runTill, double searchRate, double placeRate,
+                       int poolSize, double joinRate, double leaveRate, double 
maintRate) {
+
+               super(runTill);
+
+               this.searchRate = searchRate;
+               this.placeRate = placeRate;
+               this.poolSize = poolSize;
+               this.joinRate = joinRate;
+               this.leaveRate = leaveRate;
+               this.maintRate = maintRate;
+
+       }
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public HBuildTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               // System.err.println(Main.tabbed(s));
+
+               if (s.length < 7)
+                       throw new ScriptException("BuildTask requires seven 
parameters, not "
+                                       + s.length);
+
+               this.searchRate = Double.parseDouble(s[1]);
+               this.placeRate = Double.parseDouble(s[2]);
+
+               this.poolSize = Long.parseLong(s[3]);
+               this.joinRate = Double.parseDouble(s[4]);
+               this.leaveRate = Double.parseDouble(s[5]);
+               this.maintRate = Double.parseDouble(s[6]);
+
+               System.err.println("HBuildTask having joinRate " + joinRate + " 
and leaveRate " + leaveRate);
+
+               // System.err.println(searchRate + " " + placeRate);
+
+       }
+
+       public void started(long time, EventLogger ev) {
+               ev.message(time, "HBuildTask Started. Poolsize " + poolSize
+                               + " join/leave rate: " + joinRate + "/" + 
leaveRate
+                               + ". Insert/request rate: " + placeRate + "/" + 
searchRate
+                               + ". Maintenance rate: " + maintRate);
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "HBuildTask Complete. " + tsearches + " 
searches ("
+                               + ((float) ssearches) / tsearches + " succ, "
+                          + ((double) tsearchcost) / tsearches + " cost" + ", 
" + ((double)sroutesteps) / ssearches + " costsucc)." + tplaces
+                               + " places (" + ((float) splaces) / tplaces + " 
succ, "
+                               + ((double) tplacecost) / tplaces + " cost)." + 
tjoin
+                               + " joins, " + tleave + " leave," + tmaint
+                          + " maintenance tasks with " + tmaintcost + " 
attempts (" + (smaint) + "succ)."
+                          + "Dual: " + tdualmaint + " crossed, " + sdualmaint 
+ " succeeded."
+                          + "[" + ((float) smaint/tmaintcost) + ", " + 
((float) sdualmaint/tdualmaint) + "]");
+               ev.message(time, "HBuildTask PARTITION: \t" + tshortsearches + 
" " + sshortsearches + " " + tlongsearches + " " + slongsearches + "(" + 
((double)sshortsearches/tshortsearches) + "," + 
((double)slongsearches/tlongsearches) + ")" );
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(Simulator s) {
+               RandomEngine re = s.random();
+
+               long joinLeft = Math.max(0, poolSize - s.netSize());
+               int netSize = s.netSize();
+
+               double rate = joinLeft * joinRate + netSize
+                               * (searchRate + placeRate + leaveRate + 
maintRate);
+               // try { Thread.sleep(100); } catch (Exception e) {}
+               // System.err.println("rate: " + rate + " netsize: " + netSize);
+
+               long t = Math.max((long) (Math.log(re.nextDouble()) / (rate * 
-1)), 1);
+               System.err.print("t = " + t + " rate = " + rate + ", ");
+               return t;
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(Simulator s) {
+               RandomEngine re = s.random();
+               long joinLeft = Math.max(0, poolSize - s.netSize());
+               int netSize = s.netSize();
+               int maintSize = s.maintSize();
+
+               double joinP = joinLeft * joinRate;
+               double leaveP = netSize * leaveRate;
+               double searchP = netSize * searchRate;
+               double placeP = netSize * placeRate;
+               double maintP = maintSize * maintRate;
+
+               double norm = joinP + searchP + placeP + maintP + leaveP;
+               double val = re.nextDouble() * norm;
+
+               if (val <= joinP) {
+                       return JOIN;
+               } else if (val <= searchP + joinP) {
+                       return SEARCH;
+               } else if (val <= searchP + joinP + placeP) {
+                       return PLACE;
+               } else if (val <= searchP + joinP + placeP + maintP) {
+                       return MAINTENANCE;
+               } else {
+                       return LEAVE;
+               }
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               int cost = (int) costarr[0];
+
+               if (task == SEARCH) {
+                       tsearches++;
+                       ssearches += success ? 1 : 0;
+                       tsearchcost += cost;
+                       sroutesteps += costarr[1];
+                       
+                       int srcNet = (int) costarr[2];
+                       int origNet = (int) costarr[3];
+                       if (srcNet>0 && origNet>0) {
+                               if (srcNet != origNet) {
+                                       tlongsearches++;
+                                       slongsearches += success ? 1 : 0;
+                               } else {
+                                       tshortsearches++;
+                                       sshortsearches += success ? 1 : 0;
+                               }
+                       }
+                       
+               } else if (task == PLACE) {
+                       tplaces++;
+                       splaces += success ? 1 : 0;
+                       tplacecost += cost;
+               } else if (task == MAINTENANCE) {
+                       tmaint++;
+                       tmaintcost += cost;
+                       // Handle the case of network partitions
+                       if (costarr.length > 3) {
+                               smaint += costarr[1];
+                               tdualmaint += costarr[2];                       
                                        
+                               sdualmaint += costarr[3];
+                       }
+               } else if (task == LEAVE) {
+                       tleave++;
+               } else if (task == JOIN) {
+                       tjoin++;
+               } else if (task == DUALSTATS) {
+
+
+
+               }
+              
+       }
+
+}

Added: trunk/apps/simsalabim/HDualStats.java
===================================================================
--- trunk/apps/simsalabim/HDualStats.java                               (rev 0)
+++ trunk/apps/simsalabim/HDualStats.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,58 @@
+package simsalabim;
+
+/**
+ * Print some stats comparing dual networks in a simulator
+ */
+
+public class HDualStats extends RunTask {
+
+       int first, second;
+       static int calls;
+       double varDist1, varDist2, varDistFull, varDistMutual;
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public HDualStats(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+               calls = 0;
+               first = -1;
+               second = -1;
+               varDist1 = -1;
+               varDist2 = -1;
+               varDistFull = -1;
+       }
+
+       public void done(long time, EventLogger ev) {
+               // ev.message(time, "HDualStats Complete. " + swaps_1 + " and " 
+ swaps_2 + "swaps initiated in the networks. Swaps proposed between: " + 
cross_swaps + " of which " + cross_swaps_accepted + " were accepted.");
+
+               ev.message(time, calls + 
"\tACTIVE1\tACTIVE2\tVARDIST1\tVARDIST2\tVARDISTFULL\tVARDISTMUTUAL");
+               ev.message(time, "\t" + first + "\t" + second + "\t" +varDist1 
+"\t" + varDist2 + "\t" + varDistFull);
+
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(RandomEngine re) {
+           return 1;
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(RandomEngine re) {
+           return DUALSTATS;
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               calls++;
+               first = (int) costarr[0];
+               second = (int) costarr[1];
+               varDist1 = costarr[2];
+               varDist2 = costarr[3];
+               varDistFull = costarr[4];
+               varDistMutual = costarr[5];
+       }
+}

Added: trunk/apps/simsalabim/HJoinTask.java
===================================================================
--- trunk/apps/simsalabim/HJoinTask.java                                (rev 0)
+++ trunk/apps/simsalabim/HJoinTask.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,68 @@
+package simsalabim;
+
+/**
+ * Has a node join at every timestep (thus call with number of nodes to join 
from the underlying social network)
+ */
+
+public class HJoinTask extends RunTask {
+
+       private double joinRate;
+
+       private double mRate;
+
+       private long tjoins = 0;
+
+       private long tjoincost = 0;
+
+       private long tmaint = 0;
+
+       private long tmaintcost = 0;
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public HJoinTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               if (s.length < 1)
+                       throw new ScriptException("JoinTask requires one 
parameter, not "
+                                                 + s.length);
+
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "HJoinTask Complete. " + tjoins + " joins ("
+                               + ((float) tjoincost) / tjoins + " cost)." + 
tmaint
+                               + " maintenance.");
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(RandomEngine re) {
+           return 1;
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(RandomEngine re) {
+           return JOIN;
+           /* return re.nextDouble() < (joinRate / (joinRate + mRate)) ? JOIN
+                               : MAINTENANCE;
+           */
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               int cost = (int) costarr[0];
+
+               if (task == JOIN) {
+                       tjoins++;
+                       tjoincost += cost;
+               } else if (task == MAINTENANCE) {
+                       tmaint++;
+                       tmaintcost += cost;
+               }
+       }
+}
\ No newline at end of file

Added: trunk/apps/simsalabim/HammingKey.java
===================================================================
--- trunk/apps/simsalabim/HammingKey.java                               (rev 0)
+++ trunk/apps/simsalabim/HammingKey.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,65 @@
+package simsalabim;
+
+public class HammingKey extends PositionKey<HammingKey> {
+
+       public static int BITS_USED = 12;
+       
+       public static KeyFactory<HammingKey> getFactory() {
+               return new KeyFactory<HammingKey>() {
+                       public HammingKey newKey(long val) {
+                               return new HammingKey(val);
+                       }
+               };
+       }
+       
+       
+       private int val;
+       
+       public HammingKey(long val) {
+               this.val = (int) val;
+       }
+
+       @Override
+       public double dist(HammingKey pk) {
+               int df = pk.val ^ val;
+               int bts = 0;
+               for (int i = 0 ; i < BITS_USED ; i++) {
+                       if((df & 1) > 0)
+                               bts++;
+                       df >>= 1;
+               }
+               return bts / 64.0;
+       }
+
+       public double ident() {
+               return (double) val;
+       }
+
+       public String stringVal() {
+               return "HK" + Long.toHexString(val);
+       }
+       
+       public static void main(String[] args) {
+               java.util.Random r = new java.util.Random();
+               HammingKey k1 = new HammingKey(r.nextLong());
+               for (int i = 0 ; i < 10 ; i++) {
+                       System.err.println("----");     
+                       HammingKey k2 = new HammingKey(r.nextLong());
+                       System.err.println(toBString(k1.val));
+                       System.err.println(toBString(k2.val));
+                       System.err.println(toBString(k1.val ^ k2.val));
+                       System.err.println(k1.dist(k2));
+               }
+       }
+
+       private static String toBString(long n) {
+               StringBuffer sb = new StringBuffer();
+               for (int i = 0 ; i < 32 ; i++) {
+                       sb.append(n & 1);
+                       n >>= 1;
+                       // System.err.println("t" + n + "\t" + 
Integer.toBinaryString(n));
+               }
+               return sb.reverse().toString();
+       }
+       
+}

Added: trunk/apps/simsalabim/HistDataAggregator.java
===================================================================
--- trunk/apps/simsalabim/HistDataAggregator.java                               
(rev 0)
+++ trunk/apps/simsalabim/HistDataAggregator.java       2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,121 @@
+package simsalabim;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class HistDataAggregator extends DataAggregator {
+
+       private int[] hist;
+
+       private double start;
+
+       private double step;
+
+       private double end;
+
+       private PrintStream out;
+
+       private String fname;
+
+       public HistDataAggregator(String name, double start, double step, 
double end) {
+               super(name);
+               initHist(start, step, end);
+               out = System.out;
+       }
+
+       public HistDataAggregator(String name, double start, double step,
+                       double end, String fname) throws ScriptException {
+               super(name);
+               initHist(start, step, end);
+               this.fname = fname;
+       }
+
+       private void initHist(double start, double step, double end) {
+               this.start = start;
+               this.step = step;
+               this.end = end;
+
+               hist = new int[((int) ((end - start) / step))];
+       }
+
+       public void next(long data) {
+               next((double) data);
+       }
+
+       public void next(double data) {
+               if (data < start || data >= end) {
+                       System.err.println("WARNING: HIST DATA OUT OF BOUNDS: 
!" + start
+                                       + " < " + data + " < " + end);
+               }
+
+               hist[(int) Math.ceil((data - start) / step) - 1]++; // not so 
nice
+       }
+
+       public void complete() {
+               PrintStream ps;
+
+               if (fname != null) {
+                       try {
+                               ps = new PrintStream(new 
FileOutputStream(fname));
+                       } catch (IOException e) {
+                               throw new SimulationException("File error : " + 
e);
+                       }
+               } else {
+                       ps = this.out;
+               }
+
+               print(ps);
+
+               if (fname != null)
+                       ps.close();
+       }
+
+       public void print(PrintStream ps) {
+               ps.println("#Created by Simsalabim. Data: " + name());
+               ps.println("#name: " + name() + "_y");
+               ps.println("#type: matrix");
+               ps.println("#rows: " + hist.length);
+               ps.println("#columns: 1");
+
+               for (int i = 0; i < hist.length; i++) {
+                       ps.println(hist[i]);
+               }
+
+               ps.println("#Created by Simsalabim. Data: " + name());
+               ps.println("#name: " + name() + "_x");
+               ps.println("#type: matrix");
+               ps.println("#rows: " + hist.length);
+               ps.println("#columns: 1");
+
+               for (int i = 0; i < hist.length; i++) {
+                       ps.println(start + step / 2.0 + i * step);
+               }
+       }
+
+       public void print(PrintWriter pw) {
+               pw.println("#Created by Simsalabim. Data: " + name());
+               pw.println("#name: " + name() + "_y");
+               pw.println("#type: matrix");
+               pw.println("#rows: " + hist.length);
+               pw.println("#columns: 1");
+
+               for (int i = 0; i < hist.length; i++) {
+                       pw.println(hist[i]);
+               }
+
+               pw.println("#Created by Simsalabim. Data: " + name());
+               pw.println("#name: " + name() + "_x");
+               pw.println("#type: matrix");
+               pw.println("#rows: " + hist.length);
+               pw.println("#columns: 1");
+
+               for (int i = 0; i < hist.length; i++) {
+                       pw.println(start + step / 2.0 + i * step);
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/InfoCommand.java
===================================================================
--- trunk/apps/simsalabim/InfoCommand.java                              (rev 0)
+++ trunk/apps/simsalabim/InfoCommand.java      2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,60 @@
+package simsalabim;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import java.io.*;
+
+public class InfoCommand implements ScriptCommand {
+
+       String type;
+       String filename;
+       
+       public InfoCommand(String[] st) {
+               type = st[0];
+               filename = st[1];
+       }
+       
+       
+       public void execute(Simulator s) throws ScriptException {
+               try {
+                       PrintWriter pw = new PrintWriter("-".equals(filename) ? 
System.err : new FileOutputStream(filename));
+                       if (type.equals("nodes")) { 
+                               pw.println("#" + s.info());
+                               writeInfo(s.allNodes(), pw);
+                       } else if (type.equals("data")) {
+                               pw.println("#" + s.ds.info());
+                               writeInfo(s.ds.allData(), pw);
+                       } else {
+                               System.err.println("Can't output data about: " 
+ type);
+                       }
+                       pw.flush();
+               } catch (IOException e) {
+                       throw new ScriptException("Error writing info: " + e);
+               }
+       }
+
+
+       public static void writeInfo(Iterator<? extends InfoObject> i, 
PrintWriter pw) {
+               if (i.hasNext()) {
+                       InfoObject io = i.next();
+
+                       pw.println("#" + tabbed(io.fields()));
+                       pw.println(tabbed(io.info()));
+
+                       while (i.hasNext()) {
+                               io = i.next();
+                               pw.println(tabbed(io.info()));
+                       }
+               }
+       }
+
+       private static String tabbed(String[] s) {
+               if (s.length < 1)
+                       return "";
+               StringBuffer sb = new StringBuffer(s[0]);
+               for (int i = 1 ; i < s.length ; i++) {
+                       sb.append('\t').append(s[i]);
+               }
+               return sb.toString();
+       }
+       
+}

Added: trunk/apps/simsalabim/InfoObject.java
===================================================================
--- trunk/apps/simsalabim/InfoObject.java                               (rev 0)
+++ trunk/apps/simsalabim/InfoObject.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,19 @@
+package simsalabim;
+
+public interface InfoObject {
+
+       /**
+        * The number of fields. Must be the same for all objects of a 
particular
+        * type.
+        */
+       public int fieldnum();
+
+       /**
+        * The names of the fields. Must be the same for all objects of a 
particular
+        * type.
+        */
+       public String[] fields();
+
+       public String[] info();
+
+}

Added: trunk/apps/simsalabim/JoinTask.java
===================================================================
--- trunk/apps/simsalabim/JoinTask.java                         (rev 0)
+++ trunk/apps/simsalabim/JoinTask.java 2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,65 @@
+package simsalabim;
+
+/**
+ * Has a node join at a given rate.
+ */
+
+public class JoinTask extends RunTask {
+
+       private double joinRate;
+
+       private long tjoins = 0;
+
+       private long tjoincost = 0;
+
+       public JoinTask(long runTill, double joinRate) {
+
+               super(runTill);
+
+               this.joinRate = joinRate;
+       }
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public JoinTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               if (s.length < 2)
+                       throw new ScriptException("JoinTask requires two 
parameters, not "
+                                       + s.length);
+               this.joinRate = Double.parseDouble(s[1]);
+
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "JoinTask Complete. " + tjoins + " joins ("
+                               + ((float) tjoincost) / tjoins + " cost).");
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(RandomEngine re) {
+               return Math
+                               .max((long) (Math.log(re.nextDouble()) / 
(joinRate * -1)), 1);
+
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(RandomEngine re) {
+               return JOIN;
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               int cost = (int) costarr[0];
+               if (task == JOIN) {
+                       tjoins++;
+                       tjoincost += cost;
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/Key.java
===================================================================
--- trunk/apps/simsalabim/Key.java                              (rev 0)
+++ trunk/apps/simsalabim/Key.java      2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,25 @@
+package simsalabim;
+
+/**
+ * A note about keys: Keys are assumed to be identified by reference. Do not 
use
+ * two objects for the same key, much code will treat them as different!
+ */
+public interface Key {
+
+       /**
+        * Returns a floating point value designating the nodes position in the
+        * keyspace.
+        */
+       public double ident();
+
+       /**
+        * Key value expressed in it's natural form as a string.
+        */
+       public String stringVal();
+
+       /**
+        * It is a good idea that keys have these.
+        */
+       public int hashCode();
+
+}

Added: trunk/apps/simsalabim/KeyFactory.java
===================================================================
--- trunk/apps/simsalabim/KeyFactory.java                               (rev 0)
+++ trunk/apps/simsalabim/KeyFactory.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,36 @@
+package simsalabim;
+import java.util.Date;
+
+public abstract class KeyFactory<K extends Key> {
+
+    private static RandomEngine re = null;
+
+    /**
+        * Creates keys for a specific architecture.
+        * 
+        * @param val
+        *            A random long (64 uniform bits).
+        */
+    public abstract K newKey(long val);
+
+    /**
+        * Creates keys for a specific architecture.
+        * 
+        * @param re
+        *            Entropy source
+        */
+    public K newKey(RandomEngine re) {
+        return newKey(re.nextLong());
+    }
+
+    /**
+        * Creates keys for specific architecture.
+        */
+    public K newKey() {
+        if (re == null)
+            re = new MersenneTwister(new Date());
+        return newKey(re);
+    }
+
+
+}

Added: trunk/apps/simsalabim/LimitedDataSpace.java
===================================================================
--- trunk/apps/simsalabim/LimitedDataSpace.java                         (rev 0)
+++ trunk/apps/simsalabim/LimitedDataSpace.java 2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,74 @@
+package simsalabim;
+import java.util.*;
+
+public class LimitedDataSpace<K extends Key> implements DataSpace<K> {
+
+
+       private HashMap<K, Data<K>> datamap;
+       private Vector<Data<K>> datav;
+       private Vector<Data<K>> forgotten;
+
+       private long minsize, sizerange;
+       private long maxdocs;
+       
+       private long currSize = 0;
+
+       public LimitedDataSpace(int maxdocs, long minsize, long maxsize) {
+               this.minsize = minsize;
+               this.sizerange = maxsize - minsize;
+               this.maxdocs = maxdocs;
+               
+               datamap = new HashMap<K, Data<K>>();
+               datav = new Vector<Data<K>>(maxdocs);   
+               forgotten = new Vector<Data<K>>(maxdocs);
+       }
+
+
+       public Data<K> newData(Simulator god, K key) {
+               long size = minsize + (long) (god.random().nextDouble() * 
sizerange);
+               Data<K> d = new Data<K>(god, key, size);
+               datamap.put(key, d);
+               currSize += d.size();
+               
+               if (datav.size() < maxdocs) {
+                       datav.add(d);
+               } else {
+                       int n = god.random().nextInt(datav.size());
+                       Data<K> rm = datav.get(n);
+                       rm.forgotten(god.time());
+                       forgotten.add(rm);
+                       datamap.remove(rm.key());
+                       currSize -= rm.size();
+                       datav.set(n, d);
+               }
+
+               return d;
+       }
+
+
+       public Data<K> getData(K key) {
+               return datamap.get(key);
+       }
+
+       public Iterator<Data<K>> allData() {
+               return datamap.values().iterator();
+       }
+
+       public int size() {
+               return datav.size();
+       }
+       
+       public String info() {
+               return "LimitedDataSpace docs: " + datav.size() + " (of " + 
maxdocs + ") bytes: " + currSize; 
+       }
+       
+       /**
+        * Currently uniform.
+        */
+       public K chooseKey(RandomEngine re) {
+               if (datav.isEmpty())
+                       return null;
+               return datav.get(re.nextInt(datav.size())).key();
+       }
+
+}

Added: trunk/apps/simsalabim/MBuildTask.java
===================================================================
--- trunk/apps/simsalabim/MBuildTask.java                               (rev 0)
+++ trunk/apps/simsalabim/MBuildTask.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,189 @@
+package simsalabim;
+
+/**
+ * Task which builds a simulates a living network.
+ */
+
+public class MBuildTask extends RunTask {
+
+       private double joinRate;
+
+       private double leaveRate;
+
+       private double searchRate;
+
+       private double placeRate;
+
+       private double maintRate;
+
+       private long poolSize;
+
+       private long tsearches = 0;
+
+       private long ssearches = 0;
+
+       private long tplaces = 0;
+
+       private long splaces = 0;
+
+       private long tmaint = 0;
+
+       private long tjoin = 0;
+
+       private long tleave = 0;
+
+       private long tsearchcost = 0;
+
+       private long ssearchcost = 0;
+
+       private long tplacecost = 0;
+
+       private long splacecost = 0;
+
+       private long tmaintcost = 0;
+
+       /**
+        * The rates are per node per clocktick. They should be low (the sum of 
all
+        * the rates times poolSize should be less than << 1.
+        * 
+        * @param runTill
+        *            Run until this clockbeat
+        * @param searchRate
+        *            The rate at which each active node initiates searches
+        * @param placeRate
+        *            The rate at which each active node initiates placings.
+        * @param poolSize
+        *            The size of the pool of available nodes.
+        * @param joinRate
+        *            The rate at which each inactive node joins.
+        * @param leaveRate
+        *            The rate at which each
+        * @param maintRate
+        *            The rate at which to run maintenance.
+        */
+       public MBuildTask(long runTill, double searchRate, double placeRate,
+                       int poolSize, double joinRate, double leaveRate, double 
maintRate) {
+
+               super(runTill);
+
+               this.searchRate = searchRate;
+               this.placeRate = placeRate;
+               this.poolSize = poolSize;
+               this.joinRate = joinRate;
+               this.leaveRate = leaveRate;
+               this.maintRate = maintRate;
+
+       }
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public MBuildTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               // System.err.println(Main.tabbed(s));
+
+               if (s.length < 6)
+                       throw new ScriptException("BuildTask requires six 
parameters, not "
+                                       + s.length);
+
+               this.searchRate = Double.parseDouble(s[1]);
+               this.placeRate = Double.parseDouble(s[2]);
+
+               this.poolSize = Long.parseLong(s[3]);
+               this.joinRate = Double.parseDouble(s[4]);
+               this.leaveRate = Double.parseDouble(s[5]);
+               this.maintRate = Double.parseDouble(s[6]);
+
+               // System.err.println(searchRate + " " + placeRate);
+
+       }
+
+       public void started(long time, EventLogger ev) {
+               ev.message(time, "BuildTask Started. Poolsize " + poolSize
+                               + " join/leave rate: " + joinRate + "/" + 
leaveRate
+                               + ". Insert/request rate: " + placeRate + "/" + 
searchRate
+                               + ". Maintenance rate: " + maintRate);
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "BuildTask Complete. " + tsearches + " 
searches ("
+                               + ((float) ssearches) / tsearches + " succ, "
+                               + ((double) tsearchcost) / tsearches + " 
cost)." + tplaces
+                               + " places (" + ((float) splaces) / tplaces + " 
succ, "
+                               + ((double) tplacecost) / tplaces + " cost)." + 
tjoin
+                               + " joins, " + tleave + " leave," + tmaint
+                               + " maintenance tasks.");
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(Simulator s) {
+               RandomEngine re = s.random();
+
+               long joinLeft = Math.max(0, poolSize - s.netSize());
+               int netSize = s.netSize();
+
+               double rate = joinLeft * joinRate + netSize
+                               * (searchRate + placeRate + leaveRate + 
maintRate);
+               // try { Thread.sleep(100); } catch (Exception e) {}
+               // System.err.println("rate: " + rate + " netsize: " + netSize);
+               // Exponentially distributed.
+               return Math.max((long) (Math.log(re.nextDouble()) / (rate * 
-1)), 1);
+
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(Simulator s) {
+               RandomEngine re = s.random();
+               long joinLeft = Math.max(0, poolSize - s.netSize());
+               int netSize = s.netSize();
+
+               double joinP = joinLeft * joinRate;
+               double leaveP = netSize * leaveRate;
+               double searchP = netSize * searchRate;
+               double placeP = netSize * placeRate;
+               double maintP = netSize * maintRate;
+
+               double norm = joinP + searchP + placeP + maintP + leaveP;
+               double val = re.nextDouble() * norm;
+
+               if (val <= joinP) {
+                       return JOIN;
+               } else if (val <= searchP + joinP) {
+                       return SEARCH;
+               } else if (val <= searchP + joinP + placeP) {
+                       return PLACE;
+               } else if (val <= searchP + joinP + placeP + maintP) {
+                       return MAINTENANCE;
+               } else {
+                       return LEAVE;
+               }
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               int cost = (int) costarr[0];
+
+               if (task == SEARCH) {
+                       tsearches++;
+                       ssearches += success ? 1 : 0;
+                       tsearchcost += cost;
+               } else if (task == PLACE) {
+                       tplaces++;
+                       splaces += success ? 1 : 0;
+                       tplacecost += cost;
+               } else if (task == MAINTENANCE) {
+                       tmaint++;
+                       tmaintcost += cost;
+               } else if (task == LEAVE) {
+                       tleave++;
+               } else if (task == JOIN) {
+                       tjoin++;
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/MJoinTask.java
===================================================================
--- trunk/apps/simsalabim/MJoinTask.java                                (rev 0)
+++ trunk/apps/simsalabim/MJoinTask.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,78 @@
+package simsalabim;
+
+/**
+ * Has a node join at a given rate, with maintenance procedure inbetween.
+ */
+
+public class MJoinTask extends RunTask {
+
+       private double joinRate;
+
+       private double mRate;
+
+       private long tjoins = 0;
+
+       private long tjoincost = 0;
+
+       private long tmaint = 0;
+
+       private long tmaintcost = 0;
+
+       public MJoinTask(long runTill, double joinRate, double mRate) {
+               super(runTill);
+
+               this.joinRate = joinRate;
+               this.mRate = mRate;
+       }
+
+       /**
+        * String constructor for script. The values should be in the same 
order as
+        * above.
+        */
+       public MJoinTask(String[] s) throws ScriptException {
+               super(Long.parseLong(s[0]));
+
+               if (s.length < 2)
+                       throw new ScriptException("JoinTask requires two 
parameters, not "
+                                       + s.length);
+               this.joinRate = Double.parseDouble(s[1]);
+               this.mRate = Double.parseDouble(s[2]);
+       }
+
+       public void done(long time, EventLogger ev) {
+               ev.message(time, "MJoinTask Complete. " + tjoins + " joins ("
+                               + ((float) tjoincost) / tjoins + " cost)." + 
tmaint
+                               + " maintenance.");
+       }
+
+       /**
+        * Calculates the time until a task.
+        */
+       public long timeTillTask(RandomEngine re) {
+               return Math.max(
+                               (long) (Math.log(re.nextDouble()) / ((joinRate 
+ mRate) * -1)),
+                               1);
+
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above.
+        */
+       public int taskType(RandomEngine re) {
+               return re.nextDouble() < (joinRate / (joinRate + mRate)) ? JOIN
+                               : MAINTENANCE;
+       }
+
+       public void feedback(int task, boolean success, double[] costarr) {
+               int cost = (int) costarr[0];
+
+               if (task == JOIN) {
+                       tjoins++;
+                       tjoincost += cost;
+               } else if (task == MAINTENANCE) {
+                       tmaint++;
+                       tmaintcost += cost;
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/Main.java
===================================================================
--- trunk/apps/simsalabim/Main.java                             (rev 0)
+++ trunk/apps/simsalabim/Main.java     2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,93 @@
+package simsalabim;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * The Main run class. The only arguments needed to run is the name of the
+ * script file, and log file (which may be - ). Optionally a PRNG seed.
+ */
+
+public class Main {
+
+       public static void main(String[] args) {
+               if (args.length < 3) {
+                       System.err.println("Usage:  simsalabim.Main  
<scriptfile> <settings> <logfile> [prng seed]");
+                       System.exit(1);
+               }    
+
+               int seed;
+               if (args.length > 3)
+                       seed = Integer.parseInt(args[3]);
+               else
+                       seed = (int) (System.currentTimeMillis() % 100000000l);
+
+               System.err.println("Using PRNG seed: " + seed);
+
+               RandomEngine re = new MersenneTwister(seed);
+
+               Settings st = new Settings(args[1]);
+               
+               DataSpace ds = createDataSpace(st);
+
+               Script sc;
+               Simulator sim;
+               PrintWriter out;
+
+               try {
+                       BufferedReader br = new BufferedReader(new 
FileReader(args[0]));
+                       sc = new Script(br);
+                       if (args[1].equals("-"))
+                               out = new PrintWriter(new 
OutputStreamWriter(System.out));
+                       else
+                               out = new PrintWriter(new FileWriter(args[2]));
+                       EventLogger ev = new StringEventLogger(out);
+
+                       sim = sc.makeSim(re, ds, st, ev);
+
+                       long rtime = System.currentTimeMillis();
+
+                       ScriptCommand scom;
+                       while ((scom = sc.nextCommand()) != null) {
+                               System.err.println("Executing command: " + 
scom);
+                               scom.execute(sim);
+                       }
+
+                       System.err.println("Simulation ran in: " 
+                                       + (System.currentTimeMillis() - rtime) 
+ " ms.");
+
+               } catch (IOException e) {
+                       System.err.println("IO Error reading script: " + e);
+                       System.exit(1);
+                       return; // for compiler
+               } catch (ScriptException e) {
+                       System.err.println("Error in script: " + 
e.getMessage());
+                       System.exit(1);
+                       return; // for compiler
+               }
+
+               out.println(sim.info());
+               out.println(ds.info());
+
+               out.flush();
+               out.close();
+       }
+
+       private static DataSpace createDataSpace(Settings st) {
+               String name = st.get("dsType", "PopularityDataSpace");
+               if (name.equals("LimitedDataSpace")) {
+                       return new LimitedDataSpace(st.getInt("dsMaxDocs", 
10000), st.getInt(
+                                       "dsMinSize", 1000), 
st.getInt("dsMaxSize", 2000));
+                       /* /* */
+
+               } else if (name.equals("PopularityDataSpace")) {
+                       return new PopularityDataSpace(st.getInt("dsMaxDocs", 
10000), st
+                                       .getDouble("dsPopDecay", 1.0),
+                                       st.getInt("dsMinSize", 1000), 
st.getInt("dsMaxSize", 2000));
+               } else {
+                       throw new SimulationException("DataSpace type " + name
+                                       + " unknown.");
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/MersenneTwister.java
===================================================================
--- trunk/apps/simsalabim/MersenneTwister.java                          (rev 0)
+++ trunk/apps/simsalabim/MersenneTwister.java  2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,329 @@
+/*
+ Copyright 1999 CERN - European Organization for Nuclear Research.
+ Permission to use, copy, modify, distribute and sell this software and its 
documentation for any purpose 
+ is hereby granted without fee, provided that the above copyright notice 
appear in all copies and 
+ that both that copyright notice and this permission notice appear in 
supporting documentation. 
+ CERN makes no representations about the suitability of this software for any 
purpose. 
+ It is provided "as is" without expressed or implied warranty.
+ */
+// Modified for Simsalabim by oskar
+package simsalabim;
+
+import java.util.*;
+
+/**
+ * MersenneTwister (MT19937) is one of the strongest uniform pseudo-random
+ * number generators known so far; at the same time it is quick. Produces
+ * uniformly distributed <tt>int</tt>'s and <tt>long</tt>'s in the closed
+ * intervals <tt>[Integer.MIN_VALUE,Integer.MAX_VALUE]</tt> and
+ * <tt>[Long.MIN_VALUE,Long.MAX_VALUE]</tt>, respectively, as well as
+ * <tt>float</tt>'s and <tt>double</tt>'s in the open unit intervals
+ * <tt>(0.0f,1.0f)</tt> and <tt>(0.0,1.0)</tt>, respectively. The seed can
+ * be any 32-bit integer except <tt>0</tt>. Shawn J. Cokus commented that
+ * perhaps the seed should preferably be odd.
+ * <p>
+ * <b>Quality:</b> MersenneTwister is designed to pass the k-distribution test.
+ * It has an astronomically large period of 2<sup>19937</sup>-1 
(=10<sup>6001</sup>)
+ * and 623-dimensional equidistribution up to 32-bit accuracy. It passes many
+ * stringent statistical tests, including the <A
+ * HREF="http://stat.fsu.edu/~geo/diehard.html";>diehard</A> test of G.
+ * Marsaglia and the load test of P. Hellekalek and S. Wegenkittl.
+ * <p>
+ * <b>Performance:</b> Its speed is comparable to other modern generators (in
+ * particular, as fast as <tt>java.util.Random.nextFloat()</tt>). 2.5 million
+ * calls to <tt>raw()</tt> per second (Pentium Pro 200 Mhz, JDK 1.2, NT). Be
+ * aware, however, that there is a non-negligible amount of overhead required 
to
+ * initialize the data structures used by a MersenneTwister. Code like
+ * 
+ * <pre>
+ * double sum = 0.0;
+ * for (int i = 0; i &lt; 100000; ++i) {
+ *     RandomElement twister = new MersenneTwister(new java.util.Date());
+ *     sum += twister.raw();
+ * }
+ * </pre>
+ * 
+ * will be wildly inefficient. Consider using
+ * 
+ * <pre>
+ * double sum = 0.0;
+ * RandomElement twister = new MersenneTwister(new java.util.Date());
+ * for (int i = 0; i &lt; 100000; ++i) {
+ *     sum += twister.raw();
+ * }
+ * </pre>
+ * 
+ * instead. This allows the cost of constructing the MersenneTwister object to
+ * be borne only once, rather than once for each iteration in the loop.
+ * <p>
+ * <b>Implementation:</b> After M. Matsumoto and T. Nishimura, "Mersenne
+ * Twister: A 623-Dimensionally Equidistributed Uniform Pseudo-Random Number
+ * Generator", ACM Transactions on Modeling and Computer Simulation, Vol. 8, 
No.
+ * 1, January 1998, pp 3--30.
+ * <dt>More info on <A HREF="http://www.math.keio.ac.jp/~matumoto/eindex.html";>
+ * Masumoto's homepage</A>.
+ * <dt>More info on <A
+ * HREF="http://www.ncsa.uiuc.edu/Apps/CMP/RNG/www-rng.html";> Pseudo-random
+ * number generators is on the Web</A>.
+ * <dt>Yet <A HREF="http://nhse.npac.syr.edu/random";> some more info</A>.
+ * <p>
+ * The correctness of this implementation has been verified against the
+ * published output sequence <a
+ * 
href="http://www.math.keio.ac.jp/~nisimura/random/real2/mt19937-2.out";>mt19937-2.out</a>
+ * of the C-implementation <a
+ * 
href="http://www.math.keio.ac.jp/~nisimura/random/real2/mt19937-2.c";>mt19937-2.c</a>.
+ * (Call <tt>test(1000)</tt> to print the sequence).
+ * <dt> Note that this implementation is <b>not synchronized</b>.
+ * <p>
+ * <b>Details:</b> MersenneTwister is designed with consideration of the flaws
+ * of various existing generators in mind. It is an improved version of TT800, 
a
+ * very successful generator. MersenneTwister is based on linear recurrences
+ * modulo 2. Such generators are very fast, have extremely long periods, and
+ * appear quite robust. MersenneTwister produces 32-bit numbers, and every
+ * <tt>k</tt>-dimensional vector of such numbers appears the same number of
+ * times as <tt>k</tt> successive values over the period length, for each
+ * <tt>k &lt;= 623</tt> (except for the zero vector, which appears one time
+ * less). If one looks at only the first <tt>n &lt;= 16</tt> bits of each
+ * number, then the property holds for even larger <tt>k</tt>, as shown in
+ * the following table (taken from the publication cited above): <div
+ * align="center"> <table width="75%" border="1" cellspacing="0"
+ * cellpadding="0">
+ * <tr>
+ * <td width="2%"> <div align="center">n</div> </td>
+ * <td width="6%"> <div align="center">1</div> </td>
+ * <td width="5%"> <div align="center">2</div> </td>
+ * <td width="5%"> <div align="center">3</div> </td>
+ * <td width="5%"> <div align="center">4</div> </td>
+ * <td width="5%"> <div align="center">5</div> </td>
+ * <td width="5%"> <div align="center">6</div> </td>
+ * <td width="5%"> <div align="center">7</div> </td>
+ * <td width="5%"> <div align="center">8</div> </td>
+ * <td width="5%"> <div align="center">9</div> </td>
+ * <td width="5%"> <div align="center">10</div> </td>
+ * <td width="5%"> <div align="center">11</div> </td>
+ * <td width="10%"> <div align="center">12 .. 16</div> </td>
+ * <td width="10%"> <div align="center">17 .. 32</div> </td>
+ * </tr>
+ * <tr>
+ * <td width="2%"> <div align="center">k</div> </td>
+ * <td width="6%"> <div align="center">19937</div> </td>
+ * <td width="5%"> <div align="center">9968</div> </td>
+ * <td width="5%"> <div align="center">6240</div> </td>
+ * <td width="5%"> <div align="center">4984</div> </td>
+ * <td width="5%"> <div align="center">3738</div> </td>
+ * <td width="5%"> <div align="center">3115</div> </td>
+ * <td width="5%"> <div align="center">2493</div> </td>
+ * <td width="5%"> <div align="center">2492</div> </td>
+ * <td width="5%"> <div align="center">1869</div> </td>
+ * <td width="5%"> <div align="center">1869</div> </td>
+ * <td width="5%"> <div align="center">1248</div> </td>
+ * <td width="10%"> <div align="center">1246</div> </td>
+ * <td width="10%"> <div align="center">623</div> </td>
+ * </tr>
+ * </table> </div>
+ * <p>
+ * MersenneTwister generates random numbers in batches of 624 numbers at a 
time,
+ * so the caching and pipelining of modern systems is exploited. The generator
+ * is implemented to generate the output by using the fastest arithmetic
+ * operations only: 32-bit additions and bit operations (no division, no
+ * multiplication, no mod). These operations generate sequences of 32 random
+ * bits (<tt>int</tt>'s). <tt>long</tt>'s are formed by concatenating two
+ * 32 bit <tt>int</tt>'s. <tt>float</tt>'s are formed by dividing the
+ * interval <tt>[0.0,1.0]</tt> into 2<sup>32</sup> sub intervals, then
+ * randomly choosing one subinterval. <tt>double</tt>'s are formed by
+ * dividing the interval <tt>[0.0,1.0]</tt> into 2<sup>64</sup> sub
+ * intervals, then randomly choosing one subinterval.
+ * <p>
+ * 
+ * @author wolfgang.hoschek at cern.ch
+ * @version 1.0, 09/24/99
+ * @see edu.cornell.lassp.houle.RngPack.Ranmar
+ * @see edu.cornell.lassp.houle.RngPack.Ranlux
+ * @see edu.cornell.lassp.houle.RngPack.Ranecu
+ * @see java.util.Random
+ */
+public class MersenneTwister extends RandomEngine {
+
+       public static void main(String[] agrs) {
+               MersenneTwister mt = new MersenneTwister();
+
+               for (int i = 0; i < 100; i++) {
+                       System.err.println(mt.choose(2));
+               }
+       }
+
+       private int mti;
+
+       private int[] mt = new int[N]; /* set initial seeds: N = 624 words */
+
+       /* Period parameters */
+       private static final int N = 624;
+
+       private static final int M = 397;
+
+       private static final int MATRIX_A = 0x9908b0df; /* constant vector a */
+
+       private static final int UPPER_MASK = 0x80000000; /*
+                                                                               
                                 * most significant w-r
+                                                                               
                                 * bits
+                                                                               
                                 */
+
+       private static final int LOWER_MASK = 0x7fffffff; /*
+                                                                               
                                 * least significant r
+                                                                               
                                 * bits
+                                                                               
                                 */
+
+       /* for tempering */
+       private static final int TEMPERING_MASK_B = 0x9d2c5680;
+
+       private static final int TEMPERING_MASK_C = 0xefc60000;
+
+       private static final int mag0 = 0x0;
+
+       private static final int mag1 = MATRIX_A;
+
+       // private static final int[] mag01=new int[] {0x0, MATRIX_A};
+       /* mag01[x] = x * MATRIX_A for x=0,1 */
+
+       public static final int DEFAULT_SEED = 4357;
+
+       /**
+        * Constructs and returns a random number generator with a default seed,
+        * which is a <b>constant</b>. Thus using this constructor will yield
+        * generators that always produce exactly the same sequence. This 
method is
+        * mainly intended to ease testing and debugging.
+        */
+       public MersenneTwister() {
+               this(DEFAULT_SEED);
+       }
+
+       /**
+        * Constructs and returns a random number generator with the given seed.
+        * 
+        * @param seed
+        *            should not be 0, in such a case
+        *            <tt>MersenneTwister.DEFAULT_SEED</tt> is silently
+        *            substituted.
+        */
+       public MersenneTwister(int seed) {
+               setSeed(seed);
+       }
+
+       /**
+        * Constructs and returns a random number generator seeded with the 
given
+        * date.
+        * 
+        * @param d
+        *            typically <tt>new java.util.Date()</tt>
+        */
+       public MersenneTwister(Date d) {
+               this((int) ClockSeed(d));
+       }
+
+       /**
+        * Returns a copy of the receiver; the copy will produce identical
+        * sequences. After this call has returned, the copy and the receiver 
have
+        * equal but separate state.
+        * 
+        * @return a copy of the receiver.
+        */
+       public Object clone() throws CloneNotSupportedException {
+               MersenneTwister clone = (MersenneTwister) super.clone();
+               clone.mt = (int[]) this.mt.clone();
+               return clone;
+       }
+
+       /**
+        * Generates N words at one time.
+        */
+       protected void nextBlock() {
+               /*
+                * // ******************** OPTIMIZED ********************** // 
only
+                * 5-10% faster ? int y;
+                * 
+                * int kk; int[] cache = mt; // cached for speed int kkM; int 
limit =
+                * N-M; for (kk=0,kkM=kk+M; kk<limit; kk++,kkM++) { y =
+                * (cache[kk]&UPPER_MASK)|(cache[kk+1]&LOWER_MASK); cache[kk] =
+                * cache[kkM] ^ (y >>> 1) ^ ((y & 0x1) == 0 ? mag0 : mag1); } 
limit =
+                * N-1; for (kkM=kk+(M-N); kk<limit; kk++,kkM++) { y =
+                * (cache[kk]&UPPER_MASK)|(cache[kk+1]&LOWER_MASK); cache[kk] =
+                * cache[kkM] ^ (y >>> 1) ^ ((y & 0x1) == 0 ? mag0 : mag1); } y 
=
+                * (cache[N-1]&UPPER_MASK)|(cache[0]&LOWER_MASK); cache[N-1] =
+                * cache[M-1] ^ (y >>> 1) ^ ((y & 0x1) == 0 ? mag0 : mag1);
+                * 
+                * this.mt = cache; this.mti = 0;
+                */
+
+               // ******************** UNOPTIMIZED **********************
+               int y;
+
+               int kk;
+
+               for (kk = 0; kk < N - M; kk++) {
+                       y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+                       mt[kk] = mt[kk + M] ^ (y >>> 1) ^ ((y & 0x1) == 0 ? 
mag0 : mag1);
+               }
+               for (; kk < N - 1; kk++) {
+                       y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+                       mt[kk] = mt[kk + (M - N)] ^ (y >>> 1)
+                                       ^ ((y & 0x1) == 0 ? mag0 : mag1);
+               }
+               y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+               mt[N - 1] = mt[M - 1] ^ (y >>> 1) ^ ((y & 0x1) == 0 ? mag0 : 
mag1);
+
+               this.mti = 0;
+
+       }
+
+       /**
+        * Returns a 32 bit uniformly distributed random number in the closed
+        * interval <tt>[Integer.MIN_VALUE,Integer.MAX_VALUE]</tt> (including
+        * <tt>Integer.MIN_VALUE</tt> and <tt>Integer.MAX_VALUE</tt>).
+        */
+       public int nextInt() {
+               /* Each single bit including the sign bit will be random */
+               if (mti == N)
+                       nextBlock(); // generate N ints at one time
+
+               int y = mt[mti++];
+               y ^= y >>> 11; // y ^= TEMPERING_SHIFT_U(y );
+               y ^= (y << 7) & TEMPERING_MASK_B; // y ^= TEMPERING_SHIFT_S(y) &
+                                                                               
        // TEMPERING_MASK_B;
+               y ^= (y << 15) & TEMPERING_MASK_C; // y ^= TEMPERING_SHIFT_T(y) 
&
+                                                                               
        // TEMPERING_MASK_C;
+               // y &= 0xffffffff; //you may delete this line if word size = 32
+               y ^= y >>> 18; // y ^= TEMPERING_SHIFT_L(y);
+
+               return y;
+       }
+
+       /**
+        * Sets the receiver's seed. This method resets the receiver's entire
+        * internal state.
+        * 
+        * @param seed
+        *            should not be 0, in such a case
+        *            <tt>MersenneTwister.DEFAULT_SEED</tt> is substituted.
+        */
+       protected void setSeed(int seed) {
+               if (seed == 0) {
+                       setSeed(DEFAULT_SEED);
+                       return;
+               }
+
+               /*
+                * setting initial seeds to mt[N] using the generator Line 25 
of Table 1
+                * in [KNUTH 1981, The Art of Computer Programming Vol. 2 (2nd 
Ed.),
+                * pp102]
+                */
+
+               this.mt[0] = seed & 0xffffffff;
+               for (mti = 1; mti < N; mti++) {
+                       // if (mti<10) System.out.println(mt[mti-1] +":"+ 69069 
* mt[mti-1]
+                       // +":"+ ((69069 * mt[mti-1]) & 0xffffffff));
+                       this.mt[mti] = (69069 * this.mt[mti - 1]) & 0xffffffff;
+               }
+               // System.out.println("init done");
+
+       }
+}

Added: trunk/apps/simsalabim/Node.java
===================================================================
--- trunk/apps/simsalabim/Node.java                             (rev 0)
+++ trunk/apps/simsalabim/Node.java     2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,110 @@
+package simsalabim;
+
+
+public abstract class Node<S extends Simulator, K extends Key> implements 
InfoObject {
+
+    protected final S god;
+    protected final int num;
+    
+    private boolean active;    
+    private int currentNumber;
+    
+    protected long lastActivatedTime = -1;
+    protected long lastDeactivatedTime = -1;
+    
+    public Node(S god, int num) {
+        this.god = god;
+        this.num = num;
+    }
+
+    public int num() {
+        return num;
+    }
+
+    /**
+        * Returns the simulator to which this node belongs.
+        */
+    public S sim() {
+        return god;
+    }
+
+    /**
+        * Returns the time from this nodes simulator.
+        */
+    public long time() {
+        return god.time();
+    }
+
+
+    public String toString() {
+        return "N/#" + num + "/";
+    }
+
+    public void activate() {
+       active = true;
+       lastActivatedTime = god.time();
+       activated();
+    }
+    
+    public void deactivate() {
+       active = false;
+       lastDeactivatedTime = god.time();
+       deactivated();
+    }
+    
+    public boolean isActive() {
+       return active;
+    }
+    
+    /**
+        * Called when the node is activated. Default does nothing.
+        * 
+        */
+    protected void activated() {       
+    }
+    
+    /**
+        * Called when the node is deactivated. Default does nothing.
+        * 
+        */
+    protected void deactivated() {     
+    }
+
+    public abstract Data findData(K k);
+
+    public abstract void storeData(Data<K> d, boolean nonvolatile);
+
+    public abstract int nKeys();
+
+    /**
+        * The bottom implementation feeds an agg. called "NodeNumbers" with...
+        */
+    public void feedAggregator(DataAggregator da) {
+        if (da.name().equals("NodeNumbers")) {
+            da.next(num);
+        }
+    }
+    
+    /**
+        * Used by simulator to keep track of where the node is the in the 
array. Do
+        * not call.
+        * 
+        * @param n
+        *            The current place.
+        */
+    final void setCurrentNumber(int n) {
+       currentNumber = n;
+    }
+    
+    /**
+        * Used by simulator to keep track of where the node is the in the 
array. Do
+        * not call.
+        * 
+        * @return The current place.
+        */
+    final int getCurrentNumber() {
+       return currentNumber;
+    }
+    
+    
+}

Added: trunk/apps/simsalabim/Person.java
===================================================================
--- trunk/apps/simsalabim/Person.java                           (rev 0)
+++ trunk/apps/simsalabim/Person.java   2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,102 @@
+package simsalabim;
+import java.util.*;
+
+    /**
+        * Class for a node in social network.
+        */
+class Person implements Comparable<Person> {
+    
+    public LinkedList<Person> outN = new LinkedList<Person>();
+    public int id;
+    private boolean does_darknet = false;
+    private boolean does_opennet = false;
+
+    public int netid = 0;  // Network partition
+    public int score = 0;
+    private DarknetNode node;
+    
+    public Person(int id) {
+       this.id = id;
+    }
+
+    public Iterator<Person> neighbors() {
+       return outN.iterator();
+    }
+
+    public boolean isOpennet() {
+       if (!does_darknet && !does_opennet) throw new SimulationException 
("Node " + this + " without type.");      
+       return does_opennet;
+    }
+
+    public boolean isDarknet() {
+       if (!does_darknet && !does_opennet) throw new SimulationException 
("Node " + this + " without type.");      
+       return does_darknet;
+    }
+
+    public int degree() {
+           return outN.size();
+    }
+
+    public int network() {
+           return netid;
+    }
+
+    public void setNetwork(int id) {
+           netid = id;
+    }
+
+    public void setDarknet() {
+        does_darknet = true;
+    }
+
+    public void setOpennet() {
+       does_opennet = true;
+    }
+
+    public void joinedNetwork(DarknetNode node) {
+       this.node = node;
+    }
+
+    public void leftNetwork() {
+       this.node = null;
+    }
+
+    public boolean isInNetwork() {
+       return node != null;
+    }
+
+    public DarknetNode getNode() {
+       return node;
+    }
+
+    public void addOut(Person n) {
+       if (!outN.contains(n))
+           outN.add(n);
+    }
+    
+    public boolean linksTo(Person n) {
+       return outN.contains(n);
+    }
+    
+    public void removeNeighbor(Person n) {
+       outN.remove(n);
+    }
+
+    
+    public int compareTo(Person o) {
+       return ( score > o.score ? 
+                1 : 
+                ( score < o.score ?
+                  -1 :
+                  ( id > o.id ?
+                    1:
+                    ( id < o.id ?
+                          -1:
+                      0))));
+    }
+
+    public boolean equals(Object o) {
+       return ((Person) o).id == id && ((Person) o).score == score;
+    }
+
+}

Added: trunk/apps/simsalabim/PopularityDataSpace.java
===================================================================
--- trunk/apps/simsalabim/PopularityDataSpace.java                              
(rev 0)
+++ trunk/apps/simsalabim/PopularityDataSpace.java      2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,113 @@
+package simsalabim;
+
+import java.util.*;
+
+public class PopularityDataSpace<K extends Key> implements DataSpace<K> {
+
+       private ArrayList<Data<K>> data;                
+       private HashMap<K, Data<K>> datamap;
+       private double popDecay;
+       private int currSize;
+       private int maxSize;
+       
+       private int minDataSize;
+       private int dataSizeRange;
+       
+       public PopularityDataSpace(int size, double popDecay, int minSize, int 
maxSize) {
+               data = new ArrayList<Data<K>>(size);
+               datamap = new HashMap<K, Data<K>>();
+               this.popDecay = popDecay;
+               this.currSize = 0;
+               this.maxSize = size;
+               this.minDataSize = minSize;
+               this.dataSizeRange = maxSize - minSize;
+       }
+       
+       public Iterator<Data<K>> allData() {
+               return data.iterator();
+       }
+
+       public K chooseKey(RandomEngine re) {
+               return currSize == 0 ? null : data.get(getIndex(re)).key();
+       }
+
+       public Data<K> getData(K key) {
+               return datamap.get(key);
+       }
+       
+       public String info() {
+               return "PopularityDataSpace docs: " + data.size() + " (of " + 
maxSize + ")"; 
+       }
+
+       /**
+        * Keep min(currSize,maxSize) documents available for search
+        */
+
+       public Data<K> newData(Simulator god, K key) {
+               long datasize = minDataSize + (long) (god.random().nextDouble() 
* dataSizeRange);
+               Data<K> d = new Data<K>(god, key, datasize);
+               
+               if (currSize < maxSize) {
+                       data.add(d);
+                       datamap.put(key,d);
+                       currSize++;
+                       return d;
+               } else {
+                       int idx = god.re.nextInt(maxSize);
+                       moveDown(idx, god.re,god.time());
+                       data.set(idx,d);
+                       datamap.put(key, d);
+                       return d;
+               }
+       }
+
+       public int size() {
+               return currSize;
+       }
+       
+       /**
+        * Sample from the specified popularity (Zipf) distribution
+        */
+        
+       private int getIndex(RandomEngine re) {
+
+               // Uniform
+               if (popDecay == 0)
+                       return re.nextInt(currSize);
+
+               // Easy distribution
+               if (popDecay > 0.99 && popDecay < 1.01)
+                       return (int) Math.pow(currSize + 1, re.nextDouble()) - 
1;
+
+               // General case                 
+               double zsum = 0;
+               for (int i=1; i < currSize; i++) {
+                       zsum += (1/Math.pow(i,popDecay));
+               }
+               double r = re.nextDouble();
+               double z = 0;
+               int x = 0;
+               
+               while (r > (z/zsum)) {
+                       z += 1/Math.pow(++x,popDecay);
+               }           
+               
+               return x;
+       }
+       
+       private void moveDown(int n, RandomEngine re, long time) {
+               moveDown(n, re.nextInt(maxSize) + 1, data.get(n), time);
+       }
+       
+       private void moveDown(int n, int m, Data<K> d, long time) {
+               if ((n + m) >= maxSize) {
+                       datamap.remove(d.key());
+                       d.forgotten(time);                      
+               } else {
+                       Data<K> md = data.get(n + m);
+                       data.set(n+m,d);
+                       moveDown(n+m,2*m, md, time);
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/PositionKey.java
===================================================================
--- trunk/apps/simsalabim/PositionKey.java                              (rev 0)
+++ trunk/apps/simsalabim/PositionKey.java      2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,13 @@
+package simsalabim;
+/**
+ * A key which represents a position in a circular [0,1] keyspace
+ * 
+ * @author ossa
+ * 
+ */
+
+public abstract class PositionKey<K extends PositionKey> implements Key {
+
+       public abstract double dist(K pk);
+
+}

Added: trunk/apps/simsalabim/RandomEngine.java
===================================================================
--- trunk/apps/simsalabim/RandomEngine.java                             (rev 0)
+++ trunk/apps/simsalabim/RandomEngine.java     2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,224 @@
+/*
+ Copyright 1999 CERN - European Organization for Nuclear Research.
+ Permission to use, copy, modify, distribute and sell this software and its 
documentation for any purpose 
+ is hereby granted without fee, provided that the above copyright notice 
appear in all copies and 
+ that both that copyright notice and this permission notice appear in 
supporting documentation. 
+ CERN makes no representations about the suitability of this software for any 
purpose. 
+ It is provided "as is" without expressed or implied warranty.
+ */
+// Modified for Simsalabim by Oskar
+package simsalabim;
+
+import java.util.*;
+
+/**
+ * Abstract base class for uniform pseudo-random number generating engines.
+ * <p>
+ * Most probability distributions are obtained by using a <b>uniform</b>
+ * pseudo-random number generation engine (derived from this class or
+ * {@link edu.cornell.lassp.houle.RngPack.RandomElement}) followed by a
+ * transformation to the desired distribution. Thus, subclasses of this class
+ * are at the core of computational statistics, simulations, Monte Carlo
+ * methods, etc.
+ * <p>
+ * Subclasses produce uniformly distributed <tt>int</tt>'s and <tt>long</tt>'s
+ * in the closed intervals <tt>[Integer.MIN_VALUE,Integer.MAX_VALUE]</tt> and
+ * <tt>[Long.MIN_VALUE,Long.MAX_VALUE]</tt>, respectively, as well as
+ * <tt>float</tt>'s and <tt>double</tt>'s in the open unit intervals
+ * <tt>(0.0f,1.0f)</tt> and <tt>(0.0,1.0)</tt>, respectively.
+ * <p>
+ * Subclasses need to override one single method only: <tt>nextInt()</tt>.
+ * All other methods generating different data types or ranges are usually
+ * layered upon <tt>nextInt()</tt>. <tt>long</tt>'s are formed by
+ * concatenating two 32 bit <tt>int</tt>'s. <tt>float</tt>'s are formed by
+ * dividing the interval <tt>[0.0f,1.0f]</tt> into 2<sup>32</sup> sub
+ * intervals, then randomly choosing one subinterval. <tt>double</tt>'s are
+ * formed by dividing the interval <tt>[0.0,1.0]</tt> into 2<sup>64</sup>
+ * sub intervals, then randomly choosing one subinterval.
+ * <p>
+ * Note that this implementation is <b>not synchronized</b>.
+ * 
+ * @author wolfgang.hoschek at cern.ch
+ * @version 1.0, 09/24/99
+ * @see MersenneTwister
+ * @see MersenneTwister64
+ * @see edu.cornell.lassp.houle.RngPack.Ranmar
+ * @see edu.cornell.lassp.houle.RngPack.Ranlux
+ * @see edu.cornell.lassp.houle.RngPack.Ranecu
+ * @see java.util.Random
+ */
+public abstract class RandomEngine {
+       // extends edu.cornell.lassp.houle.RngPack.RandomSeedable implements
+       // cern.colt.function.DoubleFunction, cern.colt.function.IntFunction {
+       /**
+        * Makes this class non instantiable, but still let's others inherit 
from
+        * it.
+        */
+       protected RandomEngine() {
+       }
+
+       /**
+        * Equivalent to <tt>raw()</tt>. This has the effect that random engines
+        * can now be used as function objects, returning a random number upon
+        * function evaluation.
+        */
+       public double apply(double dummy) {
+               return raw();
+       }
+
+       /**
+        * Equivalent to <tt>nextInt()</tt>. This has the effect that random
+        * engines can now be used as function objects, returning a random 
number
+        * upon function evaluation.
+        */
+       public int apply(int dummy) {
+               return nextInt();
+       }
+
+       /**
+        * Returns a 64 bit uniformly distributed random number in the open unit
+        * interval <code>(0.0,1.0)</code> (excluding 0.0 and 1.0).
+        */
+       public double nextDouble() {
+               double nextDouble;
+
+               do {
+                       // -9.223372036854776E18 == (double) Long.MIN_VALUE
+                       // 5.421010862427522E-20 == 1 / Math.pow(2,64) == 1 / 
((double)
+                       // Long.MAX_VALUE - (double) Long.MIN_VALUE);
+                       nextDouble = ((double) nextLong() - 
-9.223372036854776E18) * 5.421010862427522E-20;
+               }
+               // catch loss of precision of long --> double conversion
+               while (!(nextDouble > 0.0 && nextDouble < 1.0));
+
+               // --> in (0.0,1.0)
+               return nextDouble;
+
+               /*
+                * nextLong == Long.MAX_VALUE --> 1.0 nextLong == 
Long.MIN_VALUE --> 0.0
+                * nextLong == Long.MAX_VALUE-1 --> 1.0 nextLong ==
+                * Long.MAX_VALUE-100000L --> 0.9999999999999946 nextLong ==
+                * Long.MIN_VALUE+1 --> 0.0 nextLong == Long.MIN_VALUE-100000L 
-->
+                * 0.9999999999999946 nextLong == 1L --> 0.5 nextLong == -1L 
--> 0.5
+                * nextLong == 2L --> 0.5 nextLong == -2L --> 0.5 nextLong == 
2L+100000L
+                * --> 0.5000000000000054 nextLong == -2L-100000L -->
+                * 0.49999999999999456
+                */
+       }
+
+       /**
+        * Returns a 32 bit uniformly distributed random number in the open unit
+        * interval <code>(0.0f,1.0f)</code> (excluding 0.0f and 1.0f).
+        */
+       public float nextFloat() {
+               // catch loss of precision of double --> float conversion
+               float nextFloat;
+               do {
+                       nextFloat = (float) raw();
+               } while (nextFloat >= 1.0f);
+
+               // --> in (0.0f,1.0f)
+               return nextFloat;
+       }
+
+       /**
+        * Returns a 32 bit uniformly distributed random number in the closed
+        * interval <tt>[Integer.MIN_VALUE,Integer.MAX_VALUE]</tt> (including
+        * <tt>Integer.MIN_VALUE</tt> and <tt>Integer.MAX_VALUE</tt>);
+        */
+       public abstract int nextInt();
+
+       /**
+        * Returns uniform integer between 0 and i-1
+        */
+       public int nextInt(int i) {
+               return choose(0, i - 1);
+       }
+
+       /**
+        * Returns a 64 bit uniformly distributed random number in the closed
+        * interval <tt>[Long.MIN_VALUE,Long.MAX_VALUE]</tt> (including
+        * <tt>Long.MIN_VALUE</tt> and <tt>Long.MAX_VALUE</tt>).
+        */
+       public long nextLong() {
+               // concatenate two 32-bit strings into one 64-bit string
+               return ((nextInt() & 0xFFFFFFFFL) << 32) | ((nextInt() & 
0xFFFFFFFFL));
+       }
+
+       /**
+        * Returns a 32 bit uniformly distributed random number in the open unit
+        * interval <code>(0.0,1.0)</code> (excluding 0.0 and 1.0).
+        */
+       public double raw() {
+               int nextInt;
+               do { // accept anything but zero
+                       nextInt = nextInt(); // in
+                                                                       // 
[Integer.MIN_VALUE,Integer.MAX_VALUE]-interval
+               } while (nextInt == 0);
+
+               // transform to (0.0,1.0)-interval
+               // 2.3283064365386963E-10 == 1.0 / Math.pow(2,32)
+               return (double) (nextInt & 0xFFFFFFFFL) * 
2.3283064365386963E-10;
+
+               /*
+                * nextInt == Integer.MAX_VALUE --> 0.49999999976716936 nextInt 
==
+                * Integer.MIN_VALUE --> 0.5 nextInt == Integer.MAX_VALUE-1 -->
+                * 0.4999999995343387 nextInt == Integer.MIN_VALUE+1 -->
+                * 0.5000000002328306 nextInt == 1 --> 2.3283064365386963E-10 
nextInt ==
+                * -1 --> 0.9999999997671694 nextInt == 2 --> 
4.6566128730773926E-10
+                * nextInt == -2 --> 0.9999999995343387
+                */
+       }
+
+       /**
+        * 
+        * Return a long integer seed calculated from the date. Equivalent to 
<CODE>ClockSeed(new
+        * Date());
+        * 
+        * @return a long integer seed
+        * 
+        */
+
+       public static long ClockSeed() {
+               return ClockSeed(new Date());
+       }
+
+       /**
+        * @param hi
+        *            upper limit of range
+        * @return a random integer in the range 1,2,... ,<STRONG>hi</STRONG>
+        */
+       public int choose(int hi) {
+               return choose(1, hi); // _WH_,
+               // return (int) (1+hi*raw()); // does not yield [1,hi]
+       }
+
+       /**
+        * @param lo
+        *            lower limit of range
+        * @param hi
+        *            upper limit of range
+        * @return a random integer in the range <STRONG>lo</STRONG>, 
<STRONG>lo</STRONG>+1,
+        *         ... ,<STRONG>hi</STRONG>
+        */
+
+       public int choose(int lo, int hi) {
+               return (int) ((long) lo + (long) ((1L + (long) hi - (long) lo) 
* raw())); // _WH_,
+                                                                               
                                                                                
        // March
+                                                                               
                                                                                
        // 12,1999
+               // return (int) (lo+(hi-lo+1)*raw()); // does not yield [lo,hi]
+       }
+
+       /**
+        * 
+        * Return a long integer seed given a date
+        * 
+        * @param d
+        *            a date
+        * @return a long integer seed
+        * 
+        */
+       public static long ClockSeed(Date d) {
+               return d.getTime();
+       }
+}

Added: trunk/apps/simsalabim/RunTask.java
===================================================================
--- trunk/apps/simsalabim/RunTask.java                          (rev 0)
+++ trunk/apps/simsalabim/RunTask.java  2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,159 @@
+package simsalabim;
+
+public abstract class RunTask implements ScriptCommand, Feedback {
+
+       private static final String DELIM;
+       static {
+               StringBuffer sb = new StringBuffer("|");
+               for (int i = 0; i < 78; i++)
+                       sb.append('-');
+               sb.append('|');
+               DELIM = sb.toString();
+       }
+
+       public static final int JOIN = 0;
+
+       public static final int SEARCH = 1;
+
+       public static final int PLACE = 2;
+
+       public static final int LEAVE = 3;
+
+       public static final int GSEARCH = 4;
+
+       public static final int MAINTENANCE = 5;
+
+       public static final int VERYUGLYHACK = 6;
+
+        public static final int DUALSTATS = 7;
+
+       public long runTill;
+
+       public RunTask(long runTill) {
+               this.runTill = runTill;
+       }
+
+       /**
+        * Calculates the time until a task. Default version calls with
+        * RandomEngine.
+        */
+       public long timeTillTask(Simulator s) {
+               return timeTillTask(s.random());
+       }
+
+       /**
+        * Calculates the type of task to do after a call to time above. Default
+        * version calls with randomEngine
+        */
+       public int taskType(Simulator s) {
+               return taskType(s.random());
+       }
+
+       /**
+        * This or timeTillTask(Simulator) must be overridden. Default throws 
error.
+        */
+       protected long timeTillTask(RandomEngine re) {
+               throw new Error("TimeTillTask unimplemented in " + 
this.getClass());
+       }
+
+       /**
+        * This or taskType(Simulator) must be overriden. Default throws error.
+        */
+       protected int taskType(RandomEngine re) {
+               throw new Error("taskType unimplemented in " + this.getClass());
+       }
+
+       /**
+        * Called when this is started.
+        */
+       public void started(long time, EventLogger ev) {
+       }
+
+       /**
+        * Called when this has finished running.
+        */
+       public void done(long time, EventLogger ev) {
+       }
+
+       public long runTill() {
+               return runTill;
+       }
+
+       public final void execute(Simulator s) {
+               long runTill = runTill();
+               long startTime = s.time();
+               long runTime = runTill - s.time();
+
+               DataSpace ds = s.dataspace();
+               int ntasks = 0;
+               long realTime = System.currentTimeMillis();
+               System.err.println("Task " + this);
+               System.err.println(DELIM);
+               started(s.time(), s.log());
+               while (s.time() < runTill) {
+                       long col = 80 * (s.time() - startTime) / runTime;
+                       s.step(timeTillTask(s));
+                       
+                       long ncol = 80 * (s.time() - startTime) / runTime;
+
+                       if (ncol > col)
+                               System.err.print("+");
+
+                       // System.err.println(time);
+
+                       if (s.time() >= runTill)
+                               break;
+
+                       int t = taskType(s);
+                       ntasks++;
+
+                       if (t == RunTask.JOIN) {
+                               // System.err.println("Join! \t");
+                               Node n = s.newNode();
+                               // if (nodes.isEmpty()) {
+                               // nodes.add(n);
+                               // } else {
+                               boolean r = s.join(n, s.chooseNode(), this);
+                               if (r) {
+                                       s.addNode(n);
+                                       n.activate();
+                               }
+                               // }
+                       } else if (t == RunTask.SEARCH || t == RunTask.GSEARCH) 
{
+                               Node n = s.chooseNode(); 
+                               if (n != null) {
+                                       Key k = s.chooseKey();
+                                       if (k != null) {
+                                               Data d = s.search(k, n, t == 
RunTask.GSEARCH, this);
+                                               if (d != null)
+                                                       d.retrieved();
+                                       }
+                               }
+                       } else if (t == RunTask.PLACE) {
+                               Node n = s.chooseNode();
+                               if (n != null) {
+                                       Data<Key> d = ds.newData(s, s.newKey());
+
+                                       s.place(d, n, this);
+                               }
+                       } else if (t == RunTask.LEAVE) {
+                               // System.err.println("Leave! \t");
+                               Node n = s.chooseNode();
+                               if (n != null) {
+                                   // Remove from active
+                                   s.removeNode(n);              
+                                   s.leave(n, this, s.leavePermanent(n));
+                                   n.deactivate();
+
+                               }
+                       } else if (t == RunTask.MAINTENANCE) {
+                               s.maintenance(this);
+                       } else if (t == RunTask.DUALSTATS) {
+                               s.dualstats(this);
+                       }
+               }
+               System.err.println();
+               System.err.println("Task " + this + " (with " + ntasks + " 
attempted tasks) complete in " + (System.currentTimeMillis() - realTime) + 
"ms.");
+               done(s.time(), s.log());
+       }
+}

Added: trunk/apps/simsalabim/Script.java
===================================================================
--- trunk/apps/simsalabim/Script.java                           (rev 0)
+++ trunk/apps/simsalabim/Script.java   2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,108 @@
+package simsalabim;
+import java.io.*;
+import java.util.*;
+import java.lang.reflect.*;
+/**
+ * Scripts that run the simulator. Look at script.txt for the format.
+ */
+public class Script {
+
+       private static HashMap<String, ScriptCommandType> commandTypes = new 
HashMap<String, ScriptCommandType>();
+
+       static {
+               commandTypes.put("BuildTask",new 
ScriptCommandType("BuildTask"));
+               commandTypes.put("JoinTask",new ScriptCommandType("JoinTask"));
+               commandTypes.put("BlaTask",new ScriptCommandType("BlaTask"));
+               commandTypes.put("NodeAggregate", new AggregateCommand.Type());
+               commandTypes.put("MJoinTask", new 
ScriptCommandType("MJoinTask"));
+               commandTypes.put("MBuildTask", new 
ScriptCommandType("MBuildTask"));
+               commandTypes.put("InfoCommand", new 
ScriptCommandType("InfoCommand"));
+               commandTypes.put("HJoinTask", new 
ScriptCommandType("HJoinTask"));
+               commandTypes.put("HBuildTask", new 
ScriptCommandType("HBuildTask"));
+               commandTypes.put("HDualStats", new 
ScriptCommandType("HDualStats"));
+       }
+
+       private Class simClass;
+       private String simArgs;
+
+       private LinkedList<ScriptCommand> cqueue = new 
LinkedList<ScriptCommand>();
+
+       public Script(BufferedReader br) throws IOException, ScriptException {
+               this(readcmds(br));
+       }
+
+
+       public Script(String[] cmds) throws ScriptException {
+               if (cmds.length < 2)
+                       throw new ScriptException("Nothing in file");
+               try {
+                       int n = cmds[0].indexOf(" ");
+                       String cl = n == -1 ? cmds[0] : cmds[0].substring(0, n);
+                       simClass = Class.forName(cl);
+
+                       if (!Simulator.class.isAssignableFrom(simClass))
+                               throw new ScriptException("Class " + cl + " not 
a simulator.");
+                       simArgs = n == -1 ? cmds[0]: cmds[0].substring(n + 
1).trim();
+               } catch (ClassNotFoundException e) {
+                       throw new ScriptException("No such simulator class: " + 
cmds[0]);
+               }
+               for (int i = 1 ; i < cmds.length ; i++) {
+                       StringTokenizer st = new StringTokenizer(cmds[i],"\t ");
+                       String cmd = st.nextToken();
+
+                       ScriptCommandType ct = commandTypes.get(cmd);
+                       if (ct == null)
+                               throw new ScriptException("No such command as: 
" + cmd);
+
+                       cqueue.addLast(ct.newCommand(st));
+               }
+       }
+
+
+
+       public Simulator makeSim(RandomEngine re,  DataSpace ds, Settings st, 
EventLogger ev) throws ScriptException {
+           
+           // Simulator s = new Darknet(re,ds,st,ev,simArgs);
+           // return s;
+
+               try {
+
+                   Constructor c = simClass.getConstructor(new Class[] 
{RandomEngine.class, 
+                                                                        
DataSpace.class, Settings.class,
+                                                                        
EventLogger.class, String.class});
+                   return (Simulator) c.newInstance(new Object[] {re, ds, st, 
ev, simArgs});
+
+               } catch (InvocationTargetException e) {
+                       throw new ScriptException("Simulator construction 
failed: " + e.getTargetException());
+               } catch (NoSuchMethodException e) {
+                       throw new ScriptException("Simulator class missing 
constructor.");
+               } catch (InstantiationException e) {
+                       throw new ScriptException("Borked: " + e);
+               } catch (IllegalAccessException e) {
+                       throw new SimulationException("WTF? : " + e);
+               }
+
+
+       }
+
+       public ScriptCommand nextCommand() {
+               if (cqueue.size() != 0)
+                       return cqueue.removeFirst();
+               else
+                       return null;
+       }
+
+
+       private static String[] readcmds(BufferedReader br) throws IOException {
+               String l;
+               Vector<String> v = new Vector<String>();
+               while ((l = br.readLine()) != null) {
+                       if (!l.trim().equals("") && !l.trim().startsWith("#"))
+                               v.add(l);
+               }
+
+               String[] cmds = new String[v.size()];
+               v.copyInto(cmds);
+               return cmds;
+       }
+}

Added: trunk/apps/simsalabim/ScriptCommand.java
===================================================================
--- trunk/apps/simsalabim/ScriptCommand.java                            (rev 0)
+++ trunk/apps/simsalabim/ScriptCommand.java    2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,7 @@
+package simsalabim;
+
+public interface ScriptCommand {
+
+       public void execute(Simulator s) throws ScriptException;
+
+}

Added: trunk/apps/simsalabim/ScriptCommandType.java
===================================================================
--- trunk/apps/simsalabim/ScriptCommandType.java                                
(rev 0)
+++ trunk/apps/simsalabim/ScriptCommandType.java        2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,49 @@
+package simsalabim;
+
+import java.lang.reflect.*;
+import java.util.StringTokenizer;
+
+public class ScriptCommandType {
+
+       private String name;
+
+       public ScriptCommandType(String name) {
+               this.name = name;
+       }
+
+       public String name() {
+               return name;
+       }
+
+       public ScriptCommand newCommand(StringTokenizer param)
+                       throws ScriptException {
+               try {
+                       // Reflection is always ugly. Remind me how many times 
I have
+                       // written this code...
+
+                       Class c = Class.forName("simsalabim." + name);
+                       int n = param.countTokens();
+                       String[] params = new String[n];
+                       for (int i = 0; i < n; i++)
+                               params[i] = param.nextToken();
+
+                       Object o = c.getConstructor(new Class[] { 
String[].class })
+                                       .newInstance(new Object[] { params });
+                       return (ScriptCommand) o;
+               } catch (InvocationTargetException e) {
+                       Throwable t = e.getTargetException();
+                       if (t instanceof ScriptException)
+                               throw (ScriptException) t;
+                       else if (t instanceof RuntimeException)
+                               throw (RuntimeException) t;
+                       else
+                               throw new ScriptException(e.toString());
+               } catch (Exception e) {
+                       // Blanket catches are BAD. Bad Oskar, bad!
+                       e.printStackTrace();
+                       throw new ScriptException("Failed to construct script 
command: "
+                                       + e);
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/ScriptException.java
===================================================================
--- trunk/apps/simsalabim/ScriptException.java                          (rev 0)
+++ trunk/apps/simsalabim/ScriptException.java  2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,17 @@
+package simsalabim;
+
+/**
+ * Thrown on broken script files.
+ */
+
+public class ScriptException extends Exception {
+
+       public ScriptException(String s) {
+               super(s);
+       }
+
+       public ScriptException() {
+
+       }
+
+}

Added: trunk/apps/simsalabim/Settings.java
===================================================================
--- trunk/apps/simsalabim/Settings.java                         (rev 0)
+++ trunk/apps/simsalabim/Settings.java 2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,93 @@
+package simsalabim;
+import java.util.*;
+import java.io.*;
+
+public class Settings {
+       private HashMap<String, String> hm = new HashMap<String, String>();
+
+       public Settings(String file) {
+               try {
+                       BufferedReader br = new BufferedReader(new 
FileReader(file));
+
+                       String s;
+                       while ((s = br.readLine()) != null) {
+                               if (!s.trim().startsWith("#") && 
!"".equals(s.trim())) {
+                                       int j = s.indexOf('=');
+                                       
hm.put(s.substring(0,j).trim(),s.substring(j+1).trim());
+                               }
+                       }
+               } catch (IOException e) {
+                       System.err.println("Exception reading settings: " + e);
+               }
+       }
+       
+       /** Creates an empty Settings object. */
+       public Settings() {
+       }
+
+       public boolean isSet(String s) {
+               return hm.containsKey(s);
+       }
+
+       /** Default 0 * */
+       public int getInt(String s) {
+               return getInt(s, 0);
+       }
+
+       public int getInt(String s, int def) {
+               if (!hm.containsKey(s)) {
+                       System.err.println("Using default value " + s + " = " + 
def);
+                       return def;
+               }
+               try {
+                       return Integer.parseInt(hm.get(s));
+               } catch (NumberFormatException e) {
+                       System.err.println("Numeric setting " + s + " 
malformatted.");
+                       return def;
+               }
+       }
+
+
+       /** Default 0.0 * */
+       public double getDouble(String s) {
+               return getDouble(s, 0.0);
+       }
+
+       public double getDouble(String s, double def) {
+               if (!hm.containsKey(s)) {
+                       System.err.println("Using default value " + s + " = " + 
def);
+                       return def;
+               }
+               try {
+                       return Double.parseDouble(hm.get(s));
+               } catch (NumberFormatException e) {
+                       System.err.println("Numeric setting " + s + " 
malformatted.");
+                       return def;
+               }
+       }
+
+       /** Default null * */
+       public String get(String s) {
+               return hm.get(s);
+       }
+
+       public String get(String s, String def) {
+               String r = hm.get(s);
+               if (r == null) {
+                       System.err.println("Using default value " + s + " = " + 
def);
+                       r = def;
+               }
+               return r;
+       }
+       
+
+       /** Lines start with ls * */
+       public String writeOut(String ls) {
+               StringBuffer sb = new StringBuffer();
+               for (String s : hm.keySet()) {
+                       sb.append(ls).append(s).append(" = 
").append(hm.get(s)).append("\n");
+               }
+               return sb.toString();
+       }
+
+}

Added: trunk/apps/simsalabim/SimulationException.java
===================================================================
--- trunk/apps/simsalabim/SimulationException.java                              
(rev 0)
+++ trunk/apps/simsalabim/SimulationException.java      2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,15 @@
+package simsalabim;
+
+/**
+ * Exceptions that signify a broken simulation.
+ */
+public class SimulationException extends RuntimeException {
+
+       public SimulationException() {
+       }
+
+       public SimulationException(String s) {
+               super(s);
+       }
+
+}

Added: trunk/apps/simsalabim/Simulator.java
===================================================================
--- trunk/apps/simsalabim/Simulator.java                                (rev 0)
+++ trunk/apps/simsalabim/Simulator.java        2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,192 @@
+package simsalabim;
+
+import java.util.*;
+
+public abstract class Simulator<N extends Node, K extends Key> {
+
+
+       protected RandomEngine re = null;
+
+       private long time = 0;
+       private int nodeNumber = 0;
+
+       protected DataSpace<K> ds;
+       protected ArrayList<N> nodes = new ArrayList<N>();
+       protected EventLogger ev;
+       protected Settings st;
+
+       public Simulator(RandomEngine re, DataSpace<K> ds, Settings st, 
EventLogger ev) {
+               this.st = st;
+               this.re = re;
+               this.ds = ds;
+               this.ev = ev;
+       }
+
+       public void dualSplitPositions() {}
+
+       public Simulator(DataSpace<K> ds, EventLogger ev) {
+               this(new MersenneTwister(new Date()), ds, new Settings(), ev);
+       }
+
+       public long time() {
+               return time;
+       }
+       
+       public void step(long stime) {
+               time += stime;
+       }
+
+       public RandomEngine random() {
+               return re;
+       }
+       
+       public EventLogger log() {
+               return ev;
+       }
+       
+       public DataSpace dataspace() {
+               return ds;
+       }
+
+       // To get an active node
+       public N chooseNode() {
+               return nodes.isEmpty() ? null : 
nodes.get(re.choose(nodes.size()) - 1);
+       }
+       
+       public K chooseKey() {
+               return ds.chooseKey(re);
+       }
+
+       /**
+        * Do NOT call remove on this Iterator.
+        * 
+        * @return An iterator of all the nodes.
+        */
+       public Iterator<N> allNodes() { 
+               return nodes.iterator();
+       }
+
+       public int netSize() {
+               return nodes.size();
+       }
+
+       public int maintSize() {
+               return nodes.size();
+       }
+       
+       public String info() {
+               return "Simulator " + this.getClass().getName() + " at time " + 
time + " net size " +
+                                       nodes.size() + ".";
+       }
+
+       /**
+        * Whether a node should leave permanently or stay dormant. Leave it to 
the network.
+        */
+       public abstract boolean leavePermanent(N node);
+
+       public void aggregateNodeData(DataAggregator da) {
+               for (Iterator<N> it = nodes.iterator() ; it.hasNext() ;) {
+                       it.next().feedAggregator(da);
+               }
+       }
+
+
+       /**
+        * Creates keys for specific architecture.
+        */
+       public abstract K newKey();
+
+       /**
+        * Creates a new node from a specific architecture.
+        */
+       public N newNode() {
+               N node = newNode(++nodeNumber);
+               return node;
+       }
+       
+       protected abstract N newNode(int num);
+       
+       public void addNode(N node) {
+               node.setCurrentNumber(nodes.size());
+               nodes.add(node);
+       }
+       
+
+       public void removeNode(N n) {
+               int i = n.getCurrentNumber();
+               if (!nodes.get(i).equals(n)) {
+                       throw new SimulationException("Node not found in 
expected place.");
+               }
+               N l = nodes.remove(nodes.size() - 1);
+               if (l != n) {
+                       l.setCurrentNumber(i);
+                       nodes.set(i, l);
+               }
+       }
+       
+
+       /**
+        * Performs a join for a node from a specific architecture.
+        * 
+        * @param newNode
+        *            The node joining.
+        * @param oldNode
+        *            A node already in the network. Null if no such node 
exists.
+        */
+       public abstract boolean join(N newNode, N oldNode, Feedback fb);
+
+
+       /**
+        * Performs a maintenance operation (which can mean anything really) on 
a
+        * node.
+        */
+       public abstract void maintenance(Feedback fb);
+
+       /**
+        * Runs a search query for a specific architecture.
+        */
+       public Data search(K k, N n, Feedback fb) {
+               return search(k, n, false, fb);
+       }
+
+       /**
+        * Gets information about the state when running with two joined 
networks
+        */
+       public void dualstats(Feedback fb) {
+       }
+
+       /**
+        * Gets information about keys on nodes (both active and passive)
+        */
+       public int[] keystats() {
+              return new int[] {-1,-1};
+       }
+
+
+       /**
+        * Runs a search query for a specific architecture.
+        * 
+        * @param ghost
+        *            Whether to perform the query in ghost mode. If true, there
+        *            will be no side effects on the network.
+        */
+       public abstract Data<K> search(K k, N n, boolean ghost, Feedback fb);
+
+
+       /**
+        * Inserts a piece of data for a specific architecture.
+        */
+       public abstract boolean place(Data<K> d, N n, Feedback fb);
+
+       /**
+        * Tells a node to leave the network. Permanently or temporarily.
+        */
+       public abstract void leave(N n, Feedback fb, boolean permanent);
+
+
+
+       public static void sleep(long millis) {
+               try { Thread.sleep(millis); } catch (InterruptedException e) {}
+       }
+
+}

Added: trunk/apps/simsalabim/StringEventLogger.java
===================================================================
--- trunk/apps/simsalabim/StringEventLogger.java                                
(rev 0)
+++ trunk/apps/simsalabim/StringEventLogger.java        2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,55 @@
+package simsalabim;
+
+import java.io.PrintWriter;
+
+public class StringEventLogger extends EventLogger {
+
+       public PrintWriter pw;
+
+       public StringEventLogger(PrintWriter pw) {
+               this.pw = pw;
+       }
+
+       public void nodeJoined(long time, Node n) {
+               pw.println(time + "\tNew Node Joined: " + n.toString());
+       }
+
+       public void nodeJoinError(long time, Node n, int error) {
+               pw.println(time + "\tJoin error for node: " + n.toString() + " 
error "
+                               + error + ": " + error(error));
+       }
+
+       public void dataPlaced(long time, Data d) {
+               pw.println(time + "\tData Placed: " + d.toString());
+       }
+
+       public void dataPlaceError(long time, Data d, int error) {
+               pw.println(time + "\tPlace error for data: " + d.toString() + " 
error "
+                               + error + ": " + error(error));
+       }
+
+       public void dataFound(long time, Data d) {
+               pw.println(time + "\tData Found: " + d.toString());
+       }
+
+       public void dataNotFound(long time, Key k, int error) {
+               pw.println(time + "\tData Not Found: " + k.toString() + " error 
"
+                               + error + ": " + error(error));
+       }
+
+       public void dataFindError(long time, Key k, int error) {
+               pw.println(time + "\tFind error for key: " + k.toString() + " 
error "
+                               + error + ": " + error(error));
+       }
+
+       public void warning(long time, int error) {
+               pw.println(time + "\tWARNING error " + error + ": " + 
error(error));
+               pw.flush();
+       }
+
+       public void message(long time, String message) {
+               pw.println(time + "\tMESSAGE: " + message);
+               pw.flush();
+       }
+
+}

Added: trunk/apps/simsalabim/TestSimulator.java
===================================================================
--- trunk/apps/simsalabim/TestSimulator.java                            (rev 0)
+++ trunk/apps/simsalabim/TestSimulator.java    2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,104 @@
+package simsalabim;
+
+public class TestSimulator extends Simulator {
+
+       public TestSimulator(RandomEngine re, DataSpace ds, EventLogger ev,
+                       Settings st, String args) {
+               super(re, ds, st, ev);
+       }
+
+       public void maintenance(Feedback fb) {
+       }
+
+       private static class TestKey implements Key {
+
+               long val;
+
+               public TestKey(long val) {
+                       this.val = val;
+               }
+
+               public double ident() {
+                       return ((double) val) / Long.MAX_VALUE;
+               }
+
+               public String stringVal() {
+                       return Long.toString(val);
+               }
+
+               public int hashCode() {
+                       return (int) (val ^ (val >>> 32));
+               }
+
+       }
+
+       public Key newKey() {
+               return new TestKey(re.nextLong());
+       }
+
+       private class TestNode extends Node {
+               int searched = 0, placed = 0;
+
+               public TestNode(Simulator god, int num) {
+                       super(god, num);
+               }
+
+               public void storeData(Data la, boolean nonvolatile) {
+               }
+
+               public Data findData(Key k) {
+                       return ds.getData(k);
+               }
+
+               public int nKeys() {
+                       return 0;
+               }
+
+               public int fieldnum() {
+                       return 3;
+               }
+
+               public String[] fields() {
+                       return new String[] { "Num", "Searched", "Placed" };
+               }
+
+               public String[] info() {
+                       return new String[] { Integer.toString(num),
+                                       Integer.toString(searched), 
Integer.toString(placed) };
+               }
+       }
+
+       public boolean leavePermanent(Node n) {
+               return true;
+       }
+
+       public Node newNode(int num) {
+               return new TestNode(this, num);
+       }
+
+       public boolean join(Node newNode, Node oldNode, Feedback fb) {
+               fb.feedback(RunTask.JOIN, true, new double[] {re.choose(20)});
+               return true;
+       }
+
+       public Data search(Key k, Node n, Feedback fb) {
+               return search(k, n, false, fb);
+       }
+
+       public Data search(Key k, Node n, boolean ghost, Feedback fb) {
+               ((TestNode) n).searched++;
+               fb.feedback(RunTask.SEARCH, true, new double[] {re.choose(20)});
+               return ds.getData(k);
+       }
+
+       public boolean place(Data d, Node n, Feedback fb) {
+               ((TestNode) n).placed++;
+               fb.feedback(RunTask.PLACE, true, new double[] {re.choose(20)});
+               d.addedTo(n);
+               return true;
+       }
+
+       public void leave(Node n, Feedback fb, boolean permanent) {
+               fb.feedback(RunTask.LEAVE, true, new double[] {re.choose(20)});
+       }
+}

Added: trunk/apps/simsalabim/TreeKey.java
===================================================================
--- trunk/apps/simsalabim/TreeKey.java                          (rev 0)
+++ trunk/apps/simsalabim/TreeKey.java  2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,40 @@
+package simsalabim;
+
+public class TreeKey extends PositionKey<TreeKey> {
+       
+       public static int BITS_USED = 12;
+       
+       public static KeyFactory<TreeKey> getFactory() {
+               return new KeyFactory<TreeKey>() {
+                       public TreeKey newKey(long val) {
+                               return new TreeKey(val);
+                       }
+               };
+       }
+       
+       
+       private int val;
+       
+       public TreeKey(long val) {
+               this.val = (int) val;
+       }
+
+       @Override
+       public double dist(TreeKey tk) {
+               int df = tk.val ^ val;
+               for (int i = 64 ; i > 0 ; i--) {
+                       if((df & 1) > 0)
+                               return i / 64.0;
+                       df >>= 1;
+               }
+               return 0.0;
+       }
+
+       public double ident() {
+               return (double) val;
+       }
+
+       public String stringVal() {
+               return "TK" + Long.toHexString(val);
+       }
+}

Added: trunk/apps/simsalabim/XorKey.java
===================================================================
--- trunk/apps/simsalabim/XorKey.java                           (rev 0)
+++ trunk/apps/simsalabim/XorKey.java   2009-02-11 13:53:49 UTC (rev 25585)
@@ -0,0 +1,47 @@
+package simsalabim;
+/**
+ * A key which does Kademlia style XOR distance, backed by 63 bits for a double
+ * 
+ * @author ossa
+ * 
+ */
+public class XorKey extends PositionKey<XorKey>{
+
+       public static KeyFactory<XorKey> getFactory() {
+               return new KeyFactory<XorKey>() {
+                       public XorKey newKey(long val) {
+                               return new XorKey(val);
+                       }
+               };
+       }
+       
+       private static final long M = 0x7FFFFFFFFFFFFFFFl;
+       
+       private long val;
+       
+       /**
+        * Creates a key with value val. Will zeroe out the highest order bit.
+        * 
+        * @param val
+        */
+       public XorKey(long val) {
+               this.val = val & M; // make sure it is positive.
+       }
+       
+       
+       @Override
+       public double dist(XorKey pk) {
+               long d = pk.val ^ val;
+               return ((double) d) / (double) Long.MAX_VALUE;
+       }
+
+       public double ident() {
+               return (double) val;
+       }
+
+       public String stringVal() {
+               return "XORK" + Long.toHexString(val);
+       }
+
+       
+}

Added: trunk/apps/simsalabim/darknet/Darknet.java
===================================================================
--- trunk/apps/simsalabim/darknet/Darknet.java                          (rev 0)
+++ trunk/apps/simsalabim/darknet/Darknet.java  2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,236 @@
+package simsalabim;
+import java.util.*;
+import java.io.*;
+/**
+ * Simulates a Darknet using the algorithm described in
+ * 
+ */
+
+public class Darknet extends Simulator<DarknetNode, CircleKey> {
+
+       int INITIAL_SWITCHES;
+       int MAINT_SWITCHES;
+       int MAX_NO_IMPROVE;
+       int STORE_SIZE;
+       int N_BEST;
+       int RAND_STEPS;
+       float RECACHE_PROB;
+       float RELINK_PROB;
+
+       HashMap<Integer, Person> people;
+       TreeSet<Person> ts = new TreeSet<Person>();
+       int size;
+
+       public Darknet(RandomEngine re, DataSpace ds, EventLogger el, Settings 
st, File graph,
+                       int startId) {
+               super(re, ds, st, el);
+
+               setParameters();
+               try {
+                       readGraph(graph, startId);
+               } catch (IOException e) {
+                       throw new RuntimeException("Error reading darknet 
graph: " + e);
+               }
+
+               logCreation();
+       }
+
+       public Darknet(RandomEngine re, DataSpace ds, Settings st, EventLogger 
el,
+                       String args) {
+               super(re, ds, st, el);
+               setParameters();
+
+               StringTokenizer stoke = new StringTokenizer(args,", ");
+
+               if (stoke.countTokens() < 1) {
+                       throw new RuntimeException("No darknet graph given.");
+               }
+
+               try {
+                       readGraph(new File(stoke.nextToken()), 
Integer.parseInt(stoke.nextToken()));
+               } catch (IOException e) {
+                       throw new RuntimeException("Error reading darknet 
graph: " + e);
+               }
+
+               System.err.println("Read graph of size " + people.size());
+
+               logCreation();
+       }
+       
+       private void setParameters() {
+               // Settings
+               INITIAL_SWITCHES = st.getInt("dnInitialSwitches",100);
+               MAINT_SWITCHES = st.getInt("dnMaintSwitches",100);
+               RAND_STEPS = st.getInt("dnRandSteps",5);
+               MAX_NO_IMPROVE = st.getInt("dnMaxNoImprove",20);
+               STORE_SIZE = st.getInt("dnStoreSize",50);
+               N_BEST = st.getInt("dnNBest",3);
+               RECACHE_PROB = (float) st.getDouble("dnRecacheProb",1.0);
+               RELINK_PROB = (float) st.getDouble("dnRelinkProb",1.0);
+       }
+
+       private void logCreation() {
+               ev.message(time(), "Created Darknet Simulator. SWITCHES: " + 
MAINT_SWITCHES + 
+                               " RANDSTEPS: " + RAND_STEPS + " MAX_NO_IMPROVE: 
" 
+                               + MAX_NO_IMPROVE + " STORESIZE: " + STORE_SIZE 
+ ".");
+       }
+
+
+       public DarknetNode newNode(int num) {
+
+               double r = re.nextDouble();
+
+               Person p = growNetwork();
+
+               if (p == null)
+                       throw new RuntimeException("Poplulation smaller than 
network.");
+
+               return new DarknetNode(this, num, p, new CircleKey(r), 0);
+
+       }
+
+       public CircleKey newKey() {
+               return new CircleKey(re.nextDouble());
+       }
+
+       public boolean join(DarknetNode newNode, DarknetNode oldNode, Feedback 
fb) {
+               int cost = newNode.join();
+               size++;
+
+               fb.feedback(RunTask.JOIN, true, cost);
+               return true;
+       }
+
+       public void maintenance(Feedback fb) {
+               for (int i = 0; i < MAINT_SWITCHES ; i++) {
+                       DarknetNode n = (DarknetNode) chooseNode();
+                       if (n != null) {
+                               n.makeSwitch();
+                       }
+               }
+               fb.feedback(RunTask.MAINTENANCE, true, MAINT_SWITCHES * 
RAND_STEPS);
+       }
+
+
+       public Data search(CircleKey k, DarknetNode n, boolean ghost, Feedback 
fb) {
+               DarknetRoute route = n.findRoute(k, null, MAX_NO_IMPROVE);
+
+               Data d = route.findData(k);
+
+               if (d != null && re.nextFloat() < RECACHE_PROB) {
+                       route.sinkStore(d);
+               }
+               route.reLink(re, RELINK_PROB);
+
+               fb.feedback(RunTask.SEARCH, d != null, route.size());
+               return d;
+       }
+
+       public boolean place(Data<CircleKey> d, DarknetNode n, Feedback fb) {
+               DarknetRoute route = n.findRoute(d.key(), null, MAX_NO_IMPROVE);
+               route.sinkStore(d);
+               route.reLink(re, RELINK_PROB);
+
+               fb.feedback(RunTask.PLACE, true, route.size());
+               return true;
+       }
+
+       public void leave(DarknetNode dn, Feedback fb) {
+               size--;
+               dn.leave();
+               shrinkNetwork(dn.person());
+               fb.feedback(RunTask.LEAVE, true, 1);
+       }
+
+
+
+       /**
+        * Reads a graph file containing the social network data.
+        */
+        protected void readGraph(File s, int startId) throws IOException {
+                BufferedReader br = new BufferedReader(new FileReader(s));
+                String line;
+                people = new HashMap<Integer, Person>();
+                System.err.println("Parsing...");
+                int i = 0;
+                while ((line = br.readLine()) != null) {
+                        if (++i % 100000 == 0)
+                                System.err.println("Line: " + i);
+                        StringTokenizer st = new StringTokenizer(line, "\t");
+                        if (st.nextToken().equals("E")) {
+                                Integer id1 = new Integer(st.nextToken());
+                                Integer id2 = new Integer(st.nextToken());
+
+                                Person n1 = people.get(id1);
+                                if (n1 == null) {
+                                        n1 = new Person(id1);
+                                        people.put(id1, n1);
+                                }
+                                Person n2 = people.get(id2);
+                                if (n2 == null) {
+                                        n2 = new Person(id2);
+                                        people.put(id2, n2);
+                                }
+
+                                // There is duplicate edge protection, and we 
are only
+                                       // interested
+                                // in undirected graphs
+                                n1.addOut(n2);
+                                n2.addOut(n1);
+                        }
+                }
+
+                Person p = people.get(startId);
+                if (p == null)
+                        throw new RuntimeException("The starting identity did 
not exist");
+                ts.add(p);
+        }
+
+
+        /**
+                * Chooses the next person to join the network.
+                */
+        protected Person growNetwork() {
+                // System.err.println("TS SIZE: " + ts.size());
+                if (ts.isEmpty()) // no more people left
+                        return null; 
+                Person cand = ts.last();
+                // System.err.println("Score: " + cand.score);
+                ts.remove(cand);
+
+                for (Iterator<Person> it = cand.neighbors() ; it.hasNext() ; ) 
{
+                        Person p = it.next();
+
+                        if (ts.contains(p)) {
+                                ts.remove(p);
+                                p.score++;             
+                                ts.add(p);
+                        } else {
+                                p.score++;
+                                if (!p.isInNetwork()) {
+                                        ts.add(p);
+                                }
+                        }
+                }
+
+                return cand;
+        }
+
+        /**
+                * Takes somebody out of the network.
+                */
+        protected void shrinkNetwork(Person p) {
+                for (Iterator<Person> it = p.neighbors() ; it.hasNext() ; ) {
+                        Person n = it.next();
+                        if (ts.contains(n)) {
+                                ts.remove(n);
+                                n.score--;             
+                                ts.add(n);
+                        } else {
+                                n.score--;
+                        }
+                }
+                ts.add(p);
+        }
+
+}

Added: trunk/apps/simsalabim/darknet/DarknetNode.java
===================================================================
--- trunk/apps/simsalabim/darknet/DarknetNode.java                              
(rev 0)
+++ trunk/apps/simsalabim/darknet/DarknetNode.java      2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,323 @@
+package simsalabim.darknet;
+import simsalabim.*;
+import java.util.*;
+import simsalabim.utils.*;
+
+public class DarknetNode extends Node<Darknet, CircleKey> {
+
+       protected LRUHash data;
+
+       protected Person user;
+
+       protected CircleKey pos;
+       protected LinkedList<DarknetNode> neighbors;
+       protected LinkedList<DarknetNode> openNeighbors;
+       final int nOpen;
+
+       private boolean inNetwork = false;
+
+       private int numQueries = 0;
+
+       public DarknetNode(Darknet god, int num, Person user, CircleKey pos,
+                       int nOpen) {
+               super(god, num);
+               this.user = user;
+
+               this.pos = pos;
+               this.nOpen = nOpen;
+
+               data = new LRUHash(god.STORE_SIZE);
+               neighbors = new LinkedList<DarknetNode>();
+               openNeighbors = new LinkedList<DarknetNode>();
+       }
+
+
+
+       public DarknetRoute findRoute(CircleKey k, DarknetRoute dnr,
+                       int maxNoImprove) {
+
+               if (!inNetwork)
+                       throw new Error("Node not in Network routed to: "  + 
this);
+               if (!isActive())
+                       throw new Error("Node that is active routed to: " + 
this);
+
+               /*
+                * This deviates in two ways from true Freenet routing. Firstly 
I don't
+                * backtrack, which I should, but which adds complication. 
Secondly
+                * Freenet doesn't have a fixed maxSteps, but stops after 
taking x steps
+                * without coming closer to the target value.
+                */
+
+               if (dnr == null)
+                       dnr = new DarknetRoute(god.N_BEST);
+
+               dnr.atNode(this, k);
+               numQueries++;
+
+               // terminate on success
+               // if (data.contains(k))
+               // return dnr;
+
+               // find next
+               double bd = Double.MAX_VALUE;
+
+               DarknetNode cand = null;
+               for (Iterator<DarknetNode> it = neighbors() ; it.hasNext() ;) {
+                       DarknetNode t = it.next();
+
+                       if (dnr.contains(t))
+                               continue;
+
+                       double cd = t.pos.dist(k);
+
+                       if (cd < bd) {
+                               bd = cd;
+                               cand = t;
+                       }
+               }
+
+               if (bd < dnr.closest) {
+                       dnr.closest = bd;
+                       dnr.stepsSince = 0;
+                       // dnr.bestNode = cand;
+               } else {
+                       dnr.stepsSince++;
+                       if (dnr.stepsSince > maxNoImprove)
+                               return dnr;
+               }
+
+               if (cand != null)
+                       return cand.findRoute(k, dnr, maxNoImprove);
+               else 
+                       return dnr;
+
+       }
+
+       public int join() {
+               inNetwork = true;
+               user.joinedNetwork(this);
+
+               for (Iterator<Person> it = user.neighbors() ; it.hasNext() ; ) {
+                       Person p = it.next();
+                       if (p.isInNetwork()) {
+                               makeNeighbors(p.getNode());
+                       }
+               }
+
+               int nsw = Math.min(god.size, god.INITIAL_SWITCHES);
+
+               for (int i = 0 ; i < nsw ; i++) {
+                       makeSwitch();
+               }
+
+               return nsw * god.RAND_STEPS;
+
+       }
+
+       public void makeSwitch() {
+               DarknetNode n = getRandomNode();
+               if (n != null) {
+                       double mhVal = Math.min(1, Math.exp((logdist(pos) + 
n.logdist(n.pos) 
+                                       - logdist(n.pos) 
+                                       - n.logdist(pos))));
+
+                       if (god.random().nextDouble() < mhVal) {
+                               CircleKey mp = pos;
+                               pos = n.pos;
+                               n.pos = mp;
+                       } 
+               }
+       }
+
+       public int leave() {
+               inNetwork = false;
+               user.leftNetwork();
+               int n = neighbors.size();
+               
+               // System.err.println("DN Node leaving network.");
+
+               for (Iterator<Person> it = user.neighbors() ; it.hasNext() ; ) {
+                       Person p = it.next();
+                       if (p.isInNetwork()) {
+                               removeNeighbor(p.getNode());
+                       }
+               }
+               
+               if (neighbors.size() != 0)
+                       throw new RuntimeException("Leaving Darknetnode did not 
remove all neighbors.");
+
+               for (Iterator<Data> it = data.data() ; it.hasNext() ;) {
+                       it.next().removedFrom(this);
+               }
+
+               return n;
+       }
+
+       public boolean isSink(CircleKey k) {
+               for (DarknetNode n : neighbors) {
+                       if (k.dist(n.pos) < k.dist(pos))
+                               return false;
+               }
+               return true;
+       }
+
+       public Data findData(CircleKey k) {
+               return data.get(k);
+       }
+
+       public void storeData(Data<CircleKey> d) {
+               d.addedTo(this);
+               Data r = data.put(d);
+               if (r != null)
+                       r.removedFrom(this);
+       }
+
+       public void reLink(DarknetNode d) {
+               // THIS HAS NOT BEEN IMPLEMENTED. NEED TO CONSIDER SYMMETRY!
+
+               if (!openNeighbors.contains(d)) {
+                       if (openNeighbors.size() >= nOpen) 
+                               openNeighbors.removeFirst();
+               }
+       }
+
+       /**
+        * Stores at this node, and depth generation neighbors.
+        */
+       public void explosiveStore(Data d, int depth) {
+               storeData(d);
+               if (depth > 0) {
+                       for (Iterator<DarknetNode> it = neighbors.iterator() ; 
it.hasNext() ;) {
+                               it.next().explosiveStore(d, depth - 1);
+                       }
+               }
+       }
+
+
+       public Iterator<DarknetNode> neighbors() {
+               return new DoubleIterator<DarknetNode>(neighbors.iterator(), 
+                               openNeighbors.iterator());
+       }
+
+       public Person person() {
+               return user;
+       }
+
+       protected DarknetNode getRandomNode() {
+               if (neighbors.size() == 0)
+                       return null; // this; using null to cause crash because 
this is
+                                                       // bad.
+
+               DarknetNode curr = this;
+               for (int i = 0 ; i < god.RAND_STEPS ; i++) {
+                       curr = 
curr.neighbors.get(god.random().nextInt(curr.neighbors.size()));
+               }
+               return curr;
+       }
+
+
+       protected void makeNeighbors(DarknetNode dn) {
+               if (!neighbors.contains(dn)) {
+                       neighbors.add(dn);
+               }
+
+               if (!dn.neighbors.contains(this)) {
+                       dn.neighbors.add(this);
+               }
+       }
+
+       protected void removeNeighbor(DarknetNode dn) {
+               neighbors.remove(dn);
+               dn.neighbors.remove(this);
+       }
+
+       /**
+        * Calculates the log distance to the neighbors of this node from 
newpos. If
+        * a neighbor has position newpos, then it is given my current position.
+        */
+       private double logdist(CircleKey newpos) {
+               double val = 0.0f;
+               for (Iterator<DarknetNode> it = neighbors.iterator() ; 
it.hasNext() ;) {
+                       DarknetNode dn = it.next();
+                       val += Math.log(dn.pos == newpos ? pos.dist(newpos) : 
+                               dn.pos.dist(newpos));
+               }
+               return val;
+       } 
+
+
+       public int fieldnum() {return 6;}
+
+       public String[] fields() {
+               return new String[]{"Num","Position","Degree","Queries", 
"Data", "LogDist"};
+       }
+
+       public String[] info() {
+               return new String[] {
+                               Integer.toString(num), pos.stringVal(), 
+                               Integer.toString(neighbors.size()),
+                               Integer.toString(numQueries), 
Integer.toString(data.size()),
+                               Double.toString(logdist(pos))
+               };
+       }
+
+       public String toString() {
+               return "DN#" + num + " (" + pos + ")";
+       }
+
+
+       /**
+        * Feeds an aggregator called "LinkDistances" with the keyspace 
distance to
+        * each of my long links. Feeds an aggregator called "NodeTraffic" with 
the
+        * number of queries that passed me.
+        */
+       public void feedAggregator(DataAggregator da) {
+               super.feedAggregator(da);
+
+               if (da.name().equals("LinkDistances")) {
+                       for (DarknetNode n : neighbors)
+                               da.next(pos.dist(n.pos));
+               } else if (da.name().equals("NodeTraffic")) {
+                       da.next(numQueries);
+               } 
+       }
+
+
+
+       private class DoubleIterator<T> implements Iterator<T> {
+
+               private Iterator<T> first;
+               private Iterator<T> second;
+               private boolean f;
+
+               public DoubleIterator(Iterator<T> first, Iterator<T> second) {
+                       this.first = first;
+                       this.second = second;
+
+                       f = first.hasNext();            
+               }
+
+               public T next() {
+                       if (f) {
+                               T r = first.next();
+                               f = first.hasNext();
+                               return r;
+                       } else {
+                               return second.next();
+                       }
+               }
+
+               public boolean hasNext() {
+                       return f || second.hasNext();
+               }
+
+               public void remove() {
+                       if (f)
+                               first.remove();
+                       else
+                               second.remove();
+               }
+
+       }
+
+}

Added: trunk/apps/simsalabim/darknet/DarknetRoute.java
===================================================================
--- trunk/apps/simsalabim/darknet/DarknetRoute.java                             
(rev 0)
+++ trunk/apps/simsalabim/darknet/DarknetRoute.java     2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,104 @@
+package simsalabim;
+import simsalabim.*;
+import simsalabim.utils.*;
+import java.util.*;
+
+
+public class DarknetRoute implements Iterable<DarknetNode>, 
Contains<DarknetNode> {
+
+       LinkedList<DarknetNode> route = new LinkedList<DarknetNode>();
+
+       double closest = Double.MAX_VALUE;
+       int stepsSince = 0;
+
+       DarknetNode bestNodes[];
+       
+       private int nBest;
+       
+       public DarknetRoute(int nBest) {
+               this.nBest = nBest;
+               bestNodes = new DarknetNode[nBest];
+       }
+       
+       public void atNode(DarknetNode dn, CircleKey k) {
+               route.add(dn);
+
+               int n = -1;
+               double md = 0;
+               for (int i = 0 ; i < nBest ; i++) {
+                       if (bestNodes[i] == null) {
+                               n = i;
+                               break;
+                       }
+                       double cd = k.dist(bestNodes[i].pos);
+                       if (cd > md) {
+                               n = i;
+                               md = cd;
+                       }
+               }
+
+               if (n >= 0 && (bestNodes[n] == null ||  
+                               k.dist(bestNodes[n].pos) > k.dist(dn.pos))) {
+                       bestNodes[n] = dn;
+               }
+
+       }
+
+
+       public int size() {
+               return route.size();
+       }
+
+       public Iterator<DarknetNode> iterator() {
+               return route.iterator();
+       }
+
+       public boolean contains(DarknetNode dn) {
+               return route.contains(dn);
+       }
+
+       public Data findData(CircleKey k) {
+               for (Iterator<DarknetNode> it = route.iterator() ; it.hasNext() 
;) {
+                       Data d  = it.next().findData(k);
+                       if (d != null)
+                               return d;
+               }
+               return null;
+       }
+
+       public void storeData(Data d) {
+               for (Iterator<DarknetNode> it = route.iterator() ; it.hasNext() 
;) {
+                       it.next().storeData(d);
+               }
+       }
+
+       public void sinkStore(Data<CircleKey> d) {
+               for (DarknetNode n : route) {
+                       if (n.isSink(d.key())) {
+                               n.storeData(d);
+                       }
+               }
+       }
+
+       public void storeBest(Data d) {
+               for (int i = 0 ; i < nBest ; i++) {
+                       if (bestNodes[i] != null)
+                               bestNodes[i].storeData(d);
+               }
+       }
+
+
+       /**
+        * Relinks nodes open neighbors to one of the best nodes with given
+        * probability, if possible.
+        */
+       public void reLink(RandomEngine re, float prob) {
+               for (DarknetNode n : route) {
+                       if (n.nOpen > 0 && re.nextFloat() < prob) {
+                               // THIS IS NOT THIS EASY. I NEED TO FIND AN 
OPEN BEST NODE!
+                               n.reLink(bestNodes[re.nextInt(nBest)]);
+                       }
+               }
+       }
+
+}

Added: trunk/apps/simsalabim/darknet/Person.java
===================================================================
--- trunk/apps/simsalabim/darknet/Person.java                           (rev 0)
+++ trunk/apps/simsalabim/darknet/Person.java   2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,70 @@
+package simsalabim;
+import java.util.*;
+
+    /**
+        * Class for a node in social network.
+        */
+class Person implements Comparable<Person> {
+    
+    public LinkedList<Person> outN = new LinkedList<Person>();
+    public int id;
+    
+    public int score = 0;
+    private DarknetNode node;
+    
+    public Person(int id) {
+       this.id = id;
+    }
+
+    public Iterator<Person> neighbors() {
+       return outN.iterator();
+    }
+
+    public void joinedNetwork(DarknetNode node) {
+       this.node = node;
+    }
+
+    public void leftNetwork() {
+       this.node = null;
+    }
+
+    public boolean isInNetwork() {
+       return node != null;
+    }
+
+    public DarknetNode getNode() {
+       return node;
+    }
+
+    
+    public void addOut(Person n) {
+       if (!outN.contains(n))
+           outN.add(n);
+    }
+    
+    public boolean linksTo(Person n) {
+       return outN.contains(n);
+    }
+    
+    public void removeNeighbor(Person n) {
+       outN.remove(n);
+    }
+
+    
+    public int compareTo(Person o) {
+       return ( score > o.score ? 
+                1 : 
+                ( score < o.score ?
+                  -1 :
+                  ( id > o.id ?
+                    1:
+                    ( id < o.id ?
+                          -1:
+                      0))));
+    }
+
+    public boolean equals(Object o) {
+       return ((Person) o).id == id && ((Person) o).score == score;
+    }
+
+}

Added: trunk/apps/simsalabim/rembre/Rembre.java
===================================================================
--- trunk/apps/simsalabim/rembre/Rembre.java                            (rev 0)
+++ trunk/apps/simsalabim/rembre/Rembre.java    2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,150 @@
+package simsalabim.rembre;
+
+import simsalabim.*;
+import java.lang.reflect.*;
+//import simsalabim.utils.*;
+//import java.util.*;
+
+public class Rembre<K extends PositionKey<K>> extends Simulator<RembreNode<K>, 
K> {
+       
+       int MAX_STEPS;
+       int MAX_NO_IMPROVE;
+       int RT_SIZE;
+       int DS_SIZE;
+       int DS_BYTES;
+       double UPDATE_PROB;
+       String KEYTYPE;
+       
+       private final KeyFactory<K> kf;
+       
+        // This isn't threadsafe anyways, but this much we can do...
+       private ThreadLocal<RembreRoute<K>> troute = new 
ThreadLocal<RembreRoute<K>>() {
+               protected RembreRoute<K> initialValue() {
+                       return new RembreRoute<K>();
+               }
+       };
+
+       public Rembre(RandomEngine re, DataSpace<K> ds, Settings st, 
EventLogger ev, String args) {
+               super(re, ds, st, ev);
+               setParams(st);
+               try {
+                       Method m = 
Class.forName(KEYTYPE).getMethod("getFactory");
+                       if (m == null) {
+                               throw new SimulationException("rbKeyType must 
be a class with a " + 
+                                                       "static getFactory() 
method.");
+                       }
+                       kf = (KeyFactory) m.invoke(null, new Object[] {});
+               } catch (Exception e) {
+                       e.printStackTrace(System.err);
+                       throw new SimulationException("Exception creating 
Factory: " + e);
+               }
+       }
+       
+       private void setParams(Settings st) {
+               MAX_STEPS = st.getInt("rbMaxSteps",100);
+               MAX_NO_IMPROVE = st.getInt("rbMaxNoImprove",10);
+               RT_SIZE = st.getInt("rbRoutingTableSize",25);
+               DS_SIZE = st.getInt("rbDataStoreSize",100);
+               DS_BYTES = st.getInt("rbDataStoreBytes",10000);
+               UPDATE_PROB = st.getDouble("rbUpdateProb",0.1);
+               KEYTYPE = st.get("rbKeyType","simsalabim.CircleKey");
+       }
+
+               
+       @Override
+       public boolean join(RembreNode<K> newNode, RembreNode<K> oldNode, 
Feedback fb) {
+               RembreRoute<K> route = troute.get();
+               K k = newNode.pos();
+               route.clear(k);
+               if (oldNode != null) { // all but first
+                       oldNode.findRoute(k, route);
+                       for (RembreNode<K> n : route) {
+                               n.rtTable.addRoute(k, newNode);
+                               newNode.rtTable.addRoute(n.pos(),n);
+                       }
+                       // System.err.println("Route.size(): " + route.size());
+               }
+               // System.err.println("Joined: oldNode " + oldNode + " 
route.size(): " +
+               // route.size() +
+               // "route.steps(): " + route.steps);
+               fb.feedback(RunTask.JOIN, true, route.size());
+               return true;
+       }
+
+       @Override
+       public void leave(RembreNode n, Feedback fb) {
+               fb.feedback(RunTask.LEAVE, true, 0);
+               // nothing
+       }
+
+       @Override
+       public void maintenance(Feedback fb) {
+               // nothing...
+
+       }
+
+       @Override
+       public K newKey() {
+               K rk = kf.newKey(re.nextLong());
+               if (rk == null)
+                       throw new SimulationException("Keyfactory created null 
key.");
+               return rk;                      
+       }
+
+       @Override
+       public RembreNode<K> newNode(int num) {
+               K pos = newKey();
+               return new RembreNode<K>(this, pos, num);
+       }
+
+       @Override
+       public boolean place(Data<K> d, RembreNode<K> n, Feedback fb) {
+               RembreRoute<K> route = troute.get();
+               route.clear(d.key()); // safety
+               n.findRoute(d.key(), route);
+               updateLinks(route, d.key());
+               route.sinkStore(d);
+               fb.feedback(RunTask.PLACE, true, route.size());
+               // System.err.println("Placed route.size(): " + route.size() +
+               // "route.steps(): " + route.steps);
+               return true;
+       }
+
+       @Override
+       public Data<K> search(K k, RembreNode<K> n, boolean ghost, Feedback fb) 
{
+               if (k == null)
+                       throw new NullPointerException("Search for null key.");
+               RembreRoute<K> route = troute.get();
+               route.clear(k);
+               n.findRoute(k, route);
+               Data<K> d = route.findData(k);
+               if (d != null) {
+                       route.sinkStore(d);
+                       updateLinks(route,k);
+               }
+               fb.feedback(RunTask.SEARCH, d != null, route.size());
+               // System.err.println("Searched route.size(): " + route.size() +
+               // "route.steps(): " + route.steps);
+
+               return d;
+       }
+       
+       private void updateLinks(RembreRoute<K> route, K k) {
+               // find the closest node in the route
+               RembreNode<K> best = null;
+               double bd = Double.MAX_VALUE;
+               for (RembreNode<K> n : route) {
+                       double dist = k.dist(n.pos());
+                       if (dist < bd) {
+                               bd = dist;
+                               best = n;
+                       }
+               }
+               for (RembreNode<K> n : route) {
+                       if (n != best && re.nextDouble() < UPDATE_PROB) {
+                               n.rtTable.addRoute(best.pos(), best);
+                       }
+               }
+       }
+       
+}

Added: trunk/apps/simsalabim/rembre/RembreNode.java
===================================================================
--- trunk/apps/simsalabim/rembre/RembreNode.java                                
(rev 0)
+++ trunk/apps/simsalabim/rembre/RembreNode.java        2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,118 @@
+package simsalabim.rembre;
+
+import simsalabim.*;
+import simsalabim.utils.*;
+
+public class RembreNode<K extends PositionKey<K>> extends Node<Rembre, K> {
+
+       private DataStore<K> datastore;
+       private K pos;
+       
+       final RoutingTable<K, RembreNode<K>> rtTable;
+       
+       private int queries = 0;
+       
+       public RembreNode(Rembre god, K pos, int num) {
+               super(god, num);
+               this.pos = pos;
+               this.rtTable = new RoutingTable<K, RembreNode<K>>(god.RT_SIZE, 
god.random());
+               this.datastore = new DataStore<K>(this, god.DS_SIZE, 
god.DS_BYTES);
+       }
+
+       @Override
+       public Data<K> findData(K k) {
+               if (!isActive()) {
+                       throw new Error("Inactive node probed for data.");
+               }
+                       
+               return datastore.get(k);
+       }
+
+       @Override
+       public void storeData(Data<K> d) {
+               if (!isActive()) {
+                       throw new Error("Inactive node asked to store data.");
+               }
+               datastore.put(d);
+       }
+
+       
+       void findRoute(K k, RembreRoute<K> route) {
+               if (!isActive()) {
+                       System.err.println("Inactive node routed to.");
+                       return;
+               } 
+               queries++;
+               route.atNode(this);
+               if (datastore.contains(k)) {                    
+                       route.dataFound = true;
+               }
+               
+               // System.err.println(god.MAX_STEPS + " route.steps()" + 
route.steps);
+               
+               while(!route.dataFound && route.steps < god.MAX_STEPS && 
+                               route.sni < god.MAX_NO_IMPROVE) {
+                       // System.err.println("Route step: " + route.steps);
+                       RembreNode<K> next;
+                       do {
+                               next = rtTable.findRoute(k, route);
+                               if (next != null && !next.isActive()) {
+                                       rtTable.remove(next.pos());
+                               }
+                       } while (next != null && !next.isActive());
+                       if (next == null)
+                               return;
+                       next.findRoute(k,route);
+               }
+       }
+       
+       public boolean isSink(K k) {
+               return !rtTable.canRouteBetter(k, k.dist(pos));
+       }
+
+               
+       public K pos() {
+               return pos;
+       }
+       
+       
+       @Override
+       protected void activated() {
+               // tell data it is back in the network (if any exists)
+               for (Data<K> d : datastore) {
+                       d.addedTo(this);
+               }
+               super.activated();
+       }
+
+       @Override
+       protected void deactivated() {
+               // tell data is has been removed
+               for (Data<K> d : datastore) {
+                       d.removedFrom(this);
+               }
+               super.deactivated();
+       }
+       
+       public int fieldnum() {
+               return 0;
+       }
+
+       public String[] fields() {
+               return new String[] 
{"Number","Active","Activated","Deactivated","rtSize","dsSize","dsFill","Queries"};
+       }
+
+       public String[] info() {
+               return new String[] {
+                               Integer.toString(this.num),
+                               isActive() ? "1" : "0",
+                               Long.toString(lastActivatedTime),
+                               Long.toString(lastDeactivatedTime),
+                               Integer.toString(rtTable.size()),
+                               Integer.toString(datastore.size()),
+                               Double.toString(datastore.fill()),
+                               Integer.toString(queries)
+               };
+       }
+
+}

Added: trunk/apps/simsalabim/rembre/RembreRoute.java
===================================================================
--- trunk/apps/simsalabim/rembre/RembreRoute.java                               
(rev 0)
+++ trunk/apps/simsalabim/rembre/RembreRoute.java       2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,102 @@
+package simsalabim.rembre;
+import simsalabim.*;
+import simsalabim.utils.Contains;
+import simsalabim.utils.Heap;
+import java.util.*;
+
+public class RembreRoute<K extends PositionKey<K>> implements 
Contains<RembreNode<K>>, Iterable<RembreNode<K>> {
+
+       KeyComparator<K> kc = new KeyComparator<K>();
+       
+       Collection<RembreNode<K>> route = new HashSet<RembreNode<K>>();
+       Heap<RembreNode<K>> nh = new Heap<RembreNode<K>>(kc);
+       
+       double closest = Double.MAX_VALUE;
+       int sni = 0;
+       int steps= 0;
+       K target;
+       boolean dataFound = false;
+       
+       public void atNode(RembreNode<K> dn) {
+               route.add(dn);
+               steps++;
+               sni++;
+               if (target == null)
+                       throw new NullPointerException("Target is null.");
+               double dist = target.dist(dn.pos());
+               if (dist < closest) {
+                       // System.err.println("New best: " + dist + " < " + 
closest);
+                       sni = 0;
+                       closest = dist; 
+               }
+       }
+       
+       public void clear(K target) {
+               this.dataFound = false;
+               this.target = target;
+               route.clear();
+               kc.setTarget(target);
+               nh.setComparator(kc);
+               closest = Double.MAX_VALUE;
+               sni = 0;
+               steps = 0;
+       }
+
+       public int steps() {
+               return steps;
+       }
+       
+       public int size() {
+               return route.size();
+       }
+
+       public Iterator<RembreNode<K>> iterator() {
+               return route.iterator();
+       }
+
+       public boolean contains(RembreNode dn) {
+               return route.contains(dn);
+       }
+
+       public Data<K> findData(K k) {
+               for (Iterator<RembreNode<K>> it = route.iterator() ; 
it.hasNext() ;) {
+                       Data<K> d  = it.next().findData(k);
+                       if (d != null)
+                               return d;
+               }
+               return null;
+       }
+
+       public void storeData(Data<K> d) {
+               for (Iterator<RembreNode<K>> it = route.iterator() ; 
it.hasNext() ;) {
+                       it.next().storeData(d);
+               }
+       }
+
+       public void sinkStore(Data<K> d) {
+               for (RembreNode<K> n : route) {
+                       if (n.isSink(d.key())) {
+                               n.storeData(d);
+                       }
+               }
+       }
+       
+       
+       
+       private static class KeyComparator<K extends PositionKey<K>> implements 
Comparator<RembreNode<K>> {
+               
+               private K target;
+               
+               public void setTarget(K target) {
+                       this.target = target;
+               }
+               
+               public int compare(RembreNode<K> n1, RembreNode<K> n2) {
+                       double d = target.dist(n1.pos()) - 
target.dist(n2.pos());
+                       return d < 0 ? -1 : d == 0 ? 0 : 1; 
+               }
+               
+       }
+
+}
+

Added: trunk/apps/simsalabim/rembre/test.java
===================================================================
--- trunk/apps/simsalabim/rembre/test.java                              (rev 0)
+++ trunk/apps/simsalabim/rembre/test.java      2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,34 @@
+package simsalabim.rembre;
+
+import java.util.*;
+
+public class test {
+
+       static long M = 0x7FFFFFFFFFFFFFFFl;
+
+       public static void main(String[] args) {
+               Random r = new Random();
+
+               long n1 = r.nextLong();
+
+               int[] ct = new int[10];
+               for (int i = 0; i < 10000; i++) {
+                       double val = ((double) (r.nextLong() & M) / (double) 
Long.MAX_VALUE);
+                       ct[(int) (val * 10)]++;
+               }
+               for (int i = 0; i < 10; i++) {
+                       System.err.println(ct[i] + "\t" + (ct[i] / 10000.0));
+               }
+       }
+
+       public static String toBString(long n) {
+               StringBuffer sb = new StringBuffer();
+               for (int i = 0; i < 64; i++) {
+                       sb.append(n & 1);
+                       n >>= 1;
+                       // System.err.println("t" + n + "\t" + 
Integer.toBinaryString(n));
+               }
+               return sb.reverse().toString();
+       }
+
+}

Added: trunk/apps/simsalabim/utils/Contains.java
===================================================================
--- trunk/apps/simsalabim/utils/Contains.java                           (rev 0)
+++ trunk/apps/simsalabim/utils/Contains.java   2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,13 @@
+package simsalabim.utils;
+/**
+ * Interface for any class with a boolean contains() methods.
+ * 
+ * @author ossa
+ * 
+ * @param <T>
+ *            The type of object it might contain.
+ */
+public interface Contains<T> {
+
+       public boolean contains(T t);
+}

Added: trunk/apps/simsalabim/utils/DataStore.java
===================================================================
--- trunk/apps/simsalabim/utils/DataStore.java                          (rev 0)
+++ trunk/apps/simsalabim/utils/DataStore.java  2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,201 @@
+package simsalabim.utils;
+import simsalabim.*;
+import java.util.*;
+
+/**
+ * Simple LRU type data storage.
+ */
+public class DataStore<K extends Key> implements Iterable<Data<K>>{
+
+       public static void main(String[] args) {
+               final Random r = new Random();
+
+               Data<Key>[] d = new Data[100];
+               for (int i = 0 ; i < d.length ; i++) {
+                       Key k = new Key() {
+                               double val = r.nextDouble();
+                               public double ident() {
+                                       return val;
+                               }
+                               public String stringVal() {
+                                       return Double.toString(val);
+                               }                               
+                       };
+                       d[i] = new Data<Key>(k, 100, 0);
+               }
+
+               int[] use = new int[1000];
+               for (int i = 0 ; i < use.length ; i++) {
+                       use[i] = r.nextInt(d.length);
+               }
+
+               DataStore<Key> ds = new DataStore<Key>(null, 100,1000);
+
+
+               long time = System.currentTimeMillis();
+               int count = 0;
+               for (int i = 0 ; i < 1000000 ; i++) {
+                       ds.put(d[i % d.length]);
+                       count += ds.get(d[use[i % use.length]].key()) != null ? 
1 : 0;
+               }
+
+
+               System.err.println("Took: " + (System.currentTimeMillis() - 
time));
+               System.err.println("Got: " + (((float) count) / 1000000.0));
+
+       }
+
+       private static class DNode<K extends Key> {
+
+               private static int me = 0;
+
+               int num = ++me;
+               Data<K> d;
+               DNode<K> prev;
+               DNode<K> next;
+
+               public String toString() {
+                       return "dn " + num;
+               }
+       }
+
+       private class DIterator implements Iterator<Data<K>> {
+               DNode<K> curr;
+
+               public DIterator(DNode<K> first) {
+                       curr = first;
+               }
+
+               public boolean hasNext() {
+                       return curr != null && curr.next != null;
+               }
+
+               public Data<K> next() {
+                       Data<K> r = curr.d;
+                       curr = curr.next;
+                       return r;
+               }
+
+               public void remove() {
+                       throw new Error("DataStore iterator remove not 
implemented");
+               }
+       }
+
+       private int maxBytes;
+       private int currBytes = 0;
+       
+       private int maxSize;
+       private HashMap<K, DNode<K>> data;
+
+       DNode<K> first;
+       DNode<K> last;
+       
+       private Node owner;
+
+       public DataStore(Node owner, int size, int maxBytes) {
+               this.owner = owner;
+               maxSize = size;
+               this.maxBytes = maxBytes;
+               data = new HashMap<K, DNode<K>>(size);
+
+       }
+
+       public void put(Data<K> d) {
+               DNode<K> dn = data.get(d.key());                
+               if (dn != null) { // swapdata
+                       if (dn.d == d) {
+                               bump(dn);
+                               return;
+                       }
+                       currBytes -= dn.d.size();
+                       if (owner != null)
+                               dn.d.removedFrom(owner);
+                       dn.d = d;
+                       bump(dn);
+               } else {
+                       dn = new DNode<K>();
+                       dn.d = d;
+
+                       data.put(d.key(), dn);
+
+                       if (first == null) {
+                               first = dn;
+                               last = dn;
+                       } else {
+                               dn.next = first;
+                               first.prev = dn;
+                               first = dn;
+                       }
+               }               
+               if (owner != null)
+                       d.addedTo(owner);
+               currBytes += d.size();
+
+
+               while (data.size() > maxSize || currBytes > maxBytes) {
+                       // remove oldest
+                       data.remove(last.d.key());
+                       currBytes -= last.d.size();
+                       if (owner != null)
+                               last.d.removedFrom(owner);
+                       last = last.prev;
+                       if (last == null)
+                               first = null;
+                       else
+                               last.next = null;
+               }
+       }
+
+       public Data<K> get(K k) {
+               DNode<K> dn = data.get(k);
+               if (dn == null)
+                       return null;
+
+               bump(dn);
+               return dn.d;
+       }
+       
+       private void bump(DNode<K> dn) {
+               if (dn.prev != null) {
+
+                       dn.prev.next = dn.next;
+
+                       if (dn.next == null)
+                               last = dn.prev;
+                       else
+                               dn.next.prev = dn.prev;
+
+
+                       dn.prev = null;
+                       first.prev = dn;
+                       dn.next = first;
+                       first = dn;
+               }
+       }
+
+       public boolean contains(K k) {
+               return data.containsKey(k);
+       }
+
+       public Iterator<Data<K>> iterator() {
+               return new DIterator(first);
+       }
+       
+       public int size() {
+               return data.size();
+       }
+       
+       public double fill() {
+               return currBytes / (double) maxBytes;
+       }
+/*
+ * private int count() { int count = 0; DNode<K> c = first; do {
+ * //System.err.println(c); count++; c = c.next; } while (c != null); return
+ * count; }
+ * 
+ * private int bcount() { int count = 0; DNode<K> c = last; do {
+ * //System.err.println(c); count++; c = c.prev; } while (c != null); return
+ * count; }
+ * 
+ */
+}

Added: trunk/apps/simsalabim/utils/Heap.java
===================================================================
--- trunk/apps/simsalabim/utils/Heap.java                               (rev 0)
+++ trunk/apps/simsalabim/utils/Heap.java       2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,180 @@
+package simsalabim.utils;
+import java.util.*;
+/**
+ * I can't believe I have to code this.
+ * 
+ * @author ossa
+ * 
+ */
+
+public class Heap<E> {
+       
+       private static Comparator ncomp = new Comparator() {
+               public int compare(Object o1, Object o2) {
+                       if (o1 == null)
+                               throw new Error("o1 was null.");
+                       if (o2 == null)
+                               throw new Error("o2 was null");
+                       return ((Comparable)o1).compareTo((Comparable) o2);
+               }
+               
+               public boolean equals(Object o1, Object o2) {
+                       return compare(o1, o2) == 0;
+               }
+       };
+       
+       public static void main(String[] args) {
+               Heap<Integer> h = new Heap<Integer>();
+               Random r = new Random();
+               for (int i = 0 ; i < 32 ; i++) {
+                       h.add(r.nextInt(1000));                 
+               }
+               
+               for (int i = 0 ; i < 16 ; i++) {
+                       System.err.println(h.removeFirst());
+               }
+               
+               System.err.println("Adding some more:");
+               
+               for (int i = 0 ; i < 16 ; i++) {
+                       h.add(r.nextInt(1000));
+               }
+               for (int i = 0 ; i < 33 ; i++) {
+                       System.err.println(h.removeFirst());
+               }
+       }
+       
+       
+       Comparator<? super E> c;
+       Vector<E> heap = new Vector<E>(); 
+       private int size = 0;
+       
+       public Heap(Comparator<? super E> c) {
+               this.c = c;
+               heap.setSize(1);
+       }
+       
+       public Heap() {
+               this.c = ncomp;
+               heap.setSize(1);
+       }
+       
+       
+       public E first() {
+               return heap.get(1);
+       }
+       
+       public E removeFirst() {
+               E first = heap.get(1);
+               fill(1);
+               size--;
+               return first;
+       }
+       
+       public void add(E e) {
+               replace(1,e);
+               size++;
+       }
+       
+       public int size() {
+               return size;
+       }
+       
+       /**
+        * Clears heap
+        */
+       public void clear() {
+               heap.clear();
+               heap.setSize(1);
+               size = 0;
+       }
+       
+       /**
+        * Clears heap and sets a new comparator.
+        * 
+        * @param c
+        */
+       public void setComparator(Comparator<? super E> c) {
+               clear();
+               this.c = c;
+       }
+       
+       /**
+        * Fills the spot i with an element from higher in the heap (or set to 
null
+        * if leaf).
+        * 
+        * @param i
+        */
+       private void fill(int i) {
+               int i1 = i*2;
+               int i2 = i*2 + 1;
+               E p1 = i1 >= heap.size() ? null : heap.get(i1);
+               E p2 = i2 >= heap.size() ? null : heap.get(i2);
+               if (p1 == null && p2 == null) { // leaf
+                       heap.set(i,null);
+               } else if (p2 == null || (p1 != null && c.compare(p1, p2) < 0)) 
{                       
+                       heap.set(i, heap.get(i1));
+                       fill(i1);
+               } else {
+                       heap.set(i, heap.get(i2));
+                       fill(i2);
+               }
+       }
+
+       /**
+        * Add e to the tree, replacing an element at i or below
+        * 
+        * @param i
+        * @param e
+        */
+       private void replace(int i, E e) {
+               if (i >= heap.size()) {
+                       heap.setSize(i + 1);
+                       heap.set(i,e);
+                       return;
+               }
+               E n = heap.get(i);
+               if (n == null) {
+                       heap.set(i,e);
+                       return;
+               }
+               int i1 = i*2;
+               int i2 = i*2 + 1;
+               E p1 = i1 >= heap.size() ? null : heap.get(i1);
+               E p2 = i2 >= heap.size() ? null : heap.get(i2);
+               
+               if (c.compare(e, n) < 0) {
+                       heap.set(i,e);
+                       if (p2 == null || (p1 != null && c.compare(p1, p2) < 
0)) {
+                               replace(i2,n);
+                       } else {
+                               replace(i1,n);
+                       }
+               } else {
+                       if (p2 == null || (p1 != null && c.compare(p1, p2) < 
0)) {
+                               replace(i1,e);
+                       } else {
+                               replace(i2,e);
+                       }
+               }
+       }
+       
+       /**
+        * Moves the element at spot i up to its correct position in the heap.
+        * 
+        * @param i
+        */
+       private void moveUp(int i) {
+               int pi = i/2;
+               E n = heap.get(i);
+               E p = heap.get(pi);
+               if (i == 0 || c.compare(n,p) < 0) 
+                       return;
+               else {
+                       heap.set(i, p);
+                       heap.set(pi, n);
+                       moveUp(pi);
+               }               
+       }
+       
+}

Added: trunk/apps/simsalabim/utils/LRUHash.java
===================================================================
--- trunk/apps/simsalabim/utils/LRUHash.java                            (rev 0)
+++ trunk/apps/simsalabim/utils/LRUHash.java    2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,174 @@
+package simsalabim.utils;
+import simsalabim.*;
+import java.util.*;
+
+/*
+ * I implement this for the millionth time...
+ */
+
+public class LRUHash {
+
+       public static void main(String[] args) {
+               final Random r = new Random();
+
+               Data[] d = new Data[200];
+               for (int i = 0 ; i < d.length ; i++) {
+                       Key k = new Key() {
+                               double val = r.nextDouble();
+                               public double ident() {
+                                       return val;
+                               }
+                               public String stringVal() {
+                                       return Double.toString(val);
+                               }                               
+                       };
+                       d[i] = new Data(k, 100, 0);
+               }
+
+               int[] use = new int[1000];
+               for (int i = 0 ; i < use.length ; i++) {
+                       use[i] = r.nextInt(d.length);
+               }
+
+               LRUHash ds = new LRUHash(100);
+
+
+               long time = System.currentTimeMillis();
+               int count = 0;
+               for (int i = 0 ; i < 1000000 ; i++) {
+                       ds.put(d[i % d.length]);
+                       count += ds.get(d[use[i % use.length]].key()) != null ? 
1 : 0;
+               }
+
+
+               System.err.println("Took: " + (System.currentTimeMillis() - 
time));
+               System.err.println("Got: " + (((float) count) / 1000000.0));
+
+       }
+
+       protected HashMap<Key, DataBucket> data = new HashMap<Key, 
DataBucket>();
+
+       protected DataBucket topData;
+       protected DataBucket bottomData;
+
+       protected int maxSize;
+
+       public LRUHash(int maxSize) {
+               this.maxSize = maxSize;
+       }
+
+       public Data get(Key k) {
+               DataBucket db = data.get(k);
+               if (db != null)
+                       moveUp(db);
+               return db == null ? null : db.d;
+       }
+
+       public boolean contains(Key k) {
+               return data.containsKey(k);
+       }
+
+       public Data put(Data d) {
+               if (data.containsKey(d.key())) {
+                       moveUp(data.get(d.key()));
+                       return null;
+               } else {
+                       DataBucket db = new DataBucket(d);      
+                       data.put(d.key(), db);
+
+                       addTop(db);
+                       return shrinkToSize();
+               }
+       }
+
+       public Data remove(Key k) {
+               DataBucket db = data.get(k);
+               if (db != null) {
+                       data.remove(k);
+                       removeFromList(db);
+                       return db.d;
+               } else
+                       return null;
+       }
+
+       public Iterator<Data> data() {
+               return new DataIterator(data.values().iterator());
+       }
+
+       public int size() {
+               return data.size();
+       }
+
+       private void addTop(DataBucket db) {
+               db.below = topData;
+               if (topData != null) 
+                       topData.above = db;
+               topData = db;
+               if (bottomData == null)
+                       bottomData = db;
+       }
+
+       private void removeFromList(DataBucket db) {
+               if (db.above != null) {
+                       db.above.below = db.below;
+               } else {
+                       topData = db.below;
+               }
+
+               if (db.below != null) {
+                       db.below.above = db.above;
+               } else {
+                       bottomData = db.above;
+               }
+
+               db.above = null;
+               db.below = null;
+       }
+
+       private void moveUp(DataBucket db) {
+
+               removeFromList(db);
+               addTop(db);
+
+       }
+
+       private Data shrinkToSize() {
+               if (data.size() > maxSize) {
+                       DataBucket db = bottomData;
+                       data.remove(db.d.key());
+                       removeFromList(db);
+                       return db.d;
+               } else
+                       return null;
+       }
+
+       private class DataBucket {
+               public Data d;
+
+               public DataBucket above;
+               public DataBucket below;
+
+               public DataBucket(Data d) {
+                       this.d = d;
+               }
+       }
+
+       private class DataIterator implements Iterator<Data> {
+               Iterator<DataBucket> it;
+               public DataIterator(Iterator<DataBucket> it) {
+                       this.it = it;
+               }
+
+               public boolean hasNext() {
+                       return it.hasNext();
+               }
+
+               public Data next() {
+                       return it.next().d;
+               }
+
+               public void remove() {
+                       it.remove();
+               }
+       }
+}

Added: trunk/apps/simsalabim/utils/RandTester.java
===================================================================
--- trunk/apps/simsalabim/utils/RandTester.java                         (rev 0)
+++ trunk/apps/simsalabim/utils/RandTester.java 2009-02-11 13:53:49 UTC (rev 
25585)
@@ -0,0 +1,40 @@
+package simsalabim.utils;
+
+import simsalabim.*;
+import java.util.*;
+
+public class RandTester {
+
+       /**
+        * @param args
+        */
+       public static void main(String[] args) {
+               RandomEngine re = new MersenneTwister((int) 
System.currentTimeMillis());
+               int N = 300; 
+               int[] f = new int[N];
+               int[] k = new int[N];
+               for (int i = 0 ; i < N ; i++)
+                       k[i] = i;
+               HashSet<Integer> hs = new HashSet<Integer>();
+               for (int i = 0 ; i < 100 * N ; i++) {                   
+                       int num = (int) Math.pow(N, re.nextDouble());
+                       int lowest = N+1;
+
+                       for (int j = 0 ; j < num ; j++) {
+                               int idx = j + re.nextInt(N - j);
+                               int t = k[j];
+                               k[j] = k[idx];
+                               k[idx] = t;
+                               if (k[j] < lowest)
+                                       lowest = k[j];
+                       }
+                       f[lowest]++;
+                       hs.clear();
+               }
+               
+               StringBuffer sb = new StringBuffer();
+               for (int n : f)
+                       sb.append(n).append(" ");
+               System.err.println(sb.toString());
+       }
+}

Added: trunk/apps/simsalabim/utils/RoutingTable.java
===================================================================
--- trunk/apps/simsalabim/utils/RoutingTable.java                               
(rev 0)
+++ trunk/apps/simsalabim/utils/RoutingTable.java       2009-02-11 13:53:49 UTC 
(rev 25585)
@@ -0,0 +1,112 @@
+package simsalabim.utils;
+import simsalabim.*;
+import java.util.*;
+
+public class RoutingTable<K extends PositionKey, N extends Node> {
+       
+       private Vector<N> routes;
+       private Vector<K> keys;
+       private int currSize = 0;
+       private int maxSize;
+       private RandomEngine random;
+       
+       public RoutingTable(int rtSize, RandomEngine random) {
+               routes = new Vector<N>(rtSize);
+               keys = new Vector<K>(rtSize);
+               maxSize = rtSize;
+               this.random = random;
+       }
+       
+       /**
+        * Finds the element with the closest key to k.
+        * 
+        * @param k
+        *            The key to route for.
+        * @param previous
+        *            A collection of nodes to avoid (previously tried). May be
+        *            null.
+        * @return The node in the routing table, and not in the previous
+        *         collection, or null.
+        */
+       public N findRoute(K k, Contains<N> previous) {
+               double best = Double.MAX_VALUE;
+               N fn = null;
+               for (int i = 0 ; i < currSize ; i++) {
+                       K key = keys.get(i);
+                       N node = routes.get(i);
+
+                       double d = key.dist(k);
+                       if (d < best && (previous != null && 
!previous.contains(node))) {
+                               fn = routes.get(i);
+                               best = d;
+                       }
+               }
+               return fn;
+       }
+       
+       /**
+        * Adds a route, replacing a random old one if the table is full or if a
+        * previous route exists for the given key.
+        * 
+        * @param key
+        *            The key to associate the node with.
+        * @param node
+        *            The node to add to the routing table.
+        * @return A node which was replaced.
+        */
+       public N addRoute(K key, N node) {
+               int pndx = keys.indexOf(key);
+               if (pndx != -1) {
+                       N n = routes.get(pndx);
+                       routes.set(pndx, node);
+                       return n;
+               } else if (currSize < maxSize) {
+                       routes.add(currSize,node);
+                       keys.add(currSize, key);
+                       currSize++;
+                       return null;
+               } else {
+                       int ndx = random.nextInt(currSize);
+                       N n = routes.get(ndx);
+                       routes.set(ndx, node);
+                       keys.set(ndx,key);
+                       return n;
+               }
+       }
+       
+       /**
+        * Removes an element.
+        * 
+        * @param k
+        *            The key of the element to remove.
+        * @return The element that was removed, or null if no element matching 
k
+        *         was found.
+        */
+       public N remove(K k) {
+               int ndx = keys.indexOf(k);
+               if (ndx != -1) {
+                       currSize--;
+                       N n = routes.get(ndx);
+                       keys.set(ndx, keys.get(currSize));
+                       routes.set(ndx, routes.get(currSize));
+                       keys.remove(currSize);
+                       routes.remove(currSize);
+                       return n;
+               } else
+                       return null;            
+       }
+       
+       public int size() {
+               return routes.size();
+       }
+       
+       public boolean canRouteBetter(K k, double dist) {
+               for (int i = 0 ; i < currSize ; i++) {
+                       if (k.dist(keys.get(i)) < dist) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
+}


Reply via email to