pub struct Graph { /* private fields */ }Expand description
The static topology of a flow-based network.
A Graph defines the structure of a network: which nodes exist, how they’re connected, and what conditions govern packet flow. The graph is immutable after creation.
§Example
use netrun_sim::graph::{Graph, Node, Edge, PortRef, PortType, Port, PortSlotSpec};
use indexmap::IndexMap;
use std::collections::HashMap;
// Create a simple A -> B graph
let node_a = Node {
name: "A".to_string(),
in_ports: HashMap::new(),
out_ports: [("out".to_string(), Port { slots_spec: PortSlotSpec::Infinite })].into(),
in_salvo_conditions: IndexMap::new(),
out_salvo_conditions: IndexMap::new(),
dependency_request_config: None,
};
let node_b = Node {
name: "B".to_string(),
in_ports: [("in".to_string(), Port { slots_spec: PortSlotSpec::Infinite })].into(),
out_ports: HashMap::new(),
in_salvo_conditions: IndexMap::new(),
out_salvo_conditions: IndexMap::new(),
dependency_request_config: None,
};
let edge = Edge {
source: PortRef { node_name: "A".to_string(), port_type: PortType::Output, port_name: "out".to_string() },
target: PortRef { node_name: "B".to_string(), port_type: PortType::Input, port_name: "in".to_string() },
};
let graph = Graph::new(vec![node_a, node_b], vec![edge]);
assert!(graph.validate().is_empty());Implementations§
Source§impl Graph
impl Graph
Sourcepub fn new(nodes: Vec<Node>, edges: Vec<Edge>) -> Self
pub fn new(nodes: Vec<Node>, edges: Vec<Edge>) -> Self
Creates a new Graph from a list of nodes and edges.
Builds internal indexes for efficient edge lookups by source (tail) and target (head) ports.
Examples found in repository?
122fn create_linear_graph() -> Graph {
123 let nodes = vec![
124 create_node("A", vec![], vec!["out"]),
125 create_node("B", vec!["in"], vec!["out"]),
126 create_node("C", vec!["in"], vec![]),
127 ];
128
129 let edges = vec![
130 create_edge("A", "out", "B", "in"),
131 create_edge("B", "out", "C", "in"),
132 ];
133
134 let graph = Graph::new(nodes, edges);
135 assert!(graph.validate().is_empty(), "Graph validation failed");
136 graph
137}More examples
122fn create_diamond_graph() -> Graph {
123 // Node A: source with two outputs
124 let node_a = Node {
125 name: "A".to_string(),
126 in_ports: HashMap::new(),
127 out_ports: [
128 (
129 "out1".to_string(),
130 Port {
131 slots_spec: PortSlotSpec::Infinite,
132 },
133 ),
134 (
135 "out2".to_string(),
136 Port {
137 slots_spec: PortSlotSpec::Infinite,
138 },
139 ),
140 ]
141 .into(),
142 in_salvo_conditions: IndexMap::new(),
143 out_salvo_conditions: IndexMap::new(),
144 dependency_request_config: None,
145 };
146
147 // Node B: one input, one output
148 let node_b = create_simple_node("B");
149
150 // Node C: one input, one output
151 let node_c = create_simple_node("C");
152
153 // Node D: TWO inputs (requires both), no outputs
154 let node_d = Node {
155 name: "D".to_string(),
156 in_ports: [
157 (
158 "in1".to_string(),
159 Port {
160 slots_spec: PortSlotSpec::Infinite,
161 },
162 ),
163 (
164 "in2".to_string(),
165 Port {
166 slots_spec: PortSlotSpec::Infinite,
167 },
168 ),
169 ]
170 .into(),
171 out_ports: HashMap::new(),
172 in_salvo_conditions: IndexMap::from([(
173 "default".to_string(),
174 SalvoCondition {
175 max_salvos: MaxSalvos::Finite(1),
176 ports: [
177 ("in1".to_string(), PacketCount::All),
178 ("in2".to_string(), PacketCount::All),
179 ]
180 .into_iter()
181 .collect(),
182 // Require BOTH inputs to be non-empty
183 term: SalvoConditionTerm::And(vec![
184 SalvoConditionTerm::Port {
185 port_name: "in1".to_string(),
186 state: PortState::NonEmpty,
187 },
188 SalvoConditionTerm::Port {
189 port_name: "in2".to_string(),
190 state: PortState::NonEmpty,
191 },
192 ]),
193 },
194 )]),
195 out_salvo_conditions: IndexMap::new(),
196 dependency_request_config: None,
197 };
198
199 let edges = vec![
200 create_edge("A", "out1", "B", "in"),
201 create_edge("A", "out2", "C", "in"),
202 create_edge("B", "out", "D", "in1"),
203 create_edge("C", "out", "D", "in2"),
204 ];
205
206 let graph = Graph::new(vec![node_a, node_b, node_c, node_d], edges);
207 assert!(graph.validate().is_empty(), "Graph validation failed");
208 graph
209}Sourcepub fn with_dependency_edges(self, dependency_edges: Vec<Edge>) -> Self
pub fn with_dependency_edges(self, dependency_edges: Vec<Edge>) -> Self
Set which edges are dependency edges. Each must be in the graph’s edge set. Panics if any dependency edge is not in the graph.
Sourcepub fn is_dependency_edge(&self, edge: &Edge) -> bool
pub fn is_dependency_edge(&self, edge: &Edge) -> bool
Check if an edge is a dependency edge.
Sourcepub fn dependency_edges(&self) -> &HashSet<Edge>
pub fn dependency_edges(&self) -> &HashSet<Edge>
Returns a reference to all dependency edges.
Sourcepub fn nodes(&self) -> &HashMap<NodeName, Node>
pub fn nodes(&self) -> &HashMap<NodeName, Node>
Returns a reference to all nodes in the graph, keyed by name.
Examples found in repository?
20fn main() {
21 // Create a linear graph: A -> B -> C
22 let graph = create_linear_graph();
23 println!("Created graph with {} nodes", graph.nodes().len());
24
25 // Create a network from the graph
26 let mut net = NetSim::new(graph);
27
28 // Create a packet outside the network
29 let packet_id = match net.do_action(&NetAction::CreatePacket(None)) {
30 NetActionResponse::Success(NetActionResponseData::Packet(id), _) => {
31 println!("Created packet: {}", id);
32 id
33 }
34 _ => panic!("Failed to create packet"),
35 };
36
37 // Transport packet to the edge A -> B
38 let edge_a_b = PacketLocation::Edge(Edge {
39 source: PortRef {
40 node_name: "A".to_string(),
41 port_type: PortType::Output,
42 port_name: "out".to_string(),
43 },
44 target: PortRef {
45 node_name: "B".to_string(),
46 port_type: PortType::Input,
47 port_name: "in".to_string(),
48 },
49 });
50 net.do_action(&NetAction::TransportPacketToLocation(
51 packet_id.clone(),
52 edge_a_b,
53 ));
54 println!("Placed packet on edge A -> B");
55
56 // Run the network - packet moves to B's input port and triggers an epoch
57 net.run_until_blocked();
58 println!("Ran network until blocked");
59
60 // Check for startable epochs
61 let startable = net.get_startable_epochs();
62 println!("Startable epochs: {}", startable.len());
63
64 if let Some(epoch_id) = startable.first() {
65 // Start the epoch
66 match net.do_action(&NetAction::StartEpoch(epoch_id.clone())) {
67 NetActionResponse::Success(NetActionResponseData::StartedEpoch(epoch), _) => {
68 println!("Started epoch {} on node {}", epoch.id, epoch.node_name);
69
70 // In a real scenario, external code would process the packet here
71 // For this example, we'll just consume it and create an output
72
73 // Consume the input packet
74 net.do_action(&NetAction::ConsumePacket(packet_id));
75 println!("Consumed input packet");
76
77 // Create an output packet
78 let output_packet =
79 match net.do_action(&NetAction::CreatePacket(Some(epoch.id.clone()))) {
80 NetActionResponse::Success(NetActionResponseData::Packet(id), _) => id,
81 _ => panic!("Failed to create output packet"),
82 };
83 println!("Created output packet: {}", output_packet);
84
85 // Load it into the output port
86 net.do_action(&NetAction::LoadPacketIntoOutputPort(
87 output_packet.clone(),
88 "out".to_string(),
89 ));
90 println!("Loaded packet into output port");
91
92 // Send the output salvo
93 net.do_action(&NetAction::SendOutputSalvo(
94 epoch.id.clone(),
95 "default".to_string(),
96 ));
97 println!("Sent output salvo - packet is now on edge B -> C");
98
99 // Finish the epoch
100 net.do_action(&NetAction::FinishEpoch(epoch.id));
101 println!("Finished epoch");
102
103 // Run the network again - packet moves to C
104 net.run_until_blocked();
105 println!("Ran network until blocked again");
106
107 // Check for new startable epochs at C
108 let startable_c = net.get_startable_epochs();
109 println!(
110 "New startable epochs (should be at C): {}",
111 startable_c.len()
112 );
113 }
114 _ => panic!("Failed to start epoch"),
115 }
116 }
117
118 println!("\nLinear flow example complete!");
119}Sourcepub fn get_edge_by_tail(&self, output_port_ref: &PortRef) -> Option<&Edge>
pub fn get_edge_by_tail(&self, output_port_ref: &PortRef) -> Option<&Edge>
Returns the edge that has the given output port as its source (tail).
Sourcepub fn get_edges_by_head(&self, input_port_ref: &PortRef) -> &[Edge]
pub fn get_edges_by_head(&self, input_port_ref: &PortRef) -> &[Edge]
Returns all edges that have the given input port as their target (head). Fan-in is allowed, so multiple edges can connect to the same input port.
Sourcepub fn validate(&self) -> Vec<GraphValidationError>
pub fn validate(&self) -> Vec<GraphValidationError>
Validates the graph structure.
Returns a list of all validation errors found. An empty list means the graph is valid.
Examples found in repository?
122fn create_linear_graph() -> Graph {
123 let nodes = vec![
124 create_node("A", vec![], vec!["out"]),
125 create_node("B", vec!["in"], vec!["out"]),
126 create_node("C", vec!["in"], vec![]),
127 ];
128
129 let edges = vec![
130 create_edge("A", "out", "B", "in"),
131 create_edge("B", "out", "C", "in"),
132 ];
133
134 let graph = Graph::new(nodes, edges);
135 assert!(graph.validate().is_empty(), "Graph validation failed");
136 graph
137}More examples
122fn create_diamond_graph() -> Graph {
123 // Node A: source with two outputs
124 let node_a = Node {
125 name: "A".to_string(),
126 in_ports: HashMap::new(),
127 out_ports: [
128 (
129 "out1".to_string(),
130 Port {
131 slots_spec: PortSlotSpec::Infinite,
132 },
133 ),
134 (
135 "out2".to_string(),
136 Port {
137 slots_spec: PortSlotSpec::Infinite,
138 },
139 ),
140 ]
141 .into(),
142 in_salvo_conditions: IndexMap::new(),
143 out_salvo_conditions: IndexMap::new(),
144 dependency_request_config: None,
145 };
146
147 // Node B: one input, one output
148 let node_b = create_simple_node("B");
149
150 // Node C: one input, one output
151 let node_c = create_simple_node("C");
152
153 // Node D: TWO inputs (requires both), no outputs
154 let node_d = Node {
155 name: "D".to_string(),
156 in_ports: [
157 (
158 "in1".to_string(),
159 Port {
160 slots_spec: PortSlotSpec::Infinite,
161 },
162 ),
163 (
164 "in2".to_string(),
165 Port {
166 slots_spec: PortSlotSpec::Infinite,
167 },
168 ),
169 ]
170 .into(),
171 out_ports: HashMap::new(),
172 in_salvo_conditions: IndexMap::from([(
173 "default".to_string(),
174 SalvoCondition {
175 max_salvos: MaxSalvos::Finite(1),
176 ports: [
177 ("in1".to_string(), PacketCount::All),
178 ("in2".to_string(), PacketCount::All),
179 ]
180 .into_iter()
181 .collect(),
182 // Require BOTH inputs to be non-empty
183 term: SalvoConditionTerm::And(vec![
184 SalvoConditionTerm::Port {
185 port_name: "in1".to_string(),
186 state: PortState::NonEmpty,
187 },
188 SalvoConditionTerm::Port {
189 port_name: "in2".to_string(),
190 state: PortState::NonEmpty,
191 },
192 ]),
193 },
194 )]),
195 out_salvo_conditions: IndexMap::new(),
196 dependency_request_config: None,
197 };
198
199 let edges = vec![
200 create_edge("A", "out1", "B", "in"),
201 create_edge("A", "out2", "C", "in"),
202 create_edge("B", "out", "D", "in1"),
203 create_edge("C", "out", "D", "in2"),
204 ];
205
206 let graph = Graph::new(vec![node_a, node_b, node_c, node_d], edges);
207 assert!(graph.validate().is_empty(), "Graph validation failed");
208 graph
209}Source§impl Graph
impl Graph
Sourcepub fn cascade_backward(
&self,
start_ports: &[PortRef],
) -> Result<CascadeResult, CascadeError>
pub fn cascade_backward( &self, start_ports: &[PortRef], ) -> Result<CascadeResult, CascadeError>
Backward BFS from a set of input ports, following all edges upstream.
Returns source nodes (nodes with no incoming edges on any input port). Errors on cycles or unconnected input ports.