Efficient Tree Search in SimPy: Environment Snapshotting for Reuse

23 views
Skip to first unread message

henrikw

unread,
Jun 20, 2025, 6:37:46 AMJun 20
to python-simpy

Hi everyone,

I'm working on a complex simulation project using SimPy, where I'm modeling real-world processes in a digital twin for optimization purposes. For the optimization phase, I use a tree search strategy (similar to MCTS or other decision tree explorations), where each node represents a state of the system and a decision taken at that point.

The challenge I'm facing is related to simulation performance during deep tree searches. Each time I simulate a new node in the tree, I deterministically replay all prior actions and decisions from the root to that node. As the tree grows deeper, this becomes increasingly inefficient because SimPy has to replay a long sequence of events using env.run(until=event) repeatedly. Additionally, the simulation is already quite slow because at specific events I perform computationally heavy side-calculations (offloaded to another process or thread), and the simulation must pause until those are complete. I currently use env.run(until=external_event) to achieve this blocking behavior.

From my understanding, using yield in a process does not truly “pause” the simulation globally — it just pauses that process. But I need the entire simulation to halt until an external computation completes.

To improve performance, I'm now exploring the idea of snapshotting the simulation state (or environment) at certain points, so I can clone or fork from a prior state instead of re-running everything from the beginning.

I’m aware that directly copying a SimPy Environment (or Process) isn’t feasible because it relies on Python generators, which cannot be cloned or pickled. However, I’m wondering if there’s a clever workaround to:

  • Capture the state of the environment at a given time (e.g., event queue, time, resources),
  • Restore this state into a new environment to simulate a different future branch.

I assume this involves serializing custom state (resources, variables, etc.), and ideally also capturing the event heap (or rebuilding it). However, I’m not sure if this is feasible or safe within SimPy’s architecture.

To illustrate the idea, here's a small example that mimics my current simulation structure:

import simpy


def worker(env, name, external_event):
    print(f'{env.now}: {name} starts')
    yield env.timeout(5)
    print(f'{env.now}: {name} waiting for external event')
    yield external_event
    print(f'{env.now}: {name} resumes after external event')


def run_simulation(branch_decision):
    env = simpy.Environment()

    # Simulate some setup process
    external_event = env.event()
    env.process(worker(env, f"Worker-{branch_decision}", external_event))

    # Simulate until the external computation point
    env.run(until=6)  # Simulation halts before external_event is triggered

    # Here I would like to "snapshot" the environment to reuse this state later
    # Instead of running everything from scratch every time

    # External computation finishes
    external_event.succeed()

    env.run()  # Finish simulation


if __name__ == "__main__":
    for i in range(3):  # Simulate different decisions
        print(f"=== Branch {i} ===")
        run_simulation(i)

In practice, the simulation and external computations are much more complex and the tree search may involve thousands of branches, where each one repeats the same deterministic prefix. So if I could “fork” the simulation after some common prefix (say time t=6 above), I would save massive amounts of computation.

My Questions:

  1. Has anyone attempted a snapshot/fork strategy with SimPy before?
  2. Is it possible to serialize or recreate the Environment (or a reduced version of it) by manually capturing and re-instantiating resources, clocks, and event queues?
  3. Would a more radical approach (like re-architecting SimPy process logic to allow easier checkpointing) be needed?
  4. Any best practices for blocking the entire simulation until an external process signals readiness?

Any guidance, workarounds, or thoughts are greatly appreciated!

Best regards,

Henrik

Andrei Savu

unread,
Jun 20, 2025, 12:20:48 PMJun 20
to henrikw, python-simpy
Hi Henrik,

I think the easiest way to achieve your goals on a Linux system is to use os.fork() and build on the copy-on-write memory semantics like this:

env.run(until=t_checkpoint)      # warm‑up to a common prefix
pid = os.fork()
if pid == 0:                     # child – explore one branch
    external_event.succeed()
    env.run()
    post_results_and_exit()
else:                            # parent – back at the fork point
    children.append(pid)


You will need a side channel for the child processes to return the results to be aggregated.

-- Andrei

--
You received this message because you are subscribed to the Google Groups "python-simpy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-simpy...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/python-simpy/33dd0d14-a2a9-42bf-a8c0-8c756f71905cn%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages