5.5 Sequential Sampling for Finite Horizon Simulations

The methods discussed in Section 3.3.2 of Chapter 3 for determining the sample size are based on pre-determining a fixed sample size and then making the replications. If the half-width equation is considered as an iterative function of \(n\):

\[h(n) = t_{\alpha/2, n - 1} \dfrac{s(n)}{\sqrt{n}} \leq E\]

Then, it becomes apparent that additional replications of the simulation can be executed until the desired half-with bound is met. This is called sequential sampling, and in this case the sample size of the experiment is not known in advance. The brute force method for implementing this approach would be to run and rerun the simulation each time increasing the number of replications until the criterion is met.

To implement this within the KSL, we need a way to stop or end a simulation when a criteria or condition is met. Because of the hierarchical nature of the model elements within a model and because there are common actions that occur when running a model the Observer pattern can be used here.

Half-Width Observer Checking Code

Figure 5.8: Half-Width Observer Checking Code

Figure 5.8 presents part of the ksl.observers package which defines a base class called ModelElementObserver that can be attached to instances of ModelElement and then will be notified if various actions take place. There are a number of actions associated with a ModelElement that occur during a simulation that can be listened for by a ModelElementObserver:

  • beforeExperiment() - This occurs prior to the first replication and before any events are executed.

  • beforeReplication() - This occurs prior to each replication and before any events are executed. The event calendar is cleared after this action.

  • initialize() - This occurs at the start of every replication (after beforeReplication() and after the event calendar is cleared) but before any events are executed. As we have seen, it is safe to schedule events in this method.

  • warmUp() - This occurs during a replication if a warm up period has been specified for the model. The statistical accumulators are cleared during this action if applicable.

  • replicationEnded() - This occurs at the end of every replication prior to the clearing of any statistical accumulators.

  • afterReplication() - This occurs at the end of every replication after the statistical accumulators have been cleared for the replication.

  • afterExperiment() - This occurs after all replications have been executed and prior to the end of the simulation.

ModelElementObservers are notified right after the ModelElement experiences the above mentioned actions. Thus, users of the ModelElementObserver need to understand that the state of the model element is available after the simulation actions of the model element have occurred.

The AcrossReplicationHalfWidthChecker class listens to the afterReplication() method of a Response instance and checks the current value of the half-width. This is illustrated in the following code listing.

    override fun afterReplication(modelElement: ModelElement) {
        if (modelElement.model.currentReplicationNumber <= 2){
            return
        }
        val statistic = myResponse.myAcrossReplicationStatistic
        val hw = statistic.halfWidth(confidenceLevel)
        if (hw <= desiredHalfWidth){
            modelElement.model.endSimulation("Half-width = ($desiredHalfWidth) condition met for response ${myResponse.name}")
        }
    }

Notice that a reference to the observed Response is used to get access to the across replication statistics. If there are more than 2 replications, then the half-width is checked against a user supplied desired half-width. If the half-width criterion is met, then the simulation is told to end using the Model class’s endSimulation() method. The endSimulation() method causes the simulation to not execute any future replications and to halt further execution.

All that is needed is to get a reference to the response variable that needs to be checked so that the observer can be attached. This can be done easily if a reference to the response is exposed through the class’s interface. In this case the property, totalProcessingTime, an instance of ResponseCIfc, is used. Alternatively if the string name representation of the response is available, it can be used to extract the response from the model via the model’s functionality for getting model elements.

fun response(name: String): Response? 
fun timeWeightedResponse(name: String): TWResponse? 
fun counter(name: String): Counter? 

Example 5.2 (Sequential Sampling) The following code listing illustrates how to set up half-width checking for the pallet processing model.

fun main() {
    val model = Model("Pallet Processing Ex 2")
    model.numberOfReplications = 10000
    model.experimentName = "Two Workers"
    // add the model element to the main model
    val palletWorkCenter = PalletWorkCenter(model)

    val hwc = AcrossReplicationHalfWidthChecker(palletWorkCenter.totalProcessingTime)
    hwc.desiredHalfWidth = 5.0

    // simulate the model
    model.simulate()

    // demonstrate that reports can have specified confidence level
    val sr = model.simulationReporter

    sr.printHalfWidthSummaryReport()

}

Notice that the number of replications is set to an arbitrarily high value (10000) and the desired half-width is specified as 5.0. In the original example, the half-width for the total processing time was 16.3269 based on 10 replications. As we can see from the following output, the half-width criteria has been met with 149 replications.

Half-Width Statistical Summary Report - Confidence Level (95.000)% 

Name                                                Count         Average      Half-Width 
----------------------------------------------------------------------------------------- 
NumBusyWorkers                                        149          1.9197          0.0101 
PalletQ:NumInQ                                        149          7.3231          0.4997 
PalletQ:TimeInQ                                       149         44.2002          2.9565 
Num Pallets at WC                                     149          9.2428          0.5047 
System Time                                           149         55.8544          2.9579 
Total Processing Time                                 149        495.6675          4.9841 
P{total time > 480 minutes}                           149          0.7114          0.0736 
Num Processed                                         149         81.5503          0.6339 
-----------------------------------------------------------------------------------------