5 Events
In a simulation model, an event represents an action that will take place in the (simulated) future.
In the simulation collection, an event represents the (simulated) future application of a procedural object to a list of arguments.
5.1 The event Structure
(struct event ( time priority process function arguments event-list linked-event-list?) #:mutable) time : (<=/c 0.0) priority : real? process : (or/c process? false/c) function : (or/c procedure? false/c) arguments : list? event-list : (or/c event-list? false/c) linked-event-list? : (or/c event-list? false/c)
time—
the (simulated) time the event is to occur. priority—
the priority if the event. Priorities are real numbers with larger numbers representing higher priority. process—
The process owning the event or #f if the event does not belong to any process. function—
The procedural object that implements the event or #f. arguments—
A list of the arguments for the function. event-list—
The event list in which the event is currently stored or #f. This is used when it is necessary to unschedule an event from whatever event list it might be stored in. An event can only be stored in one event list at any particular time. linked-event-list—
An event list storing events that are linked to this one or #f. This is used to implement linked events (i.e., those scheduled using the #:when event scheduling specification.)
(make-event time priority process function arguments) → event? time : (<=/c 0.0) priority : real? process : (or/c process? false/c) function : (or/c procedure? false/c) arguments : list?
Note that make-event is not typically used in user code. Instead, the schedule macro is used to create and schedule events.
5.2 Event Lists
It isn’t generally necessary to explicitly manipulate the event lists in user code. See Chapter 13, Simulation Control (Advanced).
An event list stores a list of events. Events are stored in order of their time and priority values.
(struct event-list (events) #:mutable) events : list?
events—
A list of the events on the event list.
(event-list-empty? event-list) → boolean? event-list : event-list?
(event-list-add! event-list event) → any event-list : event-list? event : event?
(event-list-remove! event-list event) → any event-list : event-list? event : event?
(event-list-pop! event-list) → event? event-list : event-list?
Note that the event list code will be updated to use skip lists in the future. This will change the event structure, but the manipulation functions should not change.
5.3 Example— Functions as Events
This example is a simulation model of a simple system. Customer arrivals are exponentialy distributed with an interrival time of four minutes. The time a customer remains in the system is uniformly distributed between two and ten minutes. The output is a simple trace of customer arrivals and departures.
#lang racket/base ; Example 0 - Functions as Events (require (planet williams/simulation/simulation)) (define (generator n) (for ((i (in-range n))) (wait (random-exponential 4.0)) (schedule #:now (customer i)))) (define (customer i) (printf "~a: customer ~a enters~n" (current-simulation-time) i) (work (random-flat 2.0 10.0)) (printf "~a: customer ~a leaves~n" (current-simulation-time) i)) (define (run-simulation n) (with-new-simulation-environment (schedule #:at 0.0 (generator n)) (start-simulation))) (run-simulation 10)
Produces the following output.
0.6153910608822503: customer 0 enters |
5.599485116393393: customer 1 enters |
6.411843645405005: customer 2 enters |
8.48917994426752: customer 0 leaves |
10.275428842274628: customer 1 leaves |
14.749397986170655: customer 2 leaves |
23.525886616767437: customer 3 enters |
27.18604340910279: customer 3 leaves |
32.1644631797164: customer 4 enters |
33.14558760001698: customer 5 enters |
39.67682614849173: customer 4 leaves |
40.486553934113665: customer 6 enters |
41.168084930967424: customer 5 leaves |
45.72670063299798: customer 6 leaves |
46.747675912143016: customer 7 enters |
49.212327970772435: customer 8 enters |
50.556538752352886: customer 9 enters |
51.46738784004611: customer 8 leaves |
52.514846525674855: customer 7 leaves |
56.11635302397275: customer 9 leaves |
The require form loads the simulation collection from PLaneT—
The generator function generates customers into the system. It takes a single argument, n, which is the total number of customers to generate. It provides the appropriate interrival time by waiting between each customer. After the wait, it schedules an event to represent the customer in the system. It does this n times to generate the appropriate number of customers.
The customer function represents a unique customer in the system. It takes a single argument, i, which is a unique integer between 0 and n-1, inclusive. The customer prints its arrival, works a random length of time, prints its departure, and ends.
The run-simulation function sets up and runs the simulation model. It creates a new simulation environment for the simulation model, schedules the generator to start execution as soon as the model starts (i.e. at time 0.0), and starts the simulation main loop by calling start-simulation. Note that the with-new-simulation-environment isn’t actually required in this case. However, it is a good idea to always use it to ensure the simulation model begins execution with a clean simulation environment.
Finally, the (run-simulation 10) form executes the simulation model, specifying a total of ten customers.
We will build on this trivial simulation model as we define more advanced simulation models. A few things to note at this point are:
You can add (random-source-randomize! (current-random-source)) to (run-simulation) to produce random results.
Executing this simulation model will always give the same output—
even across the various platforms supported by Racket. This is the default behavior of the random number generation package we are using. [This is actually desirable behavior and allows repeatable random streams where desired.] Events are basically the delayed execution of procedural objects under the control of the simulation main loop.
Events can span simulated time. That is, calls to wait/work (or, equivalently, wait or work perform as expected inside of events.)
We will subsequently see that there are many things that events can’t do—
such as utilize interprocess communications. But for simple elements, like the generator, events are appropriate.