7.4 The Station Package

This section describes a JSL package that facilitates the modeling of system components that can send or receive entities. Many systems have as a basic component a location where something happens or where processing can occur. A station represents this concept. The goal of this package is to illustrate the development and use of common components that help with the modeling of simple queueing system modeling. First, an overview of the package will be presented and then the operation of important classes discussed. Then, a number of examples will illustrate how to utilize the classes.

Major Classes within Station Package

Figure 7.3: Major Classes within Station Package

Figure 7.3 illustrates the primary classes and interfaces within the station package. There are two key interfaces: SendQObjectIfc and ReceiveQObjectIfc.

public interface SendQObjectIfc {
    
    void send(QObject qObj);
}

public interface ReceiveQObjectIfc {

    void receive(QObject qObj);
}

The interfaces define single methods, send(QObject qObj) and receive(QObject qObj), which permit implementors to promise the capability of receiving and sending objects of type QObject. Figure 7.4 illustrates the connection between the Station class and SendQObjectIfc and ReceiveQObjectIfc. This design permits the development of model elements that know how to send instances of QObject and how to receive instances of QObject. A Station is an abstract base class that implements the ReceiveQObjectIfc interface. Sub-classes of Station will have a receive() method that can be called to tell the station that an instance of a QObject should be received.

The Station Class

Figure 7.4: The Station Class

The following listing shows the implementation of the Station class. The Station class may have a reference to an object that implements the SendQObjectIfc interface and may have an object that implements the ReceiveQObjectIfc interface. An object that implements the SendQObjectIfc interface can be supplied to define behavior for sending a received instance of a QObject onwards within the system. If the SendQObjectIfc interface attribute is not supplied, then the ReceiveQObjectIfc object is used to indicate the next location for receipt. The protected method, send(QObject qObj), indicates this logic. The UML diagram for the Station class is provided in Figure 7.4. Notice that Station implements the ReceiveQObjectIfc interface, may have a reference to a ReceiveQObjectIfc instance, and has a reference to an instance of a SendQObjectIfc interface.

If the client does not supplier either an object that implements the ReceiveQObjectIfc interface or an object that implements the SendQObjectIfc interface, then an exception will be thrown. The object that implements the ReceiveQObjectIfc interface is meant to be a direct connection (i.e. something that can directly receive instances of QObject). Sometimes, the logic to determine where to send the QObject is complex. In which case, the user can either sub-class the Station class or provide a instance of a class that implements the SendQObjectIfc interface. This allows the sending logic to be delegated to another class rather than forcing the user to sub-class the Station class. This flexibility can be confusing to users of the class.

public abstract class Station extends SchedulingElement implements ReceiveQObjectIfc {

    /**
     * Can be supplied in order to provide logic
     *  to send the QObject to its next receiver
     */
    private SendQObjectIfc mySender;

    /** Can be used to directly tell the receiver to receive the departing
     *  QObject
     * 
     */
    private ReceiveQObjectIfc myNextReceiver;

    /**
     *
     * @param parent the parent model element
     */
    public Station(ModelElement parent) {
        this(parent, null, null);
    }

    /**
     *
     * @param parent the parent model element
     * @param name a unique name
     */
    public Station(ModelElement parent, String name) {
        this(parent, null, name);
    }

    /**
     * 
     * @param parent the parent model element
     * @param sender can be null, represents something that can send QObjects
     * @param name a unique name
     */
    public Station(ModelElement parent, SendQObjectIfc sender, String name) {
        super(parent, name);
        setSender(sender);
    }

    /**
     * A Station may or may not have a helper object that implements the 
     *  SendQObjectIfc interface.  If this helper object is supplied it will
     *  be used to send the processed QObject to its next location for
     *  processing.
     * @return the thing that will be used to send the completed QObject
     */
    public final SendQObjectIfc getSender() {
        return mySender;
    }

    /**
     * A Station may or may not have a helper object that implements the 
     *  SendQObjectIfc interface.  If this helper object is supplied it will
     *  be used to send the processed QObject to its next location for
     *  processing.
     * @param sender the thing that will be used to send the completed QObject
     */
    public final void setSender(SendQObjectIfc sender) {
        mySender = sender;
    }

