Skip to content

Standard vs. Low-Level Paradigms

Dynamic DES provides two ways to build your event-driven simulations, allowing you to choose between ease of use and raw control.


Two Paradigms

Feature Standard API (Declarative) Low-Level API (Imperative)
Entry Point SimulationContext DynamicRealtimeEnvironment
Philosophy Define what the system looks like and use decorators for task lifecycles. Define how every event and resource operates step-by-step.
Boilerplate Low (Automatic event emission, resource requesting, and sampling). High (Manual queueing, starting, timing out, and releasing).
Typical Use Case Building standard digital twins, historical data generation, and forecasting pipelines. Edge-case scenarios requiring dynamic topology changes mid-run.

1. Standard API (Declarative)

The Standard API uses the SimulationContext builder to configure the twin's resources, distributions, ingress/ingress parameters, and I/O connectors.

All execution logic is declared using clean Python decorators:

from dynamic_des import SimulationContext, ConsoleEgress

app = (
    SimulationContext(sim_id="Line_A", factor=1.0)
    .add_resource("lathe", current_cap=2)
    .add_arrival("standard", dist="exponential", rate=1.0)
    .add_service("milling", dist="normal", mean=3.0, std=0.5)
    .add_egress(ConsoleEgress())
)

@app.arrival_loop("standard")
def generate(context: SimulationContext):
    task_id = 0
    while True:
        yield context.wait_for_arrival("standard")
        context.spawn(run_task(task_id))
        task_id += 1

@app.task(service_id="milling", resource_id="lathe")
def run_task(task_id: int):
    # This function is automatically wrapped with:
    # 1. Emission of a "queued" event to Kafka/Console.
    # 2. Block until the "lathe" resource is acquired.
    # 3. Emission of a "started" event.
    # 4. Yield of the "milling" timeout (sampled from the distribution).
    # 5. Emission of a "finished" event with the dictionary returned below.
    return {"part_id": task_id}

2. Low-Level API (Imperative)

The Low-Level API exposes DynamicRealtimeEnvironment directly. You are responsible for instantiating and configuring the registry, setting up I/O connectors manually, and writing raw SimPy generators.

This is ideal when you need to bypass standard telemetry rules or dynamically construct new topics and environments on the fly.

import numpy as np
from dynamic_des import DynamicRealtimeEnvironment, DynamicResource, Sampler, ConsoleEgress

env = DynamicRealtimeEnvironment(factor=1.0)
egress = ConsoleEgress()
env.setup_egress([egress])

res = DynamicResource(env, "Line_A", "lathe")
sampler = Sampler(rng=np.random.default_rng(42))

def manual_generator(env, res):
    task_id = 0
    while True:
        # Manual arrival sampling
        yield env.timeout(1.0)

        # Manual event emission
        task_key = f"task-{task_id}"
        env.publish_event(task_key, {"status": "queued"})

        # Manual resource requesting
        with res.request() as req:
            yield req
            env.publish_event(task_key, {"status": "started"})
            yield env.timeout(3.0)  # Manual service duration
            env.publish_event(task_key, {"status": "finished"})

        task_id += 1