Structs and Spawnables
Core models often treat arrivals as counts. Agent-heavy models need each
arrival to carry its own state: arrival time, priority, service choice,
patience, or accumulated outcomes. Use sim.Struct for per-process fields
and sim.Spawnable for processes created during a trial.
Per-process fields
A sim.Struct declares fields stored with a process. Fields may be int
or float:
class Patient(sim.Struct):
arrival: float
acuity: int
wait_started: float
class Clinic(sim.Model):
patients: sim.Spawnable
completed: sim.State
model = Clinic("clinic")
@model.process
def patients(env: Clinic, patient: Patient):
patient.wait_started = sim.now()
# The process can use its own fields throughout the visit.
The final annotated process parameter receives the current process’s struct view. Multi-copy static processes can also receive a copy index before the struct view.
Dynamic process creation
Declare a sim.Spawnable field with the same name as the process that should
be created dynamically:
import cimba.random as random
@model.process
def arrivals(env: Clinic):
while True:
sim.hold(random.exponential(1.0 / env.arrival_rate))
handle = sim.spawn(env.patients, env)
patient = Patient(handle)
patient.arrival = sim.now()
patient.acuity = 1 if random.uniform() < 0.2 else 0
The spawned process begins only after the current process blocks. That gives the spawning process a clean initialization window: create the process, write its struct fields through the handle, and then let simulated time continue.
Joining and reclaiming
sim.wait_process(handle) waits for a spawned process to finish.
sim.despawn(handle) reclaims a finished spawned process:
@model.process
def arrivals(env: Clinic):
handle = sim.spawn(env.patients, env)
Patient(handle).arrival = sim.now()
sim.wait_process(handle)
sim.despawn(handle)
Long-running models should reclaim finished dynamic processes when they are no
longer needed. A common pattern is to put finished handles into a sim.Store
and have a cleanup process despawn them:
class Clinic(sim.Model):
patients: sim.Spawnable
departures: sim.Store
@model.process
def cleanup(env: Clinic):
while True:
handle = env.departures.take()
sim.despawn(handle)
@model.process
def patients(env: Clinic, patient: Patient):
# ... patient journey ...
env.departures.put(sim.current())
Leftover spawned processes are stopped and reclaimed at the end of a trial, but explicit cleanup keeps long trials from accumulating completed agents.
Component-owned spawnables
Components can own spawnable fields. This keeps dynamic agents close to the subsystem that creates them:
class Intake(sim.Component):
patient: sim.Spawnable
@sim.process
def arrivals(self, env):
handle = sim.spawn(self.patient, env)
Patient(handle).arrival = sim.now()
@sim.process
def patient(self, env, p: Patient):
sim.hold(random.exponential(env.mean_service))
Use this when the dynamic process is naturally part of a component. Use a
model-level sim.Spawnable when the dynamic process crosses many domains.
For a larger worked example with dynamic agents and resources, see Tutorial: Modeling with Cimba Python.