Skip to main content

sim_lib_topology/
model.rs

1//! Public topology graph model: graphs, nodes, edges, ports, cells, budgets.
2//!
3//! These are the authoring value types that topology data compiles from.
4
5use sim_kernel::{Expr, Symbol};
6
7/// Canonical topology data API tag used by serialized graphs.
8pub const TOPOLOGY_API: &str = "sim.topology.v3";
9
10/// Default graph version for newly constructed in-memory graphs.
11pub const DEFAULT_GRAPH_VERSION: &str = "0.1.0";
12
13/// Default maximum scheduler steps for a graph run.
14pub const DEFAULT_MAX_STEPS: u32 = 256;
15
16/// Default maximum visits to any node during a graph run.
17pub const DEFAULT_MAX_NODE_VISITS: u32 = 64;
18
19/// Default maximum traversals of any edge during a graph run.
20pub const DEFAULT_MAX_EDGE_VISITS: u32 = 64;
21
22/// Stable node identifier inside a topology graph.
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct NodeId(pub Symbol);
25
26impl NodeId {
27    /// Creates a node id from an unqualified symbol name.
28    pub fn new(name: impl Into<String>) -> Self {
29        Self(Symbol::new(name.into()))
30    }
31
32    /// Returns the underlying symbol.
33    pub fn as_symbol(&self) -> &Symbol {
34        &self.0
35    }
36}
37
38impl From<Symbol> for NodeId {
39    fn from(value: Symbol) -> Self {
40        Self(value)
41    }
42}
43
44impl From<&str> for NodeId {
45    fn from(value: &str) -> Self {
46        Self::new(value)
47    }
48}
49
50impl From<String> for NodeId {
51    fn from(value: String) -> Self {
52        Self::new(value)
53    }
54}
55
56/// Stable edge identifier inside a compiled or parsed topology graph.
57#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
58pub struct EdgeId(pub u32);
59
60impl EdgeId {
61    /// Creates an edge id from a stable zero-based index.
62    pub const fn new(index: u32) -> Self {
63        Self(index)
64    }
65}
66
67impl From<u32> for EdgeId {
68    fn from(value: u32) -> Self {
69        Self(value)
70    }
71}
72
73/// Reference to a named port on a node.
74#[derive(Clone, Debug, PartialEq, Eq, Hash)]
75pub struct PortRef {
76    /// The node that owns the referenced port.
77    pub node: NodeId,
78    /// The referenced port name.
79    pub port: Symbol,
80}
81
82impl PortRef {
83    /// Creates a port reference from an explicit node and port symbol.
84    pub fn new(node: impl Into<NodeId>, port: Symbol) -> Self {
85        Self {
86            node: node.into(),
87            port,
88        }
89    }
90
91    /// Creates a port reference from an unqualified port name.
92    pub fn named(node: impl Into<NodeId>, port: impl Into<String>) -> Self {
93        Self::new(node, Symbol::new(port.into()))
94    }
95
96    /// Creates a destination reference using the default `in` port.
97    pub fn input(node: impl Into<NodeId>) -> Self {
98        Self::named(node, "in")
99    }
100
101    /// Creates a source reference using the default `out` port.
102    pub fn output(node: impl Into<NodeId>) -> Self {
103        Self::named(node, "out")
104    }
105}
106
107/// Canonical in-memory representation of topology graph data.
108#[derive(Clone, Debug)]
109pub struct Graph {
110    /// Graph name.
111    pub name: Symbol,
112    /// Graph artifact version.
113    pub version: String,
114    /// Canonical topology data API tag.
115    pub api: String,
116    /// Optional public input shape expression.
117    pub input: Option<Expr>,
118    /// Optional public output shape expression.
119    pub output: Option<Expr>,
120    /// Graph nodes in deterministic declaration order.
121    pub nodes: Vec<Node>,
122    /// Graph edges in deterministic declaration order.
123    pub edges: Vec<Edge>,
124    /// Graph state cells.
125    pub cells: Vec<Cell>,
126    /// Run scheduling policy.
127    pub scheduler: Scheduler,
128    /// Run budget limits.
129    pub budget: Budget,
130    /// Capabilities required by this graph.
131    pub capabilities: Vec<Symbol>,
132    /// Extra graph metadata.
133    pub metadata: Vec<(Symbol, Expr)>,
134    /// Embedded graph tests.
135    pub tests: Vec<GraphTest>,
136}
137
138impl Graph {
139    /// Creates an empty graph with deterministic defaults.
140    pub fn new(name: Symbol) -> Self {
141        Self {
142            name,
143            version: DEFAULT_GRAPH_VERSION.to_owned(),
144            api: TOPOLOGY_API.to_owned(),
145            input: None,
146            output: None,
147            nodes: Vec::new(),
148            edges: Vec::new(),
149            cells: Vec::new(),
150            scheduler: Scheduler::default(),
151            budget: Budget::default(),
152            capabilities: Vec::new(),
153            metadata: Vec::new(),
154            tests: Vec::new(),
155        }
156    }
157
158    /// Creates a minimal graph from an unqualified symbol name.
159    pub fn minimal(name: impl Into<String>) -> Self {
160        Self::new(Symbol::new(name.into()))
161    }
162
163    /// Compatibility constructor for earlier scaffold tests.
164    pub fn placeholder() -> Self {
165        Self::minimal("topology")
166    }
167}
168
169impl Default for Graph {
170    fn default() -> Self {
171        Self::minimal("topology")
172    }
173}
174
175/// A topology graph step with named input and output ports.
176#[derive(Clone, Debug)]
177pub struct Node {
178    /// Node id unique within the graph.
179    pub id: NodeId,
180    /// Generic node verb, such as `in`, `out`, `call`, or `wire`.
181    pub verb: Symbol,
182    /// Canonical `in` ports.
183    pub inputs: Vec<Port>,
184    /// Canonical `out` ports.
185    pub outputs: Vec<Port>,
186    /// Optional runtime target value expression.
187    pub target: Option<Expr>,
188    /// Optional role tag used by frame-aware targets.
189    pub role: Option<Symbol>,
190    /// Optional node input shape expression.
191    pub input: Option<Expr>,
192    /// Optional node output shape expression.
193    pub output: Option<Expr>,
194    /// Node options preserved from graph data.
195    pub options: Vec<(Symbol, Expr)>,
196}
197
198impl Node {
199    /// Creates a node with default ports for the provided verb.
200    pub fn new(id: impl Into<NodeId>, verb: Symbol) -> Self {
201        let (inputs, outputs) = default_ports_for_verb(&verb);
202        Self {
203            id: id.into(),
204            verb,
205            inputs,
206            outputs,
207            target: None,
208            role: None,
209            input: None,
210            output: None,
211            options: Vec::new(),
212        }
213    }
214
215    /// Creates a node from an unqualified verb name.
216    pub fn named(id: impl Into<NodeId>, verb: impl Into<String>) -> Self {
217        Self::new(id, Symbol::new(verb.into()))
218    }
219
220    /// Creates a node with explicitly declared ports.
221    pub fn with_ports(
222        id: impl Into<NodeId>,
223        verb: Symbol,
224        inputs: Vec<Port>,
225        outputs: Vec<Port>,
226    ) -> Self {
227        Self {
228            id: id.into(),
229            verb,
230            inputs,
231            outputs,
232            target: None,
233            role: None,
234            input: None,
235            output: None,
236            options: Vec::new(),
237        }
238    }
239}
240
241/// A named input or output port on a topology node.
242#[derive(Clone, Debug)]
243pub struct Port {
244    /// Port name.
245    pub name: Symbol,
246    /// Optional port shape expression.
247    pub shape: Option<Expr>,
248    /// Whether packets are values or streams.
249    pub mode: PortMode,
250    /// Whether this port must be connected or provided.
251    pub required: bool,
252}
253
254impl Port {
255    /// Creates a port from an explicit symbol.
256    pub fn new(name: Symbol, mode: PortMode, required: bool) -> Self {
257        Self {
258            name,
259            shape: None,
260            mode,
261            required,
262        }
263    }
264
265    /// Creates a value port from an unqualified symbol name.
266    pub fn value(name: impl Into<String>, required: bool) -> Self {
267        Self::new(Symbol::new(name.into()), PortMode::Value, required)
268    }
269
270    /// Creates a stream port from an unqualified symbol name.
271    pub fn stream(name: impl Into<String>, required: bool) -> Self {
272        Self::new(Symbol::new(name.into()), PortMode::Stream, required)
273    }
274}
275
276/// Port packet mode.
277#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
278pub enum PortMode {
279    /// The port carries one value packet at a time.
280    #[default]
281    Value,
282    /// The port carries stream packets or stream handles.
283    Stream,
284}
285
286/// A directed route from one node port to another node port.
287#[derive(Clone, Debug)]
288pub struct Edge {
289    /// Stable edge id.
290    pub id: EdgeId,
291    /// Source node port. Omitted source ports normalize to `out`.
292    pub from: PortRef,
293    /// Destination node port. Omitted destination ports normalize to `in`.
294    pub to: PortRef,
295    /// Optional edge predicate expression.
296    pub when: Option<Expr>,
297    /// Optional transform expression applied while routing.
298    pub transform: Option<Expr>,
299    /// Optional wrapper key for the routed value.
300    pub as_name: Option<Symbol>,
301    /// Deterministic routing priority.
302    pub priority: i64,
303    /// Optional per-edge visit cap.
304    pub max_visits: Option<u32>,
305    /// Optional buffer policy expression.
306    pub buffer: Option<Expr>,
307    /// Extra edge metadata.
308    pub metadata: Vec<(Symbol, Expr)>,
309}
310
311impl Edge {
312    /// Creates an edge with deterministic defaults.
313    pub fn new(id: impl Into<EdgeId>, from: PortRef, to: PortRef) -> Self {
314        Self {
315            id: id.into(),
316            from,
317            to,
318            when: None,
319            transform: None,
320            as_name: None,
321            priority: 0,
322            max_visits: None,
323            buffer: None,
324            metadata: Vec::new(),
325        }
326    }
327}
328
329/// State cell available to nodes during a topology run.
330#[derive(Clone, Debug)]
331pub struct Cell {
332    /// Cell name.
333    pub name: Symbol,
334    /// Optional cell shape expression.
335    pub shape: Option<Expr>,
336    /// Initial cell value expression.
337    pub initial: Expr,
338    /// Optional merge strategy symbol.
339    pub merge: Option<Symbol>,
340    /// Whether reflection should redact this cell by default.
341    pub private: bool,
342}
343
344impl Cell {
345    /// Creates a public cell with no shape or merge strategy.
346    pub fn new(name: Symbol, initial: Expr) -> Self {
347        Self {
348            name,
349            shape: None,
350            initial,
351            merge: None,
352            private: false,
353        }
354    }
355}
356
357/// Deterministic scheduler settings for a topology graph.
358#[derive(Clone, Debug, PartialEq, Eq)]
359pub struct Scheduler {
360    /// Scheduler mode.
361    pub mode: SchedulerMode,
362    /// Optional deterministic seed.
363    pub seed: Option<u64>,
364    /// Maximum number of concurrent node runs.
365    pub max_concurrency: u32,
366    /// Whether scheduling must preserve deterministic replay.
367    pub deterministic: bool,
368}
369
370impl Default for Scheduler {
371    fn default() -> Self {
372        Self {
373            mode: SchedulerMode::Sequential,
374            seed: None,
375            max_concurrency: 1,
376            deterministic: true,
377        }
378    }
379}
380
381/// Scheduler strategy for graph execution.
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
383pub enum SchedulerMode {
384    /// Runs nodes in deterministic sequential order.
385    #[default]
386    Sequential,
387}
388
389/// Bounded execution budget for one topology run.
390#[derive(Clone, Debug, PartialEq, Eq)]
391pub struct Budget {
392    /// Maximum scheduler steps.
393    pub max_steps: u32,
394    /// Maximum visits to any node.
395    pub max_node_visits: u32,
396    /// Maximum traversals of any edge.
397    pub max_edge_visits: u32,
398    /// Maximum emitted outputs.
399    pub max_outputs: u32,
400    /// Maximum nested child topology runs.
401    pub max_child_runs: u32,
402    /// Optional wall-clock deadline in milliseconds.
403    pub deadline_ms: Option<u64>,
404    /// Exhaustion policy.
405    pub on_exhausted: BudgetExhausted,
406}
407
408impl Default for Budget {
409    fn default() -> Self {
410        Self {
411            max_steps: DEFAULT_MAX_STEPS,
412            max_node_visits: DEFAULT_MAX_NODE_VISITS,
413            max_edge_visits: DEFAULT_MAX_EDGE_VISITS,
414            max_outputs: 64,
415            max_child_runs: 16,
416            deadline_ms: None,
417            on_exhausted: BudgetExhausted::Fail,
418        }
419    }
420}
421
422/// Budget exhaustion behavior.
423#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
424pub enum BudgetExhausted {
425    /// Fails the graph run when a budget is exhausted.
426    #[default]
427    Fail,
428    /// Returns the outputs produced before exhaustion.
429    Partial,
430}
431
432/// Embedded graph-level test case.
433#[derive(Clone, Debug)]
434pub struct GraphTest {
435    /// Test case name.
436    pub name: Symbol,
437    /// Input expression for the graph.
438    pub input: Expr,
439    /// Expected output expression or shape expression.
440    pub expect: Expr,
441    /// Fixture values available while running the test.
442    pub fixtures: Vec<(Symbol, Expr)>,
443}
444
445impl GraphTest {
446    /// Creates a test case without fixtures.
447    pub fn new(name: Symbol, input: Expr, expect: Expr) -> Self {
448        Self {
449            name,
450            input,
451            expect,
452            fixtures: Vec::new(),
453        }
454    }
455}
456
457fn default_ports_for_verb(verb: &Symbol) -> (Vec<Port>, Vec<Port>) {
458    match verb.name.as_ref() {
459        "in" => (Vec::new(), vec![Port::value("out", true)]),
460        "out" => (vec![Port::value("in", true)], Vec::new()),
461        "call" => (
462            vec![Port::value("in", true)],
463            vec![Port::value("out", true), Port::value("error", false)],
464        ),
465        "branch" => (
466            vec![Port::value("in", true)],
467            vec![
468                Port::value("true", false),
469                Port::value("false", false),
470                Port::value("else", false),
471            ],
472        ),
473        "merge" => (
474            vec![Port::value("in", true)],
475            vec![Port::value("out", true)],
476        ),
477        "tee" => (
478            vec![Port::value("in", true)],
479            vec![Port::value("out", true)],
480        ),
481        _ => (
482            vec![Port::value("in", true)],
483            vec![Port::value("out", true)],
484        ),
485    }
486}