Coming from SimPy
If you have written a SimPy model, you already know most of what desflow needs. This page takes a plain SimPy model in the structure most tutorials teach and shows where each part goes in desflow. Nothing you know is lost: a desflow model is SimPy underneath, and SimPy and desflow shows each construct.
The structure comes from the open book HSMA: the little book of DES by Dan Chalk, Sammi Rosser and Amy Heather, which teaches SimPy to health-service modellers. Its chapter The Recommended Structure for DES Models splits a model into four classes: a class of global parameters, an entity class, a model class and a trial class. We read that chapter and the chapter on running large numbers of scenarios. The book's text is under CC BY-NC-SA, so none of it is copied here: the code below is our own, on the packing shop, and follows the same four-class shape.
Four Classes
Look at the table for where each class goes. The sections after it explain each row on the shop.
| In a plain SimPy model | In desflow |
|---|---|
| The global parameters class: arrival and packing times, packers, run length, number of runs | Arguments of add_arrival, add_service and add_resource, which are registry paths a run can change, and add_variable (variables in YAML) for values of your own |
| The entity class: the thing that moves through the model, with the facts it remembers | An entity with attributes, and statistics that record each order's wait and service for you |
| The model class: one run, with its environment, resources, arrival generator and journey generator | SimulationContext, with @app.arrival_loop for the arrival generator and @app.task for the journey |
| The trial class: a loop that builds a model for each run number and collects one result per run | replicate, which runs a model n times from spawned seeds and gives an interval over the runs |
Global Parameters
In the usual structure one class holds every number the model reads, and the model reads them as G.packers. This is the shop's.
| docs/snippets/pages/coming_from_simpy.py:globals | |
|---|---|
In desflow these numbers are arguments of the parts they describe: add_arrival("orders", dist="exponential", rate=0.5), add_service("packing", dist="constant", value=1.5) and add_resource("packers", current_cap=1, max_cap=10). Each is stored in a registry under a path such as Shop.resources.packers.current_cap, and the model reads the registry whenever it needs the value. So the difference from G is that a value can change while the run goes on, from a scenario or a connector, and each change is checked before it is applied. A number of your own, such as a conveyor speed, goes in add_variable in the builder and variables in YAML. The run length and the number of runs are arguments of run and replicate, because they belong to the run and not to the system. Live Parameters lists the paths.
Entity Class
The entity class is what travels through the model. Here an order carries its number and the time it waited.
| docs/snippets/pages/coming_from_simpy.py:entity | |
|---|---|
In desflow an arrival creates an entity for you, and attributes on the arrival give it starting facts, such as attributes={"express": 0}. A task can read them to decide what comes next, and change them when it finishes. You do not store the wait yourself: the task records when the order queued, started and finished, and the statistics report the mean wait and service time of every task. Task Flow explains attributes.
Model Class
The model class is one run. Its constructor makes the environment and the resources, one generator creates the arriving orders, and another is the journey of one order.
In desflow, SimulationContext is that class. It makes the environment, which is a SimPy environment, and the resources from the add_ calls, so the constructor of your own class has nothing left to do. The arrival generator is @app.arrival_loop, and the journey is @app.task, which requests the resource, waits, holds it for the service time and releases it. The task replaces the pack_order method above, and it also publishes queued, started and a finished event as records. If the journey is more than one task, a task names the next one, as A Flow of Tasks shows.
Trial Class
The trial class runs the model once for each run number and keeps one result per run. Here the run number also seeds the random generator, so each run differs and each can be repeated.
| docs/snippets/pages/coming_from_simpy.py:trial | |
|---|---|
In desflow this loop is replicate. It runs the model n times, gives each run its own seed spawned from one seed, reads the statistics at the end of each run, and returns the interval for each measure you name. You do not write the loop, the seeding or the interval. Replications explains the options, and Trusting Results uses it on the shop.
Shop in desflow
Here is the whole shop in desflow, with the trial loop beside it. The model is one function that builds a context, and the loop is one call.
Both versions run 10 times for one hour from an empty shop, so the two answers estimate the same thing. They use different random numbers, so the values differ, but the check in the script asserts that they agree within their intervals.
What you take with you is the SimPy you wrote. A model can still be a SimPy generator inside desflow, through the low-level API, and it keeps the SimPy constructs it uses. What you leave behind is the code around it: the parameters class, the result bookkeeping and the trial loop. What you gain is records, pacing, live parameters and the checks on Output Analysis.