Dash simulators are event-based simulations of actor decisions. Dash uses Discrete Event Simulation (DES). Increasingly, complex systems are composed out of small, decoupled, components that communicate by publishing and subscribing to events. Dash makes modeling these systems easy so simulations function more like the environments they are intended to model.
This section introduces some of the basic mechanics of how Dash works and how it represents the world. It does not cover simulation as a field. There are many excellent references that provide a thorough foundation in simulation, including the following text.
Averill M. Law. Simulation Modeling and Analysis. 5th ed. McGraw-Hill, 2014.
Dash is a simulator that runs a given model using a single set of inputs. This estimates how a single scenario might play out. Comparing different models or scenarios requires running multiple, often many, simulations.
At a high level, Dash reads JSON input describing the state of a system and constructs actors based on that state. It then simulates the behaviors and interactions of those actors through events. Actors maintain state and record measurements of values important to the system. In order to leverage this simulator, Dash requires the modeler to define actors and their decision and communication logic.
Dash provides a simulator type which is responsible for stepping through simulated time, scheduling actors, passing messages in the form of events, and tracking output measures the modeler wants from a simulation. To do this, a Dash simulator requires one or more actors.
An actor is a model-specific structure with a
Run method. Dash calls
Run with the current simulation time whenever an actor is next scheduled. The actor should then update its state based on the current simulation time and any events it has received, and return the next time it should be run and a boolean indicator stating whether it has anything left to do in the simulation.
Dash always runs the earliest scheduled actor first. Actors are scheduled when they are first added to a simulation, then by subsequent calls to their
Run methods. These times must always move forward and never backward, otherwise history could be rewritten. Dash runs simulations until either there are no more actors left or it meets a simulated time duration limit.
Similarly to Hop, once the modeler has codified actors and their guiding decision and communication rules, it's time to integrate with existing production systems. Typical Dash integration architecture involves real-time data flowing through to an "iteration wrapper," which is responsible for calling Dash using the desired option settings. The wrapper will run Dash for
n iterations and store the KPI performance from each run. A separate analysis process will typically receive the simulated KPIs, analyze performance against actuals, and recommend changes. This integration flow enables rapid, targeted experimentation without the operational risks typically involved in real-world experimentation.
This model simulates an ordered set of customers entering a line and waiting for service. One server processes customers in a first-in-first-out manner. The primary measurement provided by the simulation is customer wait time in the queue.
There are a number of ways to model such a system. Using Dash, the most natural method is to represent customers and the server as actors. These actors maintain internal state and use events to signal and trigger state transitions. Let's walk through an example of what that looks like.
Say we are simulating two customers, with arrival time as the number of minutes after the start of the simulation that the customer arrives, and the service time being the time required to service them:
|Customer||Arrival Time||Service Time|
At the start of the simulation, no customer has arrived and the server is idle. We have the following actor states:
At time 0, Dash calls customer 0's
Run method, which publishes an arrival event. The server, subscribing to that event, adds customer 0 to their queue. Customer 0's arrival time is the current time in the simulation.
|Server||Idle||Queue: [Customer 0]|
|Customer 0||Waiting||Arrival: 0|
Dash then runs the server, which publishes a service event for customer 0. Since customer 0's service time is 3, the server is now busy for 3 minutes. Customer 0 does not have to wait in the queue for service, so their arrival and service times are the same. Customer 0 publishes a wait time measurement of 0. This results in the following states, still at simulation time 0:
|Customer 0||In Service||Arrival: 0, Service 0|
Customer 1 arrives at time 1. The server is busy, so they wait to be served.
|Server||Busy||Queue: [Customer 1]|
|Customer 0||In Service||Arrival: 0, Service 0|
|Customer 1||Waiting||Arrival: 1|
At time 3, the server finishes serving customer 0 and starts serving customer 1. Customer 1 publishes a wait time measurement of 2. Customer 0 exits the simulation.
|Customer 1||In Service||Arrival: 1, Service: 3|
Finally, the server finishes with customer 1 at time 13 and they exit the simulation.
The tables above show the states of actors in our simulation. We can also examine the event log, which is maintained and made available by Dash. Events are just data, and can have attributes like actors.
|0||Customer 0||Arrival||Customer: 0|
|1||Customer 1||Arrival||Customer: 1|
The measure ledger for our example has just two records.
|Customer 0||Wait Time||0|
|Customer 1||Wait Time||2|
These events make it possible to replay exactly what happens in the simulation, and how wait time measurements can be computed directly from them. It is fairly simple to add additional events and measures, such as customer departures and total time in the system.