    /**
     *  A Station may or may not have a helper object that implements the 
     *  ReceiveQObjectIfc interface.  If this helper object is supplied and
     *  the SendQObjectIfc helper is not supplied, then the object that implements
     *  the ReceiveQObjectIfc will be the next receiver for the QObject when using 
     *  default send() method.
     * @return the thing that should receive the completed QObject, may be null
     */
    public final ReceiveQObjectIfc getNextReceiver() {
        return myNextReceiver;
    }

    /**
     *  A Station may or may not have a helper object that implements the 
     *  ReceiveQObjectIfc interface.  If this helper object is supplied and
     *  the SendQObjectIfc helper is not supplied, then the object that implements
     *  the ReceiveQObjectIfc will be the next receiver for the QObject when using 
     *  default send() method.
     * @param receiver the thing that should receive the completed QObject, may be null
     */
    public final void setNextReceiver(ReceiveQObjectIfc receiver) {
        myNextReceiver = receiver;
    }

    /**
     *  A Station may or may not have a helper object that implements the 
     *  SendQObjectIfc interface.  If this helper object is supplied it will
     *  be used to send the processed QObject to its next location for
     *  processing.
     * 
     *  A Station may or may not have a helper object that implements the 
     *  ReceiveQObjectIfc interface.  If this helper object is supplied and
     *  the SendQObjectIfc helper is not supplied, then the object that implements
     *  the ReceiveQObjectIfc will be the next receiver for the QObject
     * 
     *  If neither helper object is supplied then a runtime exception will
     *  occur when trying to use the send() method     
     * @param qObj the completed QObject
     */
    protected void send(QObject qObj) {
        if (getSender() != null) {
            getSender().send(qObj);
        } else if (getNextReceiver() != null) {
            getNextReceiver().receive(qObj);
        } else {
            throw new RuntimeException("No valid sender or receiver");
        }
    }

}

7.4.1 Modeling Simple Queueing Stations

In this section, the station package is used to implement a simple queueing station. Recall the pharmacy model. In that model, we had a Poisson arrival process with customers arriving to a drive through pharmacy window. The service time associated with the pharmacy was exponentially distributed and their was one pharmacist. That situation was labeled as a M/M/1 queue. In this example, we generalize the queueing station to a G/G/c queue. That is, any general (G) arrival process, any general (G) service process, and (c) servers. We will model the arrival process using an EventGenerator and model the queueing with a Queue class. In addition, we will generalize the concept of the servers by encapsulating them in a class called SResource (for simple resource). Finally, the Queue and the SResource classes will be combined into one class that sub-classes Station, called SingleQueueStation. This is illustrated in Figure 7.5.

GGC Queueing Station Classes

Figure 7.5: GGC Queueing Station Classes

In the figure, the GGCQueueingStation class uses inner classes for handling the arrivals and for disposing customers that have completed service. In addition, GGCQueueingStation uses an instance of SingleQueueStation to process the customers after arrival.

Let’s first examine the organization of the GGCQueueingStation class and how it works with the Station package. Then, we will present how the SingleQueueStation class was implemented. As shown in Figure 7.5 the GGCQueueingStation class uses an instance of EventGenerator. The event generator’s action is called Arrivals, which is implemented as an inner class of GGCQueueingStation. Secondly, GGCQueueingStation uses an instance of the class Dispose, which implements the ReceiveQObjectIfc interface. This instance is used as the receiver after processing by the SingleQueueStation class. The class collects some system level statistics.

The following code listing shows the code for the GGCQueueingStation class. The constructor takes in instances of RandomIfc interfaces to represent the arrival and service processes. The arrival generator and statistical collection model elements are created within the constructor. The third line of the constructor creates the resource that is used by the arriving customers. The fifth line of the constructor creates an instance of a SingleQueueStation to handle the customers. Notice that the next line defines the next receiver for the station to be an instance of the class Dispose. When the SingleQueueStation is done, the send() method is called (as previously described) and since a receiver has been attached, the station uses this receiver to receive the QObject that was completed. In this case, the QObject has statistics collected as it leaves the system. The only other code to note is how the EventGenerator’s action is used to send the QObject to the SingleQueueStation. Within the Arrivals class, the receive() method of the SingleQueueStation instance is called with the created QObject. In essence, the GGCQueueingStation class simply defines and hooks up the various components needed to model the situation. The RecieveQObjectIfc and the Station concept play an integral role in facilitating how the object instances are connected.

