Models, Components and Experiments

Model declarations

Use cimba.sim for modeling. A model is a Model subclass whose annotated fields are typed by their simulation role:

Model, Component, Experiment, Env, Handle, Param, Output, State, FloatState, Const, Queue, Resource, Pool, Store, Dataset, Condition, Predicate, Event, Processes, PQueues, Ref, Refs, Spawnable, Struct, Trace, capacity(), count(), process(), collect().

sim.Param values are expanded into parameter combinations, sim.Output values are collected after each trial, sim.State and sim.FloatState hold mutable trial-local state, and entities such as sim.Queue, sim.Resource, sim.Pool, sim.Store, sim.PQueues, sim.Condition, and sim.Dataset are created for each trial.

import cimba.sim as sim

class Clinic(sim.Model):
    arrival_rate: sim.Param
    wait_time: sim.Output
    queue: sim.Queue
    doctor: sim.Resource
    waits: sim.Dataset

model = Clinic("clinic")

Components

Components group related declarations and process methods. Methods decorated with top-level @sim.process are lowered into ordinary model processes at model construction, and model callbacks can read component fields with env.retailer.orders. Component fields are exposed in experiments with flattened names such as retailer__orders. Methods decorated with top-level @sim.collect run once per instance at the end of each trial, before the model-level @model.collect callback, typically assigning the component’s sim.Output fields.

Components may contain other components, and flattened names follow the same recursive convention, for example env.attraction.queues.line becomes attraction__queues__line. Nested component process methods are also lowered with their component path in the process name.

Components may declare sim.Spawnable fields. A component-owned spawnable binds to a same-named @sim.process method on that component, and can be spawned from component or model code with natural paths such as sim.spawn(self.visitor, env) or sim.spawn(env.park.entrance.visitor, env). Spawnable component processes may receive a final sim.Struct view parameter.

Components may reference other declared components with sim.Ref[Target] fields and routing tables of collection items with sim.Refs[Target], letting method bodies route through paths such as self.downstream.inbox.put(h) or self.routes[i].inbox.put(h); see Components for wiring and routing details.

Fixed repeated structures can be declared with standard list[Component] annotations, for example attractions: list[Attraction] = [...]. Model callbacks can use indexed access such as env.attractions[i].queues[j]; runtime fields remain flattened, for example attractions__queues. Nested collections are linearized behind the scenes, so env.campus.zones[i].gates[j].queue remains valid model source while the trial table stores a one-dimensional campus__zones__gates__queue field.

Per-process fields

Declare a sim.Struct subclass with float and int annotations. A process can receive its own field view as a final annotated parameter: def visitor(env, view: Visitor). Multi-copy processes can also receive the copy index: def visitor(env, idx, view: Visitor). Visitor(handle) returns a read/write view of another process’s fields when model code already has that process handle.

Process graphs

Call model.process_dag() to infer a resource-aware graph from registered process bodies. The returned ProcessDAG contains ProcessDAGNode and ProcessDAGEdge records for processes and model fields, and can render Mermaid or Graphviz DOT text. The inference follows direct sim calls, simple aliases, helper functions called with env, spawnables, stores, priority queues, conditions, events, mutable state, and shared resources:

graph = model.process_dag()
print(graph.to_mermaid())
print(graph.to_dot())

Experiments

model.experiment(...) returns an Experiment; exp.run() executes the trial table in place and returns the number of failed trials, and exp["field"] reads any trial column as an array. exp.summary() condenses the outputs across replications: it returns a structured array with one record per design point holding the swept parameter values and, for each output, its replication mean (name) and Student-t confidence-interval half-width (name_hw, 95% by default):

exp = model.experiment(utilization=[0.7, 0.8, 0.9], replications=20,
                       duration=10_000.0, seed=42)
exp.run()
for row in exp.summary("avg_wait"):
    print(f"rho={row['utilization']:.1f}  "
          f"wait={row['avg_wait']:.2f} +- {row['avg_wait_hw']:.2f}")

exp.summary("a", "b", confidence=0.99) selects outputs and the confidence level; failed trials (NaN) are excluded per output. exp.replications and exp.swept expose the layout (trial order is design-point-major with replications innermost).