You are viewing Nextmv legacy docs. ⚡️ Go to latest docs ⚡️

Apps

Random Queue

Overview of the random queue simulation app.

This example demonstrates the use of Nextmv's discrete event simulator, Dash, for customers using Nextmv Enterprise.

In this example, we generalize the single-server queue by randomly generating customers with interarrival times and service times from discrete uniform distributions. Interarrival time is just the time between subsequent arrivals.

Randomization helps us to capture the uncertainty of real-world problems.

Input Data

Instead of using the input to specify deterministic customers, we define two arrays of integer values: interarrival times and service times. Our simulation will later generate users by sampling with replacement from each of these lists. We also specify the maximum number of customers to generate (if time permits).

{
    "InterarrivalTimes": [5, 10, 3, 7, 8, 7, 2],
    "ServiceTimes": [20, 30, 40, 25, 15],
    "NumCustomers": 100
}
Copy

Configuration

In the previous example, we used the CLI runner to load our customers from input data. In this version of the model, we generate customers instead of specifying them in the input data, so we define a struct to hold the number of customers and distribution of times.

package main

import (
    "time"

    "github.com/nextmv-io/code/dash/sim/rand"
)

type config struct {
    InterarrivalTimes []int
    ServiceTimes      []int
    NumCustomers      int

    samplers struct {
        interarrival sample.Sampler
        service      sample.Sampler
    }
}
Copy

The first three values will be automatically unmarshaled from the JSON. To initialize the samplers, we define a method on this struct:

func (c *config) init() {
    c.samplers.interarrival = sample.WithReplacement(c.InterarrivalTimes)
    c.samplers.service = sample.WithReplacement(c.ServiceTimes)
}
Copy

Customers

The customer struct in this example is nearly identical to the single server queue:

type customer struct {
    Number       int
    Interarrival time.Duration // minutes after the last customer arrived
    Service      time.Duration

    events   ledger.Ledger
    measures ledger.Ledger

    arrivalTime *time.Time
    serviceTime *time.Time
}
Copy

The customer Run method and measures also don't have to change from the earlier example. However, we add a new function newCustomer which generates a random customer by sampling from the interarrival time and service time distributions.

func newCustomer(num int, cfg *config) *customer {
    return &customer{
        Number:       num,
        Interarrival: cfg.interarrival(),
        Service:      cfg.service(),
    }
}
Copy

Server

This example is able to reuse the same single server code from the previous example.

Runner

The runner in this example changes to handle the dynamic creation of customers. Instead of taking customers as input, the Run function takes a config object. This config object has to be initialized inside the run function to ensure the samplers are initialized with the input values.

The runner creates NumCustomers customers and adds them to the simulator's queue.

func main() {

    cli.Run(
        func(cfg *config, opt sim.Options) (sim.Simulator, error) {
            cfg.init()

            simulator := sim.New(opt)

            now := time.Now()
            var cumulativeTime time.Duration

            for i := 0; i < cfg.NumCustomers; i++ {
                c := newCustomer(i, cfg)

                cumulativeTime += c.Interarrival
                arrival := now.Add(cumulativeTime)
                simulator.Add(arrival, c)
            }

            simulator.Add(now, &server{})

            return simulator, nil
        },
    )
}
Copy

We can run this example just like the single-server queue. Using jq, we can look at the wait time measures:

./random-queue -dash.runner.input.path input.json \
               -dash.simulator.limits.duration 2h \
               -dash.runner.output.measures | \
jq .measures[].measure
Copy
{ "Number": 0, "Minutes":  0 }
{ "Number": 1, "Minutes": 10 }
{ "Number": 2, "Minutes": 35 }
{ "Number": 3, "Minutes": 58 }
Copy

Despite this being the randomized simulation example, if you rerun that command, you will get the same output as a result of Go using a default random seed. However, you can set the random seed with the -dash.simulator.random.seed flag.

By setting a new random seed, we get a different simulation

./random-queue -dash.runner.input.path input.json \
               -dash.simulator.random.seed 234 \
               -dash.simulator.limits.duration 2h \
               -dash.runner.output.measures | \
jq .measures[].measure
Copy
{ "Number": 0, "Minutes":  0 }
{ "Number": 1, "Minutes":  5 }
{ "Number": 2, "Minutes": 17 }
{ "Number": 3, "Minutes": 25 }
{ "Number": 4, "Minutes": 62 }
{ "Number": 5, "Minutes": 77 }
Copy

To get different result each time, you could set the seed to $(date +%s) or $RANDOM.

Page last updated

Go to on-page nav menu