public class GGCQueuingStation extends ModelElement {

    private final EventGenerator myArrivalGenerator;
    private final SingleQueueStation mySQS;
    private ResponseVariable mySystemTime;
    private TimeWeighted myNumInSystem;
    private final SResource myServers;
    private final RandomVariable mySTRV;

    public GGCQueuingStation(ModelElement parent, RandomIfc tba, RandomIfc st,
            int numServers) {
        this(parent, tba, st, numServers, null);
    }

    public GGCQueuingStation(ModelElement parent, RandomIfc tba, RandomIfc st,
                             int numServers, String name) {
        super(parent, name);
        myArrivalGenerator = new EventGenerator(this, new Arrivals(), tba, tba);
        myServers = new SResource(this, numServers, "Servers");
        mySTRV = new RandomVariable(this, st);
        mySQS = new SingleQueueStation(this, myServers, mySTRV, "Station");
        mySQS.setNextReceiver(new Dispose());
        mySystemTime = new ResponseVariable(this, "System Time");
        myNumInSystem = new TimeWeighted(this, "Num in System");
    }

    private class Arrivals implements EventGeneratorActionIfc {

        @Override
        public void generate(EventGenerator generator, JSLEvent event) {
            myNumInSystem.increment();
            mySQS.receive(new QObject(getTime()));
        }

    }

    protected class Dispose implements ReceiveQObjectIfc {

        @Override
        public void receive(QObject qObj) {
            // collect final statistics
            myNumInSystem.decrement();
            mySystemTime.setValue(getTime() - qObj.getCreateTime());
        }

    }

The last piece of this puzzle is how the SingleQueueStation class implements the abstract base class Station and becomes a component that can be reused in various models. Recall the logic for handling arrivals and service completions from the pharmacy example. In that code, there was the need to have the customer enter the queue and then check if the pharmacist was available. If the pharmacist was available, then the customer was removed from the queue and the end of service scheduled. In addition, when the service is completed, if the queue is not empty, then the next customer is started into service. The SingleQueueStation generalizes this same logic by using an instance of SResource to represent the servers.

The following code listing illustrates the critical code for the SingleQueueStation class. The receive() method (which must be implemented by a sub-class of Station), shows the QObject being placed in the queue. Then, the resource is checked to see if it is available and then the next customer is served via the call to serveNext(). Upon the end of service the EndServiceAction is called. Notice that the resource is released, and then the queue is checked. Finally, the send() method is used to send the QObject to whereever it is intended to go.

    protected double getServiceTime(QObject customer) {
        double t;
        if (getUseQObjectServiceTimeOption()) {
            Optional<GetValueIfc> valueObject = customer.getValueObject();
            if (valueObject.isPresent()){
                t = valueObject.get().getValue();
            } else {
                throw new IllegalStateException("Attempted to use QObject.getValueObject() when no object was set");
            }
        } else {
            t = getServiceTime().getValue();
        }
        return t;
    }

    /**
     * Called to determine which waiting QObject will be served next Determines
     * the next customer, seizes the resource, and schedules the end of the
     * service.
     */
    protected void serveNext() {
        QObject customer = myWaitingQ.removeNext(); //remove the next customer
        myResource.seize();
        // schedule end of service
        scheduleEvent(myEndServiceAction, getServiceTime(customer), customer);
    }

    @Override
    public void receive(QObject customer) {
        myNS.increment(); // new customer arrived
        myWaitingQ.enqueue(customer); // enqueue the newly arriving customer
        if (isResourceAvailable()) { // server available
            serveNext();
        }
    }

    class EndServiceAction implements EventActionIfc<QObject> {

