001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.algorithm.randomWalk; 033 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Iterator; 037import java.util.Comparator; 038import java.util.Random; 039 040import org.graphstream.algorithm.DynamicAlgorithm; 041import org.graphstream.graph.Edge; 042import org.graphstream.graph.Graph; 043import org.graphstream.graph.Node; 044import org.graphstream.stream.SinkAdapter; 045 046import static org.graphstream.algorithm.Toolkit.*; 047 048/** 049 * A random walk on a graph. 050 * 051 * <h3>Idea</h3> 052 * 053 * <p> 054 * This algorithm create a given number of entities first associated with random 055 * nodes in the graph. Then by turns, each entity chooses an edge at random and 056 * crosses it. This is iterated a given number of turns. Each time an entity 057 * crosses an edge, a count is incremented on it and each time it arrives on 058 * a node a count is counted on it. 059 * </p> 060 * 061 * <p> 062 * You can override the entity class to provide your own behaviour for entity 063 * movement. 064 * </p> 065 * 066 * <h3>Counts on edges and nodes</h3> 067 * 068 * <p> 069 * If the algorithm was run for an infinite number of turns, each counter would 070 * have the same value. However we can choose to stop the algorithm when needed. 071 * Furthermore the algorithm can be biased by providing each entity with a 072 * memory of the already crossed edges. It can avoid these edges when choosing 073 * at random its next edge. 074 * </p> 075 * 076 * <p> 077 * When an entity has no edge to choose (either because of its memory or because 078 * it reached a node that is only reachable via a one directed edge), the entity 079 * will jump randomly on another node. 080 * </p> 081 * 082 * <p> 083 * When the number of turns 084 * awaited is reached, one can observe the counts on each edge and node. Edges 085 * and nodes that are very attractive in terms of topology should have a more 086 * important count than others. 087 * </p> 088 * 089 * <p> 090 * This algorithm does not cope well with dynamic graphs. You can however improve 091 * this by using evaporation. When evaporation is activated, at each turn, the 092 * node and edge counts are multiplied by a number between 0 and 1. Therefore each 093 * edge or node count must be constantly updated by entities leading to a value that 094 * stabilizes in time. 095 * </p> 096 * 097 * <h3>The basic tabu entity</h3> 098 * 099 * <p> 100 * At each step, the default entities move from their current node to another via 101 * an edge randomly chosen. This is done in the {@link Entity#step()} method. 102 * </p> 103 * 104 * <p> 105 * This method makes a list of all leaving edges of the current node. If the 106 * node has no leaving edge, the entity jumps to another randomly chosen node. 107 * Then an edge is chosen at random in the list of leaving edges. The edge is 108 * chosen uniformly if there are no weights on the edges, else, an edge with 109 * an higher weight has more chances to be chosen than an edge with a lower 110 * weight. 111 * </p> 112 * 113 * <p> 114 * When crossed, if the memory is larger than 0, the edge crossed is remembered 115 * so that the entity will not choose it anew until it crosses as many edges as 116 * the memory size. 117 * </p> 118 * 119 * <h2>Usage</h2> 120 * 121 * <p> 122 * With the default entities, you can make a node entirely tabu by putting the 123 * ``tabu`` attribute on it. No entity will traverse an edge that leads 124 * to such a node. 125 * </p> 126 * 127 * <p> 128 * You can change the default entity class either by overriding the 129 * {@link #createEntity()} method or by changing the entity class name 130 * using {@link #setEntityClass(String)}. 131 * </p> 132 * 133 * <p> 134 * If the edges have weights, the entities can use them to favour edges 135 * with higher weights when randomly choosing them. By default the 136 * weights are searched on edges using the ``weight`` attribute. However 137 * you can override this using {@link #setWeightAttribute(String)} method. 138 * </p> 139 * 140 * <p> 141 * If you choose to have evaporation on edge counts at each turn, you can 142 * set it using {@link #setEvaporation(double)}. The evaporation is a number 143 * between 0 and 1. If set to 1 (the default), the counts are not modified, 144 * else the counts are multiplied by the evaporation at each turn. 145 * </p> 146 * 147 * <p> 148 * To compute a turn, use the {@link #compute()} method. This will move each 149 * entity from one node to another. 150 * </p> 151 * 152 * <p> 153 * Once computed each edge and node will have an attribute ``passes`` stored 154 * on it containing the number of passage of an entity. You can change the 155 * name of this attribute using {@link #setPassesAttribute(String)}. After 156 * each computation of a turn, you can obtain the edge and nodes counts using 157 * either the passes attribute, or the utility methods {@link #getPasses(Node)} 158 * and {@link #getPasses(Edge)}. 159 * </p> 160 * 161 * <p> 162 * You can count only the passes on the nodes or edges using the two methods 163 * {@link #computeEdgesPasses(boolean)} and {@link #computeNodePasses(boolean)}. 164 * </p> 165 * 166 * <p> 167 * As some entities may have jumped from their node to another one chosen 168 * randomly, you can obtain the number of entities that jumped using 169 * {@link #getJumpCount()}. 170 * </p> 171 * 172 * <h2>Complexity</h2> 173 * 174 * The complexity, at each turn is O(n) with n the number of entities. 175 * 176 * <h2>Example</h2> 177 * 178 * Here is how to compute a simple pass count for 1000 steps: 179 * 180 * <pre> 181 * Graph graph = new MultiGraph("random walk"); 182 * RandomWalk rwalk = new RandomWalk(); 183 * // Populate the graph. 184 * rwalk.setEntityCount(graph.getNodeCount()/2); 185 * rwalk.init(graph); 186 * for(int i=0; i<1000; i++) { 187 * rwalk.compute(); 188 * } 189 * rwalk.terminate(); 190 * for(Edge edge: graph.getEachEdge()) { 191 * System.out.println("Edge %s counts %f%n", edge.getId(), rwalk.getPasses(edge)); 192 * } 193 * </pre> 194 */ 195public class RandomWalk extends SinkAdapter implements DynamicAlgorithm { 196 // Attribute 197 198 public class Context { 199 /** 200 * The graph. 201 */ 202 protected Graph graph; 203 204 /** 205 * The name of the attribute used to count the number of pass on an edge. 206 */ 207 protected String passesAttribute = "passes"; 208 209 /** 210 * The name of the attribute on edges that give their respective importance. 211 */ 212 protected String weightAttribute = null; 213 214 /** 215 * Random number generator. 216 */ 217 protected Random random; 218 219 /** 220 * The node tabu list. 221 */ 222 protected int entityMemory = 0; 223 224 /** 225 * Number of entities that jump at each step. 226 */ 227 protected int jumpCount = 0, goCount = 0, waitCount = 0; 228 229 public String getPassesAttribute() { 230 return passesAttribute; 231 } 232 233 public String getWeightAttribute() { 234 return weightAttribute; 235 } 236 237 public Random getRandom() { 238 return random; 239 } 240 241 public int getEntityMemory() { 242 return entityMemory; 243 } 244 } 245 246 /** 247 * The informations shared between this class an entities. 248 */ 249 protected Context context = new Context(); 250 251 /** 252 * The entity class to use. 253 */ 254 protected String entityClass = TabuEntity.class.getName();// "org.graphstream.algorithm.RandomWalk#TabuEntity"; 255 256 /** 257 * The set of entities travelling on the graph. 258 */ 259 protected ArrayList<Entity> entities = new ArrayList<Entity>(); 260 261 /** 262 * The random seed. 263 */ 264 protected long randomSeed; 265 266 /** 267 * Initial count of entities. 268 */ 269 protected int entityCount = 100; 270 271 /** 272 * Allow to reduce the amount counted on each edge at each turn. At each turn 273 * the edges counts are multiplied by the evaporation. 274 */ 275 protected double evaporation = 1; 276 277 /** 278 * Compute counts on nodes. 279 */ 280 protected boolean doNodes = true; 281 282 /** 283 * Compute counts on edges. 284 */ 285 protected boolean doEdges = true; 286 287 // Constructor 288 289 /** 290 * New random walk with a new random seed (based on time), with an entity 291 * memory set to 10 nodes (tabu list), with an attributes to store passes 292 * named "passes" and no weight attribute. 293 */ 294 public RandomWalk() { 295 this(System.currentTimeMillis()); 296 } 297 298 /** 299 * New random walk with a given random seed, with an entity memory set to 10 300 * nodes (tabu list), with an attributes to store passes named "passes" and 301 * no weight attribute. 302 * 303 * @param randomSeed 304 * The random seed. 305 */ 306 public RandomWalk(long randomSeed) { 307 this.randomSeed = randomSeed; 308 this.context.random = new Random(randomSeed); 309 } 310 311 /** 312 * The name of the attribute where the number of entities passes are stored 313 * (for edges and nodes). 314 * 315 * @return A string representing the attribute name for entity passes. 316 */ 317 public String getPassesAttribute() { 318 return context.passesAttribute; 319 } 320 321 /** 322 * Set the name of the entity class to use. If set to null, the default entity 323 * class will be used (RandomWalk.TabuEntity). 324 * @param name The name of the entity class to use. 325 */ 326 public void setEntityClass(String name) { 327 if(name == null) { 328 entityClass = TabuEntity.class.getName();//"org.graphstream.algorithm.RandomWalk#TabuEntity"; 329 } else { 330 entityClass = name; 331 } 332 } 333 334 /** 335 * Set the entity memory in number of nodes remembered. This memory is used 336 * as a tabu list, that is a set of nodes not to cross. 337 * 338 * @param size 339 * The memory size, 0 is a valid size to disable the tabu list. 340 */ 341 public void setEntityMemory(int size) { 342 if (size < 0) 343 size = 0; 344 345 context.entityMemory = size; 346 } 347 348 /** 349 * Set the evaporation of edge counts. This is a number between 0 and 1. If less 350 * than 1, at each turn, each edge count is multiplied by this factor. The use 351 * of evaporation allows to stabilize the counts. 352 * @param evaporation A number between 0 and 1. 353 */ 354 public void setEvaporation(double evaporation) { 355 if(evaporation>=0 && evaporation<1) { 356 this.evaporation = evaporation; 357 } 358 } 359 360 /** 361 * The evaporation value. 362 * @return The evaporation. 363 */ 364 public double getEvaporation() { 365 return evaporation; 366 } 367 368 /** 369 * The random seed used. 370 * 371 * @return A long integer containing the random seed. 372 */ 373 public long getRandomSeed() { 374 return randomSeed; 375 } 376 377 /** 378 * Number of entities. 379 * 380 * @return The number of entities. 381 */ 382 public int getEntityCount() { 383 return entities.size(); 384 } 385 386 /** 387 * Number of entities that jumped instead of traversing an edge at last 388 * step. An entity executes a jump when it is blocked in a dead end (either 389 * a real one, or because of its tabu list). 390 * 391 * @return The jump count. 392 */ 393 public int getJumpCount() { 394 return context.jumpCount; 395 } 396 397 public int getWaitCount() { 398 return context.waitCount; 399 } 400 401 public int getGoCount() { 402 return context.goCount; 403 } 404 405 /** 406 * Ratio of entities that executed a jump instead of traversing an edge at 407 * last step. An entity executes a jump when it is blocked in a dead end 408 * (either a real one, or because of its tabu list). 409 * 410 * @return The jump ratio (in [0-1]). 411 */ 412 public double getJumpRatio() { 413 return (((double) context.jumpCount) / ((double) entities.size())); 414 } 415 416 /** 417 * The name of the attribute used to fetch edges importance. 418 * 419 * @param name 420 * A string giving the weight name. 421 */ 422 public void setWeightAttribute(String name) { 423 context.weightAttribute = name; 424 } 425 426 /** 427 * Set the name of the attribute used to store the number of passes of each 428 * entity on each edge or node. 429 * 430 * @param name 431 * A string giving the passes name. 432 */ 433 public void setPassesAttribute(String name) { 434 if (context.graph != null) { 435 for (Edge e : context.graph.getEachEdge()) { 436 e.addAttribute(name, e.getNumber(context.passesAttribute)); 437 e.removeAttribute(context.passesAttribute); 438 } 439 440 for (Node n : context.graph) { 441 n.addAttribute(name, n.getNumber(context.passesAttribute)); 442 n.removeAttribute(context.passesAttribute); 443 } 444 } 445 446 context.passesAttribute = name; 447 } 448 449 /** 450 * The number of entity passage on the given edge. 451 * @param edge The edge to look at. 452 * @return The number of passes on the edge. 453 */ 454 public double getPasses(Edge edge) { 455 return edge.getNumber(context.passesAttribute); 456 } 457 458 /** 459 * The number of entity passage on the given node. 460 * @param node The node to look at. 461 * @return The number of passes on the node. 462 */ 463 public double getPasses(Node node) { 464 return node.getNumber(context.passesAttribute); 465 } 466 467 /** 468 * Set the number of entities which will be created at the algorithm 469 * initialization. 470 * 471 * @param entityCount 472 */ 473 public void setEntityCount(int entityCount) { 474 this.entityCount = entityCount; 475 } 476 477 /** 478 * Activate or not the counts on edges when entities cross thems. 479 * @param on If true (the default) the edges passes are counted. 480 */ 481 public void computeEdgesPasses(boolean on) { 482 doEdges = on; 483 } 484 485 /** 486 * Activate or not the counts on nodes when entities cross thems. 487 * @param on If true (the default) the nodes passes are counted. 488 */ 489 public void computeNodePasses(boolean on) { 490 doNodes = on; 491 } 492 493 /** 494 * Create an entity. Override this method to create different kinds of 495 * entities or change the entity class name. The default one is the "TabuEntity". 496 * 497 * @return The new entity. 498 * @see #setEntityClass(String) 499 */ 500 public Entity createEntity() { 501 try { 502 Object o = Class.forName(entityClass).newInstance(); 503 if(o instanceof Entity) { 504 Entity e = (Entity) o; 505 e.init(context, randomNode(context.graph, context.random)); 506 507 return e; 508 } else { 509 System.err.printf("Object %s pointed at by class name '%s' does not implement Entity.%n", o.getClass().getName(), entityClass); 510 } 511 } catch (Exception e) { 512 System.err.printf("Error: %s%n", e.getMessage()); 513 e.printStackTrace(); 514 } 515 516 return null; 517 } 518 519 /** 520 * Initialize the algorithm for a given graph with a given entity count. The 521 * entities are created at random locations on the graph. 522 * 523 * @param graph 524 * The graph to explore. 525 */ 526 public void init(Graph graph) { 527 if (context.graph != null) 528 throw new RuntimeException( 529 "cannot begin a random walk if the previous one was not finished, use end()."); 530 531 context.graph = graph; 532 533 entities.clear(); 534 535 for (int i = 0; i < entityCount; i++) 536 entities.add(createEntity()); 537 538 equipGraph(); 539 graph.addElementSink(this); 540 } 541 542 /** 543 * Execute one step of the algorithm. During one step, each entity choose a 544 * next edge to cross, toward a new node. The passes attribute of these edge 545 * and node are updated. 546 */ 547 public void compute() { 548 context.jumpCount = 0; 549 context.goCount = 0; 550 context.waitCount = 0; 551 552 for (Entity entity : entities) { 553 entity.step(); 554 } 555 556 if(evaporation<1) 557 evaporate(); 558 } 559 560 /** 561 * Apply evaporation on each edge. 562 */ 563 protected void evaporate() { 564 for(Edge edge: context.graph.getEachEdge()) { 565 edge.setAttribute(context.passesAttribute, edge.getNumber(context.passesAttribute)*evaporation); 566 } 567 for(Node node: context.graph) { 568 node.setAttribute(context.passesAttribute, node.getNumber(context.passesAttribute)*evaporation); 569 } 570 } 571 572 /** 573 * End the algorithm by removing any listener on the graph and releasing 574 * memory. 575 */ 576 public void terminate() { 577 entities.clear(); 578 context.graph.removeElementSink(this); 579 context.graph = null; 580 } 581 582 protected void equipGraph() { 583 for (Edge e : context.graph.getEachEdge()) { 584 e.addAttribute(context.passesAttribute, 0.0); 585 } 586 587 for (Node n : context.graph) { 588 n.addAttribute(context.passesAttribute, 0.0); 589 } 590 } 591 592 /** 593 * Sort all edges by their "passes" attribute and return the array of sorted 594 * edges. 595 * 596 * @return An array with all edges of the graph sorted by their number of 597 * entity pass. 598 */ 599 public ArrayList<Edge> findTheMostUsedEdges() { 600 ArrayList<Edge> edges = new ArrayList<Edge>(context.graph.getEdgeCount()); 601 Iterator<? extends Edge> i = context.graph.getEdgeIterator(); 602 603 while (i.hasNext()) { 604 edges.add(i.next()); 605 } 606 607 Collections.sort(edges, new Comparator<Edge>() { 608 public int compare(Edge e1, Edge e2) { 609 int n1 = (int) e1.getNumber(context.passesAttribute); 610 int n2 = (int) e2.getNumber(context.passesAttribute); 611 612 return (n1 - n2); 613 } 614 }); 615 616 return edges; 617 } 618 619 /** 620 * Sort all nodes by their "passes" attribute and return the array of sorted 621 * nodes. 622 * 623 * @return An array with all nodes of the graph sorted by their number of 624 * entity pass. 625 */ 626 public ArrayList<Node> findTheMostUsedNodes() { 627 ArrayList<Node> nodes = new ArrayList<Node>(context.graph.getNodeCount()); 628 Iterator<? extends Node> i = context.graph.getNodeIterator(); 629 630 while (i.hasNext()) { 631 nodes.add(i.next()); 632 } 633 634 Collections.sort(nodes, new Comparator<Node>() { 635 public int compare(Node e1, Node e2) { 636 int n1 = (int) e1.getNumber(context.passesAttribute); 637 int n2 = (int) e2.getNumber(context.passesAttribute); 638 639 return (n1 - n2); 640 } 641 }); 642 643 return nodes; 644 } 645 // Graph listener 646 647 @Override 648 public void edgeAdded(String graphId, long timeId, String edgeId, 649 String fromNodeId, String toNodeId, boolean directed) { 650 Edge edge = context.graph.getEdge(edgeId); 651 652 if (edge != null) 653 edge.addAttribute(context.passesAttribute, 0.0); 654 } 655 656 @Override 657 public void nodeAdded(String graphId, long timeId, String nodeId) { 658 Node node = context.graph.getNode(nodeId); 659 660 node.addAttribute(context.passesAttribute, 0.0); 661 } 662} 663 664 665// Nested classes 666/* 667public class TabuTimedEntity extends TabuEntity { 668 protected static final float SPEED = 1000; 669 670 protected double crossing = 0; 671 672 @Override 673 public void init(Node start) { 674 super.init(start); 675 } 676 677 @Override 678 public void step() { 679 if (crossing > 0) { 680 waitCount++; 681 crossing -= SPEED; 682 } else { 683 goCount++; 684 tabuStep(); 685 } 686 } 687 688 @Override 689 protected void jump() { 690 super.jump(); 691 crossing = 0; 692 } 693 694 @Override 695 protected void cross(Edge edge) { 696 super.cross(edge); 697 698 float speed = 1f; 699 700 if (edge.hasLabel("SPEED_CAT")) { 701 String s = (String) edge.getLabel("SPEED_CAT"); 702 speed = 9 - Float.parseFloat(s); 703 } 704 if (edge.hasLabel("LANE_CAT")) { 705 String s = (String) edge.getLabel("LANE_CAT"); 706 speed *= Float.parseFloat(s); 707 } 708 709 if (speed <= 0) 710 speed = 1f; 711 712 crossing = edgeLength(edge) / speed; 713 } 714} 715*/