/* Queue.mm */


/*
This is a reprogramming of Tom Grossman's 
N-server queue simulation - in MM. It has two main components: 
a set of customers, and a set of servers. The number of customers and
servers is fixed before starting.

The scenario is that customers enter a shop, go to a server, may
queue, get served, and leave. Each customer interacts once only and
then leaves.

We could slice up time by allocating equally-spaced time points to
successive rows. Instead, following Grossman's simulation, each row
represents one customer's complete sequence of transactions: ariving,
starting to queue, starting being served, and leaving. We handle this in
MM by declaring our base to be 'event = 1..10' or whatever, where each
point represents a separate customer.

This being done, we do not need to represent each customer as a distinct
object (though from a sheaf semantics point of view, I would prefer to
start by doing so, and then transform the program into one where they're
all collapsed into one object). Instead, we define one 'customers'
object to represent the whole lot. This has attributes such as
customer_number, arrival_time, service_start_time and service_duration,
all quantified over 'event'.

Each customer has a unique customer number: this is 1 for the first
customer, and is incremented for each successive customer.

Having defined a basic 'customers' object, we can then extend it in
various ways. One thing we shall want to do is to generate the arrival
times. Since we may want to change the distribution function, we shall
have various sub-objects which inherit from 'customers'.

Now we need to do the servers. Grossman allocates one column to each
server, so we shall do likewise. This would make it easy to treat each
server as a distinct object. But again, there's not much to be gained by
doing so - we might as well treat the whole group as one object. (This
is partly because I've not decided how best to split up Grossman's code
across servers if we do make them separate objects.)

Finally, we define some specialisations of 'customers' with particular
distributions of arrival times, and then specify the worksheet to
be built.
*/


/* Declare our units.
*/
unit duration
// Durations, as minutes.

unit time = @duration
// Points in time, as hh:mm.

unit customer_id ~ integer
// Customer numbers, from 1 upwards.

unit server_id ~ integer
// Server numbers, from 1 upwards.


/* Define our 'base'. This is the interval over
   which customer numbers will range, to be allocated
   to rows.
*/
base event = 1 .. 10


/* Define a succession of customers.
*/
customers = 
  <customer_number[event] as customer_id
   arrival_time[event] as time
   service_start_time[event] as time
   service_duration[event] as duration   
  >
  where (e) customer_number[e]=e 
//
// The above represents one sequence of customers. Each
// attribute will go into a column. We have four attributes:
// customer number, arrival time, service start time, and
// service duration.
//
// Each attribute has as many rows as there are points
// in 'event'. The equation stipulates that the customer
// number in any row e is equal to e.


/* Define a group of N servers.
*/
servers( N:server_id ) =
  <start as time
   customer_number[event] as customer_id
   arrival_time[event] as time
   service_start_time[event] as time
   service_duration[event] as duration
   service_end_time[event] as time
   first_free[event] as server_id
   potential_start_time[event,N] as time
  >
  where (e>1,N) potential_start_time[e,N] = max( service_end_time[e-1], arrival_time[e] ) 
        (N) potential_start_time[1,N] = start   
        (e) service_start_time[e] = min( (N)potential_start_time[e] )
            // The argument must deliver a range into min.
        (e) service_end_time[e] = service_start_time[e] + service_duration[e]
        (e) first_free[e] = match( actual_start_time[e], (N)potential_start_time[e], 0 )
            // The 2nd argument must deliver a range into 'match'.
            // 'match' returns the relative position of the first cell in
            // its 2nd argument which is the same as the 1st argument.
            // The 0 in the 3rd argument, taken from Grossman's simulation,
            // should never happen, I think.
//
// This has similar attributes to the 'customers' object, plus
// some others taken from Grossman's simulation.
//
// Attribute potential_start_time is a kind of two-dimensional array.
// The first dimension is down the rows, as before. The second dimension
// is along columns: one column for each value of N, i.e. for each
// server number. This represents the time at which each server _could_
// start, given when it finishes the previous customer. This is the
// greater of the current customer's arrival time, and the previous customer's
// finishing time.
//
// service_start_time is the least value of potential_start_time over
// all servers.
//
// service_end_time is this plus the service duration. That's not set
// here - it will come from the customer.
//
// first_free is the number of the first free server, i.e. that with
// the smallest potential_start_time. This is found by searching all
// servers' potential_start_times for service_start_time, i.e. the
// least value of potential_start_time.
//
// 'start' is the time at which the simulation begins. Notice that it
// is not subscripted by 'event'. So it will occupy only one cell. It
// will be set later.
//
// The equations use Excel's built-in max, min and match functions.
// Where the arguments are unsubscripted, the compiler translates them
// into entire ranges.


/* Declare a sequence of customers with an additional
   'interarrival_time' attribute and a start time.
*/
customers_with_interarrival_times = 
  customers *
  <interarrival_time[event] as duration
   start as time
  >
  where (e>1) arrival_time[e] = arrival_time[e-1] + interarrival_time[e] &
              arrival_time[1] = start + interarrival_time[1] 
//
// interarrival_time is the time between arrivals of successive
// customers. This can be calculated in different ways for different
// sub-objects of customers_with_interarrival_times.
//
// Notice that 'start' is not subscripted by 'event'. So it will
// occupy only one cell. This is the start time for the entire
// simulation, as for the N servers above.


/* Declare a sequence of customers with random
   interarrival times by extending the previous definition.
*/
random_customers =
  customers_with_interarrival_times 
  where (e) interarrival_time[e] = rand()
//
// We use Excel's built-in rand function to allocate a random
// interarrival time to each point in 'event'.


/* Finally, tell the compiler to build the worksheet.
*/
model
  ( random_customers on servers(4) )
  where start=9.
//
// The 'on' operator makes attributes in 'random_customers'
// equal to attributes of the same name in 'servers(4)'. So,
// for example, it makes the two values of 'start' equal. The
// equation sets these both to 9.