Skip to content

Dynamic Topology (Absolute Edge Cases)

The Standard API (SimulationContext) relies on an immutability constraint: all physical resources, service distribution schemas, and I/O connections must be fully configured before the clock starts running.

In some rare digital twin applications, the physical layout itself changes dynamically during execution (e.g., a shipping dock provisions a new bay, or a network threshold triggers the dynamic creation of new Kafka topics and data formats mid-simulation).

If your twin requires dynamic topology mutation, you must bypass SimulationContext and write purely imperative code against the low-level DynamicRealtimeEnvironment and the flat registry keys.


Code Example: Mutating the Infrastructure Registry Mid-Run

This example shows how to dynamically register a brand new resource and update flat paths in the registry mid-simulation.

import logging
import numpy as np
from dynamic_des import (
    DynamicRealtimeEnvironment,
    DynamicResource,
    Sampler,
    ConsoleEgress,
    CapacityConfig,
    SimParameter
)

logging.basicConfig(level=logging.INFO)

# 1. Start with a minimal initial configuration
initial_params = SimParameter(
    sim_id="Line_A",
    resources={"lathe": CapacityConfig(current_cap=1, max_cap=5)}
)

env = DynamicRealtimeEnvironment(factor=1.0)
env.registry.register_sim_parameter(initial_params)
env.setup_egress([ConsoleEgress()])

# Active resources map
resources = {
    "lathe": DynamicResource(env, "Line_A", "lathe")
}

def worker(env, resource_name: str, task_id: int):
    res = resources[resource_name]
    with res.request() as req:
        yield req
        print(f"Task {task_id} started on {resource_name} (capacity: {res.capacity})")
        yield env.timeout(3.0)
        print(f"Task {task_id} finished on {resource_name}")

def supervisor(env):
    """
    Acts as the topology supervisor, adding new physical machines
    when queues become too long.
    """
    task_id = 0
    while True:
        yield env.timeout(4.0)

        # At t=12.0s, dynamically provision a second resource 'molder'
        # that didn't exist when the simulation started!
        if env.now >= 12.0 and "molder" not in resources:
            print("--- Supervisor: Registering new resource 'molder' ---")

            # 1. Update the schema config in the registry
            env.registry.update_value("Line_A.resources.molder.max_cap", 5)
            env.registry.update_value("Line_A.resources.molder.current_cap", 2)

            # 2. Instantiate and register the new SimPy DynamicResource object
            resources["molder"] = DynamicResource(env, "Line_A", "molder")

        # Spawn tasks on the lathe
        env.process(worker(env, "lathe", task_id))

        # If the molder is available, also route tasks to it
        if "molder" in resources:
            env.process(worker(env, "molder", task_id))

        task_id += 1

if __name__ == "__main__":
    env.process(supervisor(env))
    env.run(until=20.0)