# You may have to edit this file to delete header lines produced by # mailers or news systems from this file (all lines before these shell # comments you are currently reading). # Shell archive made by dwjones on Tue 01 Dec 2020 09:00:36 PM CST # To install this software on a UNIX system: # 1) create a directory (e.g. with the shell command mkdir stuff) # 2) change to that directory (e.g. with the command cd stuff), # 3) direct the remainder of this text to sh (e.g. sh < ../savedmail). # This will make sh create files in the new directory; it will do # nothing else (if you're paranoid, you should scan the following text # to verify this before you follow these directions). Then read README # in the new directory for additional instructions. cat > README.md <<\xxxxxxxxxx # Road Network Simulator A discrte-event simulator for a road network, built in Java. The network consists of any number of intersections connected by roads. Intersections may be guarded by stop lights or uncontrolled. A special intersection type injects vehicles into the model and another consumes vehicles. Roads and intersection have each have the time it take a vehicle to traverse them. This is really a feasibility demonstration, because the only output it produces is a trace of activity. Furthermore, vehicles have no identity and no agenda but just bounce around the road network at random. There is no congestion penalty for roads. ## Install This file contains a file called `RoadFiles` listing all the Java source files. Each source file contains one top-level class. RoadFiles is divided into 3 sections: * Utility classes including the simulation framework. * The simulation model, roads, intersections and types of intersection. * The main program to read the model and run the simulation. ### Prerequisites The code is in Java 8, it uses some lambda expressions. The following instructions assume use of some variant of the Unix shell, as is commonly used on Linux and MacOS for command line input. ### Instruction To compile the entire project, use this shell command: ``` make ``` To build the HTML documentation for the internals of the code, use this shell command: ``` make html ``` ### Cleanup The above steps created a large number of `.class` files containing parts of the compiler output, and a large number of `.html` files containing documentation, plus `package-list` and some `.css` and `.js` files. These clutter up the directory and, since all are automatically generated, you can delete them with the following shell command when they get in the way: ``` make clean ``` ## Usage The distribution contains a file called `testfile` that describes a trivial little road network. After installing the simulator, you can test and demonstrate it shell commands such as the following: ``` make test java RoadNetwork testfile java RoadNetwork testfile 10.0 ``` You may substitute your own road network description for `testfile` and as the second example above demonstrates, you may specify a time limit for the simulation shorter than the limit given in the test file. If none is given, the simulation will run forever. BUG Unfortunately, we not yet written a decent description of the test file format, so you'll have to infer that from the example. Mea culpa! Contributing ------------ - Douglas Jones, Department of Computer Science, University of Iowa ## License Free software, the author makes no claim on it and doesn't want to be bothered by people asking to use it. Just take it, it's yours! It would be better to use the GPL, Gnu Pulbic License, or one of the Creative Commons licenses. ## Waranty You get what you paid for. If this does anything for you, great, but don't bother me if it doesn't work. ## Version History 2020-12-01 -- circular inter-module dependencies eliminated and Makefile added 2020-11-12 -- new anti-lambda expression simulation framework earlier versions used lambda expressions xxxxxxxxxx cat > Makefile <<\xxxxxxxxxx # Makefile for the road network simulator # Author Douglas Jones # Version 2020-12-01 # Status: There is a bug: `make clean; make; make test` does extra javac steps # The following make commands are supported # make -- equivalent to make RoadNetwork.class # make RoadNetwork.class -- makes RoadNetwork.class and all subsidiaries # # make test -- run a demo showing how the program works # make html -- makes web site of internal documentation # make clean -- deletes all files created by any of the above ############# # All the source files, broken up by categories UtilityJava = Error.java MyScanner.java MyRandom.java Simulator.java IntJava = NoStop.java StopLight.java Sink.java Source.java ModelJava = Road.java Roads.java Intersection.java Intersections.java $(IntJava) MainJava = RoadNetwork.java AllJava = $(UtilityJava) $(ModelJava) $(MainJava) ############# # primary make target UtilityClasses = Error.class MyScanner.class Simulator.class SimulateClasses = Road.class Roads.class Intersection.class Intersections.class RoadNetwork.class: RoadNetwork.java $(UtilityClasses) $(SimulateClasses) javac RoadNetwork.java # secondary make target -- simulation model InterDepends = Road.class Source.class Sink.class StopLight.class NoStop.class Intersections.class: Intersections.java $(InterDepends) $(UtilityClasses) Roads.class: Roads.java Road.class Intersection.class $(UtilityClasses) javac Roads.java Source.class: Source.java Source.class: Intersection.class NoStop.class $(UtilityClasses) MyRandom.class javac Source.java Sink.class: Sink.java Intersection.class Road.class $(UtilityClasses) javac Sink.java StopLight.class: StopLight.java Intersection.class Road.class $(UtilityClasses) javac StopLight.java NoStop.class: NoStop.java Intersection.class Road.class $(UtilityClasses) javac NoStop.java Intersection.class: Intersection.java Road.class $(UtilityClasses) Road.class: Road.java javac Road.java # secondary make target -- utility classes MyScanner.class: MyScanner.java Error.class javac MyScanner.java Error.class: Error.java javac Error.java Random.class: Random.java javac Random.java Simulator.class: Simulator.java javac Simulator.java ############# # utility make targets test: RoadNetwork.class java RoadNetwork testfile html: javadoc @RoadFiles clean: rm -f *.class *.html package-list script.js stylesheet.css xxxxxxxxxx cat > Error.java <<\xxxxxxxxxx /** * Error handling * @author Douglas Jones * @version 10/27/2020 * Status: Stable code */ abstract class Error{ private Error(){} // prevent instantiation of this class private static int errorCount = 0; private static final int errorLimit = 10; /** Warn the user of a non-fatal error * @param message to output * This version outputs the message to System.err (standard error). * Other versions could output via a pop-up window or something. * If there are too many errors, this code will terminate the application. * The error limit is currently hard coded to 10. */ public static void warn( String message ) { System.err.println( message + "\n" ); errorCount = errorCount + 1; if (errorCount > errorLimit) System.exit( 1 ); } /** Warn the user of a fatal error * @param message to output * This version outputs the message exactly as warn() does. * @see warn * it terminates the application immediately, indicating a failure. */ public static void fatal( String message ) { warn( message ); System.exit( 1 ); } /** Terminate application if there have been any warnings */ public static void quitIfAny() { if (errorCount > 0) System.exit( 1 ); } } xxxxxxxxxx cat > Intersection.java <<\xxxxxxxxxx import java.util.LinkedList; import java.util.Iterator; /** * Intersections are connected by roads, only subclasses are ever instantiated * @author Douglas Jones * @version 2020-12-01 -- Eliminate circular dependencies * Status: Works * @see Road */ public abstract class Intersection { // instance variables final String name; final Double travelTime; private final LinkedList outgoing = new LinkedList (); protected final LinkedList incoming = new LinkedList (); private static final MyRandom rand = MyRandom.stream(); // the collection of all instances private static final LinkedList allIntersections = new LinkedList (); /** Constructor needed to initialize final fields * @param name of the intersection */ Intersection( String name, double travelTime ) { this.name = name; this.travelTime = travelTime; allIntersections.add( this ); } /** Primarily for debugging * @return textual name and travel time of the road */ public String toString() { return "Intersection " + name + " " + travelTime; } /** Allow outsiders to iterate over all roads * @return textual name and travel time of the road */ public static Iterator iterator() { return allIntersections.iterator(); } /** Allow finding intersections by name * @param n, the textual name of the intersection * @return the Intersection with that name */ public static Intersection lookup( String n ) { for (Intersection i: allIntersections) { if (i.name.equals( n )) return i; } return null; } /** Allow a road to connect to this intersection * @param r the road that goes here * @param q the incoming road's queue number */ public abstract void addIncoming( Road r, int q ); /** Allow a road to connect form this intersection * @param r the road that leaves here */ public void addOutgoing( Road r ) { this.outgoing.add( r ); } // Simulation methods /** Allow vehicles in an intersection to pick outgoing road * @return one of the roads at random */ protected Road pickOutgoing() { return outgoing.get( rand.nextInt( outgoing.size() ) ); } /** Signal that a vehicle on a road is entering this intersection * @param time of vehicle entry * @param queue vehicle enters */ public abstract void enter( double time, int queue ); } xxxxxxxxxx cat > Intersections.java <<\xxxxxxxxxx /** * Intersections are connected by roads, only subclasses are ever instantiated * @author Douglas Jones * @version 2020-12-01 -- Eliminate circular dependencies * Status: Works * @see Road */ public class Intersections { // not a subclass of Intersection! public static class ConstructorFail extends Exception {}; /** Constructor to prevent instantiation */ private Intersections() { assert false; } /** The the factory for intersections * @param sc, scanner from which the Intersection description is scanned * @return the Intersection constructed from that description * @throws ConstructorFail when it cannot construct an Intersection * Input format scanned from sc: name sub ... *
where name is a string *
where sub is a string naming the subclass *
where ... signifes details the deals with, if any */ public static Intersection factory( MyScanner sc ) throws ConstructorFail { // pick off name of intersection; String name = sc.getNext( "???", () -> "Intersection name missing" ); double time = sc.getNextFloat( Float.NaN, () -> "Intersection " + name + ": missing travel time" ); if ((name == "???") || Double.isNaN( time )) { throw new ConstructorFail(); } if (Intersection.lookup( name ) != null) { Error.warn( "Intersection " + name + ": redefined" ); throw new ConstructorFail(); } if (time < 0.0) { Error.warn( "Intersection " + name + " " + time + ": negative travel time?" ); throw new ConstructorFail(); } Intersection self; // this is the intersection the factory is working on // find out what kind of intersection call right constructor if (sc.hasNext( "stoplight" )) { sc.next(); // skip keyword stoplight self = new StopLight( sc, name, time ); } else if (sc.hasNext( "source" )) { sc.next(); // skip keyword source self = new Source( sc, name, time ); } else if (sc.hasNext( "sink" )) { sc.next(); // skip keyword source self = new Sink( sc, name, time ); } else { // plain uncontrolled intersection self = new NoStop( sc, name, time ); } return self; } } xxxxxxxxxx cat > MyRandom.java <<\xxxxxxxxxx import java.util.Random; /** * Singleton wrapper for Java's Random class * @author Douglas Jones * @version 10/27/2020 * Status: Fairly stable, but other probability distributions could be added * @see java.util.Random */ public class MyRandom extends Random { private MyRandom() { // uncomment exactly one of the following! super(); // let Java pick a random seed // super( 3004 ); // set seed so we can debug } /** the only stream visible to users * users can use the stream field or * the stream() method interchangably. */ public static final MyRandom stream = new MyRandom(); /** an alternate way to expose users to the stream * @return handle on the stream * users can use the stream field or * the stream() method interchangably. */ public static MyRandom stream() { return stream; } /** get the next exponentially distributed pseudo-random number * @param mean, the mean value of the distribution * @return the next number drawn from this distribution */ public double nextExponential( double mean ) { return -Math.log( nextDouble() ) * mean; } } xxxxxxxxxx cat > MyScanner.java <<\xxxxxxxxxx import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; /** * Wrapper or Adapter for scanners that integrates error handling * @author Douglas Jones * @version 10/27/2020 * Status: Stable code, but probably not as good as it could be * @see java.util.Scanner * @see Error */ public class MyScanner { private Scanner self; // the scanner this object wraps /** * Parameter carrier interface for deferred string construction * Wsed only for error message parameters to getXXX() methods. * Most code that uses this will use it implicitly through * a lambda expression. */ static interface ErrorMessage { String myString(); } /** * Construct a new MyScanner to read from a file * @param f, the file to read * @exception FileNotFoundException thrown if the file cannot be opened */ public MyScanner( File f ) throws FileNotFoundException { self = new Scanner( f ); } // methods we wish could inherit from Scanner but can't beause it's final public boolean hasNext() { return self.hasNext(); } public boolean hasNext( String s ) { return self.hasNext( s ); } public boolean hasNextFloat() { return self.hasNextFloat(); } public boolean hasNextInt() { return self.hasNextInt(); } public String next() { return self.next(); } public float nextFloat() { return self.nextFloat(); } public String nextLine() { return self.nextLine(); } // new methods we add to this class /** Get the next string, if one is available * @param def the default value if no string is available * @param msg the error message to print if no string is available * @return the next token from the input, or def if none */ public String getNext( String def, ErrorMessage msg ) { if (self.hasNext()) return self.next(); Error.warn( msg.myString() ); return def; } /** Get the next float, if one is available * @param def the default value if no float is available * @param msg the error message to print if no float is available * @return the value of the next token from the input, or def if none */ public float getNextFloat( float def, ErrorMessage msg ) { if (self.hasNextFloat()) return self.nextFloat(); Error.warn( msg.myString() ); return def; } /** Get the next int, if one is available * @param def the default value if no int is available * @param msg the error message to print if no int is available * @return the value of the next token from the input, or def if none */ public int getNextInt( int def, ErrorMessage msg ) { if (self.hasNextInt()) return self.nextInt(); Error.warn( msg.myString() ); return def; } } xxxxxxxxxx cat > NoStop.java <<\xxxxxxxxxx /** * Uncontrolled Intersections * @author Douglas Jones * @version 11/10/2020 -- new simulation framework * Status: NoStop intersections might work, but new framework * @see Intersection */ public class NoStop extends Intersection { protected int waiting = 0; /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ NoStop( MyScanner sc, String name, double time ) { super( name, time ); // needed because of final fields } /** Allow a road to connect to this intersection * @param r the road that goes here * @param q the incoming road's queue number */ public void addIncoming( Road r, int q ) { this.incoming.add( r ); if (q > 0) { Error.warn( r.toString() + ": positive queue number not allowed" ); } } /* Primarily for debugging * @return textual name and attributes of the intersection */ // public String toString() { // // the superclass version does this! // } // simulation methods /** Signal that a vehicle on a road is entering this intersection * @param t, the time of vehicle entry * @param queue vehicle enters (ignored) */ public void enter( double t, int queue ) { waiting = waiting + 1; // enqueue this car if (waiting <= 1) { // am I the only one in the queue, send me on class MyEvent extends Simulator.Event { MyEvent() { super( t ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); } System.out.println( this.toString() +": entered at t ="+ t ); } /** Event that a vehicle in this intersection is leaving for a road * @param t, the time of vehicle departure */ protected void leave( double t ) { waiting = waiting - 1; // dequeue one car (or not, if queue emptied) if (waiting > 0) { // if I dequeued a car, send it through class MyEvent extends Simulator.Event { MyEvent() { super( t ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); } this.pickOutgoing().enter( t ); } } xxxxxxxxxx cat > Road.java <<\xxxxxxxxxx /** * Roads connect intersections * @author Douglas Jones * @version 2020-12-01 -- split Road to eliminate circular dependencies * Status: Works, but see BUG notices * @see Intersection */ public interface Road { // Simulation methods /** Signal that a vehicle is entering this road * @param t, the time of entry to this road */ void enter( double t ); } xxxxxxxxxx cat > RoadNetwork.java <<\xxxxxxxxxx import java.util.Iterator; import java.io.File; import java.io.FileNotFoundException; /** * Main class builds model and will someday simulate it * @author Douglas Jones * @version 11/11/2020 -- new simulation framework * Status: Works * @see Road * @see Roads * @see Intersection * @see Intersections */ public abstract class RoadNetwork { private static void readNetwork( MyScanner sc ) { while (sc.hasNext()) { // until the input file is finished String command = sc.next(); if ("intersection".equals( command )) { try { Intersections.factory( sc ); } catch ( Intersections.ConstructorFail e ) {}; } else if ("road".equals( command )) { try { new Roads( sc ); } catch ( Roads.ConstructorFail e ) {}; } else if ("endtime".equals( command )) { float endTime = sc.getNextFloat( 0, ()-> "endtime: time expected" ); class MyEvent extends Simulator.Event { MyEvent() { super( endTime ); } public void trigger() { System.exit( 0 ); } } Simulator.schedule( new MyEvent() ); } else if ("//".equals( command )) { sc.nextLine(); } else { Error.warn( "unknown command: " + command ); } } } private static void writeNetwork() { for (Iterator i = Intersection.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } for (Iterator i = Roads.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } } public static void main( String[] args ) { if (args.length < 1) { Error.fatal( "Missing file name argument" ); } else try { readNetwork( new MyScanner( new File( args[0] ) ) ); } catch ( FileNotFoundException e ) { Error.fatal( "Can't open file: " + args[0] ); } if (args.length > 1) { try { float endTime = Float.parseFloat( args[1] ); class MyEvent extends Simulator.Event { MyEvent() { super( endTime ); } public void trigger() { System.exit( 0 ); } } Simulator.schedule( new MyEvent() ); } catch ( NumberFormatException e ) { Error.fatal( "RoadNetwork "+ args[0] +" "+ args[1] +": Negative end?" ); } } Error.quitIfAny(); writeNetwork(); // BUG -- this code is for debugging only Simulator.run(); } } xxxxxxxxxx cat > Roads.java <<\xxxxxxxxxx import java.util.LinkedList; import java.util.Iterator; /** * Roads connect intersections * @author Douglas Jones * @version 2020-12-01 -- split Road to eliminate circular dependencies * Status: Works, but see BUG notices * @see Intersection */ public class Roads implements Road { public static class ConstructorFail extends Exception {}; // instance variables private final double travelTime; private final Intersection destination; private final Intersection source; private final int queueNumber; // the collection of all instances private static final LinkedList allRoads = new LinkedList (); /** The only constructor * @param sc MyScanner from which description comes * @throws ConstructorFail if the Road cannot be constructed * Input format scanned from sc: source-name destination-name travel-time *
where source-name is the name of the source intersection and *
destination-name is the name of the destination intersection and *
travel-time is a floating point time in seconds */ public Roads( MyScanner sc ) throws ConstructorFail { // keyword Road was already scanned final String src; // where does it come from final String dst; // where does it go src = sc.getNext( "???", () -> "road: from missing source" ); dst = sc.getNext( "???", () -> "road " + src + ": to missing destination" ); travelTime = sc.getNextFloat( Float.NaN, () -> "road " + src + " " + dst + ": missing travel time" ); if (sc.hasNextInt()) { // get the queue number queueNumber = sc.getNextInt( -1, () -> "road " + src + " " + dst + " " + travelTime + ": missing travel time" ); } else { queueNumber = -1; } if ((src == "???") || (dst == "???") || Double.isNaN( travelTime )) { throw new ConstructorFail(); } destination = Intersection.lookup( dst ); if (destination == null) { Error.warn( "road " + src + " " + dst + " undefined: " + dst ); throw new ConstructorFail(); } source = Intersection.lookup( src ); if (source == null) { Error.warn( "road " + src + " " + dst + " undefined: " + src ); throw new ConstructorFail(); } if (travelTime < 0.0) { Error.warn( this.toString() + ": negative travel time?" ); throw new ConstructorFail(); } allRoads.add( this ); // this is the only place items are added! destination.addIncoming( this, queueNumber ); // connect the road source.addOutgoing( this ); // connect the road } /** Primarily for debugging * @return textual name and travel time of the road */ public String toString() { return "Road " + source.name + " " + destination.name + " " + travelTime; // BUG throws exception when source/destination is null } /** Allow outsiders to iterate over all roads * @return textual name and travel time of the road */ public static Iterator iterator() { return allRoads.iterator(); } // Simulation methods /** Signal that a vehicle is entering this road * @param t, the time of entry to this road */ public void enter( double t ) { // BUG -- this.occupants = this.occupants + 1 // BUG -- congestion ?=? this.occupants/travelTime (vehicles/time) class MyEvent extends Simulator.Event { MyEvent() { super( t ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); } /** Event indicating that a vehicle is leaving this road * @param time of departure from this road * called only from enter! */ private void leave( double time ) { // BUG -- this.occupants = this.occupants - 1 // tell the destination intersection that we're arriving this.destination.enter( time, this.queueNumber ); } } xxxxxxxxxx cat > Simulator.java <<\xxxxxxxxxx import java.util.PriorityQueue; /** * Framework for discrete event simulation. * @author Douglas Jones * @version 11/10/2020 -- new simulation framework * Status: New code! Unstable? */ public abstract class Simulator { private Simulator(){} // prevent anyone from instantiating this class /** Users create and schedule subclasses of events */ public static abstract class Event { /** The time of the event, set by the constructor */ public final double time; // the time of this event /** Construct a new event and set its time * @param t, the event's time */ Event( double t ) { time = t; } /** What to do when this event is triggered * Within trigger, this.time is the time of this event, * Each subclass of event must provide a trigger method. */ public abstract void trigger(); // what to do at that time } private static PriorityQueue eventSet = new PriorityQueue ( (Event e1, Event e2)-> Double.compare( e1.time, e2.time ) ); /** Call schedule to make an event happen at its time. * Users create events with trigger method and a time, then schedule it */ static void schedule( Event e ) { eventSet.add( e ); } /** run the simulation. * Call run() after scheduling some initial events * to run the simulation. * This becomes the main loop of the program; typically, some scheduled * event will terminate the program by calling System.exit(). */ static void run() { while (!eventSet.isEmpty()) { eventSet.remove().trigger(); } } } xxxxxxxxxx cat > Sink.java <<\xxxxxxxxxx /** * Sink intersections that consume traffic * @author Douglas Jones * @version 10/27/2020 * Status: works * @see Intersection */ public class Sink extends Intersection { /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ Sink( MyScanner sc, String name, double time ) { super( name, time ); // sinks don't seem to need anything else } /** Allow a road to connect to this intersection * @param r the road that goes here * @param q the incoming road's queue number (ignored) */ public void addIncoming( Road r, int q ) { this.incoming.add( r ); if (q >= 0) { Error.warn( r.toString() + ": queue number not allowed" ); } } /** Primarily for debugging * @return textual name and attributes of the intersection */ public String toString() { return super.toString() + " Sink"; } /** Signal that a vehicle on a road is entering this intersection * @param time of vehicle entry * @param queue queue the vehicle enters (ignored) */ public void enter( double time, int queue ) { // BUG -- missing code for entry to intersection! System.out.println( this.toString() +": entered at t ="+ time ); } } xxxxxxxxxx cat > Source.java <<\xxxxxxxxxx /** * Source intersections that produce traffic * @author Douglas Jones * @version 11/10/2020 -- new simulation framework * Status: Works * @see Intersection */ public class Source extends NoStop { private final double period; // average interval between vehicle production private static MyRandom rand = MyRandom.stream; // we need randomness /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ Source( MyScanner sc, String name, double time ) { super( sc, name, time ); period = sc.getNextFloat( 0.1F, ()-> super.toString() +" source: expected source average interval" ); if (period <= 0) { Error.warn( this.toString() + ": source interval not positive" ); } else { class MyEvent extends Simulator.Event { MyEvent() { super( rand.nextExponential( period ) ); } public void trigger() { produce( time ); } } Simulator.schedule( new MyEvent() ); } } /** Primarily for debugging * @return textual name and attributes of the intersection */ public String toString() { return super.toString() + " Source " + period; } // simulation methods for Source private void produce( double t ) { this.enter( t, -1 ); class MyEvent extends Simulator.Event { MyEvent() { super( t + rand.nextExponential( period ) ); } public void trigger() { produce( time ); } } Simulator.schedule( new MyEvent() ); } } xxxxxxxxxx cat > StopLight.java <<\xxxxxxxxxx /** * Stoplight intersection * @author Douglas Jones * @version 11/11/2020 -- new simulation framework * Status: Works * @see Intersection */ public class StopLight extends Intersection { // instance variables set on construction private final double period; // interval between light changes private final int queueCount; // number of distinct incoming queues // instance variables change during simulation private boolean occupied = false;// is a car currently in this intersection private final int waiting[]; // one queue per queueCount private int greenDir; // queue number for which light is green // we need a stream of random numbers private static final MyRandom rand = MyRandom.stream(); /** Constructor * @param sc scanner from which to scan any extra details * @param name the name of the intersection */ StopLight( MyScanner sc, String name, double time ) { super( name, time ); // needed because Interesection.name is final period = sc.getNextFloat( 0.1F, ()-> super.toString() +" stoplight: expected stoplight period" ); queueCount = sc.getNextInt( -1, ()-> super.toString() +" stoplight "+ period +": expected queue count" ); if (period <= 0) { Error.warn( this.toString() + ": stoplight period not positive" ); } else { class MyEvent extends Simulator.Event { MyEvent() { super( period ); } // BUG -- period above should scale by random from 0.0 and 1.0 public void trigger() { lightChange( time ); } } Simulator.schedule( new MyEvent() ); } if (queueCount > 0) { waiting = new int[ queueCount ]; greenDir = rand.nextInt( queueCount ); } else { Error.warn( this.toString() + ": queue count not positive" ); waiting = null; greenDir = 0; } } /** Allow a road to connect to this intersection * @param r the road that goes here * @param q the incoming road's queue number */ public void addIncoming( Road r, int q ) { this.incoming.add( r ); if ((q < 0) || (q >= queueCount)) { Error.warn( r.toString() + ": queue number out of bounds" ); } } /** Primarily for debugging * @return textual name and attributes of the intersection */ public String toString() { return super.toString() + " Stoplight " + period + " " + queueCount; } // simulation methods for StopLight private void lightChange( double t ) { { class MyEvent extends Simulator.Event { MyEvent() { super( t + period ); } public void trigger() { lightChange( time ); } } Simulator.schedule( new MyEvent() ); } // update the light direction greenDir = (greenDir + 1) % queueCount; if ((waiting[greenDir] > 0) && !occupied) { class MyEvent extends Simulator.Event { MyEvent() { super( t + travelTime ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); waiting[greenDir] = waiting[greenDir] - 1; occupied = true; } System.out.println( this.toString() +": light change at t ="+ t ); } /** Signal that a vehicle on a road is entering this intersection * @param t, the time of vehicle entry * @param queue vehicle enters at this stop light */ public void enter( double t, int queue ) { // do I have to wait? if ((waiting[queue] <= 0) && (greenDir == queue) && !occupied ) { // do not wait class MyEvent extends Simulator.Event { MyEvent() { super( t + travelTime ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); occupied = true; } else { // wait waiting[queue] = waiting[queue] + 1; } } /** Event simulation of a vehicle leaving this stop light * @param t, the time of vehicle exit */ public void leave( double t ) { assert occupied == true; // does my departure unblock another vehicle if (waiting[greenDir] > 0) { class MyEvent extends Simulator.Event { MyEvent() { super( t + travelTime ); } public void trigger() { leave( time ); } } Simulator.schedule( new MyEvent() ); waiting[greenDir] = waiting[greenDir] - 1; // assert occupied still true } else { occupied = false; } this.pickOutgoing().enter( t ); } } xxxxxxxxxx cat > testfile <<\xxxxxxxxxx intersection A 0.1 source 10.0 intersection B 1.0 stoplight 20.0 2 intersection C 1.0 sink road A B 0.4 0 road A B 0.4 1 road B B 10.4 0 road B B 10.4 1 road B C 0.4 road B C 0.4 endtime 40.0 xxxxxxxxxx