        @Override
        public void action(JSLEvent<QObject> event) {
            QObject leavingCustomer = event.getMessage();
            myNS.decrement(); // customer departed
            myResource.release();
            if (isQueueNotEmpty()) { // queue is not empty
                serveNext();
            }
            send(leavingCustomer);
        }
    }

This is the same send() method that was previously discussed. Thus, because the receiver was set to be the instance of Dispose, the departing customer will be sent to the GGCQueueingStation where the statistics will be collected, as previously noted.

Figure 7.6 shows the SResource class. The SResource class models a resource that has a defined capacity, which represents the number of units of the resource that can be in use. The capacity of the resource represents the maximum number of units available for use. For example, if the resource has capacity 3, it may have 2 units busy and 1 unit idle. A resource cannot have more units busy than the capacity. A resource is considered busy when it has 1 or more units busy. A resource is considered idle when all available units are idle. Units of the resource can be seized via the seize() methods and released via the release() methods. It is an error to attempt to seize more units than are currently available. In addition, it is an error to try to release() more units than are currently in use (busy). Statistics on resource utilization and number of busy units is automatically collected.

The SResource Class

Figure 7.6: The SResource Class

The following listing illustrates how to build and run an instance of a GGCQueuingStation by simulating a M/M/2 queue.

    public static void main(String[] args) {
        Simulation sim = new Simulation("M/M/2");
        // get the model
        Model m = sim.getModel();
        // add system to the main model
        ExponentialRV tba = new ExponentialRV(1);
        ExponentialRV st = new ExponentialRV(.8);
        int ns = 2;
        GGCQueuingStation system = new GGCQueuingStation(m, tba, st, ns);
        // set the parameters of the experiment
        sim.setNumberOfReplications(30);
        sim.setLengthOfReplication(20000.0);
        sim.setLengthOfWarmUp(5000.0);
        SimulationReporter r = sim.makeSimulationReporter();
        System.out.println("Simulation started.");
        sim.run();
        System.out.println("Simulation completed.");
        r.printAcrossReplicationSummaryStatistics();
    }

This is the same as any JSL run. In this case, the GGCQueueingStation class used a SingleQueueStation to do, essentially, all of the work. Now, imagine many queueing stations organized into a system. Because the SingleQueueStation is built as a component that can be reused, it is available for more complicated modeling.

The following code illustrates how easy it is to model a tandem queue using the SingleQueueStation class. A tandem queue is a sequence of two queueing stations in series, where the customers departing the first staiton go on for further processing at the second station. The constructor shows the creation of two stations, where the receiver of the first station is set to the second station (line 9). The receiver for the second station is set to an instance of a class that implements the ReceiveQObjectIfc interface and collects the total time in the system and the number of customers in the system. This should begin to indicate how complex networks of queueing stations can be formed and simulated.

    public TandemQueue(ModelElement parent, String name) {
        super(parent, name);
        myTBA = new RandomVariable(this, new ExponentialRV(1.0/1.1));
        myST1 = new RandomVariable(this, new ExponentialRV(0.8));
        myST2 = new RandomVariable(this, new ExponentialRV(0.7));
        myArrivalGenerator = new EventGenerator(this, new Arrivals(), myTBA, myTBA);
        myStation1 = new SingleQueueStation(this, myST1, "Station1");
        myStation2 = new SingleQueueStation(this, myST2, "Station2");
        myStation1.setNextReceiver(myStation2);
        myStation2.setNextReceiver(new Dispose());
        mySysTime = new ResponseVariable(this, "System Time");
        myNumInSystem = new TimeWeighted(this, "NumInSystem");
    }

    protected class Arrivals implements EventGeneratorActionIfc {

        @Override
        public void generate(EventGenerator generator, JSLEvent event) {
            myNumInSystem.increment();
            myStation1.receive(new QObject(getTime()));
        }

    }

    protected class Dispose implements ReceiveQObjectIfc {

        @Override
        public void receive(QObject qObj) {
           // collect system time
            mySysTime.setValue(getTime() - qObj.getCreateTime());
            myNumInSystem.decrement();
        }
        
    }