per_node_output_access/
per_node_output_access.rs

1//! Comprehensive demonstration of per-node and per-branch output access
2//!
3//! This example shows how to:
4//! - Access outputs from specific nodes
5//! - Access outputs from specific branches
6//! - Track execution history through per-node outputs
7//! - Debug data flow by inspecting individual node results
8
9use graph_sp::Graph;
10use std::collections::HashMap;
11
12fn main() {
13    println!("═══════════════════════════════════════════════════════════");
14    println!("  Per-Node and Per-Branch Output Access Demo");
15    println!("  Track execution results at every level");
16    println!("═══════════════════════════════════════════════════════════\n");
17    
18    demo_per_node_access();
19    demo_per_branch_access();
20    demo_variant_per_node_access();
21    demo_execution_history_tracking();
22}
23
24fn demo_per_node_access() {
25    println!("─────────────────────────────────────────────────────────");
26    println!("Demo 1: Per-Node Output Access");
27    println!("─────────────────────────────────────────────────────────\n");
28    
29    let mut graph = Graph::new();
30    
31    // Node 0: Source
32    graph.add(
33        |_: &HashMap<String, String>, _| {
34            let mut result = HashMap::new();
35            result.insert("value".to_string(), "10".to_string());
36            result
37        },
38        Some("Source"),
39        None,
40        Some(vec![("value", "initial_data")])
41    );
42    
43    // Node 1: Double
44    graph.add(
45        |inputs: &HashMap<String, String>, _| {
46            let value = inputs.get("in").unwrap().parse::<i32>().unwrap();
47            let mut result = HashMap::new();
48            result.insert("doubled".to_string(), (value * 2).to_string());
49            result
50        },
51        Some("Double"),
52        Some(vec![("initial_data", "in")]),
53        Some(vec![("doubled", "doubled_data")])
54    );
55    
56    // Node 2: Add Ten
57    graph.add(
58        |inputs: &HashMap<String, String>, _| {
59            let value = inputs.get("in").unwrap().parse::<i32>().unwrap();
60            let mut result = HashMap::new();
61            result.insert("added".to_string(), (value + 10).to_string());
62            result
63        },
64        Some("AddTen"),
65        Some(vec![("doubled_data", "in")]),
66        Some(vec![("added", "final_result")])
67    );
68    
69    // Execute and get detailed results
70    let dag = graph.build();
71    let result = dag.execute_detailed(false, None);
72    
73    println!("🌍 Global Context (all variables):");
74    for (key, value) in &result.context {
75        println!("  {} = {}", key, value);
76    }
77    
78    println!("\n📦 Per-Node Outputs:");
79    println!("\nNode 0 (Source) outputs:");
80    if let Some(outputs) = result.get_node_outputs(0) {
81        for (key, value) in outputs {
82            println!("  {} = {}", key, value);
83        }
84    }
85    
86    println!("\nNode 1 (Double) outputs:");
87    if let Some(outputs) = result.get_node_outputs(1) {
88        for (key, value) in outputs {
89            println!("  {} = {}", key, value);
90        }
91    }
92    
93    println!("\nNode 2 (AddTen) outputs:");
94    if let Some(outputs) = result.get_node_outputs(2) {
95        for (key, value) in outputs {
96            println!("  {} = {}", key, value);
97        }
98    }
99    
100    println!("\n🎯 Accessing specific node outputs:");
101    if let Some(value) = result.get_from_node(0, "initial_data") {
102        println!("  Node 0 'initial_data': {}", value);
103    }
104    if let Some(value) = result.get_from_node(1, "doubled_data") {
105        println!("  Node 1 'doubled_data': {}", value);
106    }
107    if let Some(value) = result.get_from_node(2, "final_result") {
108        println!("  Node 2 'final_result': {}", value);
109    }
110    
111    println!();
112}
113
114fn demo_per_branch_access() {
115    println!("─────────────────────────────────────────────────────────");
116    println!("Demo 2: Per-Branch Output Access");
117    println!("─────────────────────────────────────────────────────────\n");
118    
119    let mut graph = Graph::new();
120    
121    // Main graph: Source node
122    graph.add(
123        |_: &HashMap<String, String>, _| {
124            let mut result = HashMap::new();
125            result.insert("dataset".to_string(), "100".to_string());
126            result
127        },
128        Some("Source"),
129        None,
130        Some(vec![("dataset", "data")])
131    );
132    
133    // Branch A: Statistics
134    let mut branch_a = Graph::new();
135    branch_a.add(
136        |inputs: &HashMap<String, String>, _| {
137            let value = inputs.get("input").unwrap();
138            let mut result = HashMap::new();
139            result.insert("stat_result".to_string(), format!("Mean of {}", value));
140            result
141        },
142        Some("Statistics"),
143        Some(vec![("data", "input")]),
144        Some(vec![("stat_result", "statistics")])
145    );
146    
147    // Branch B: Model Training
148    let mut branch_b = Graph::new();
149    branch_b.add(
150        |inputs: &HashMap<String, String>, _| {
151            let value = inputs.get("input").unwrap();
152            let mut result = HashMap::new();
153            result.insert("model_result".to_string(), format!("Model trained on {}", value));
154            result
155        },
156        Some("ModelTraining"),
157        Some(vec![("data", "input")]),
158        Some(vec![("model_result", "trained_model")])
159    );
160    
161    // Branch C: Visualization
162    let mut branch_c = Graph::new();
163    branch_c.add(
164        |inputs: &HashMap<String, String>, _| {
165            let value = inputs.get("input").unwrap();
166            let mut result = HashMap::new();
167            result.insert("viz_result".to_string(), format!("Plot of {}", value));
168            result
169        },
170        Some("Visualization"),
171        Some(vec![("data", "input")]),
172        Some(vec![("viz_result", "plot")])
173    );
174    
175    let branch_a_id = graph.branch(branch_a);
176    let branch_b_id = graph.branch(branch_b);
177    let branch_c_id = graph.branch(branch_c);
178    
179    // Execute and get detailed results
180    let dag = graph.build();
181    let result = dag.execute_detailed(false, None);
182    
183    println!("🌍 Global Context:");
184    for (key, value) in &result.context {
185        println!("  {} = {}", key, value);
186    }
187    
188    println!("\n🌿 Per-Branch Outputs:");
189    
190    println!("\nBranch {} (Statistics) outputs:", branch_a_id);
191    if let Some(outputs) = result.get_branch_outputs(branch_a_id) {
192        for (key, value) in outputs {
193            println!("  {} = {}", key, value);
194        }
195    }
196    
197    println!("\nBranch {} (Model Training) outputs:", branch_b_id);
198    if let Some(outputs) = result.get_branch_outputs(branch_b_id) {
199        for (key, value) in outputs {
200            println!("  {} = {}", key, value);
201        }
202    }
203    
204    println!("\nBranch {} (Visualization) outputs:", branch_c_id);
205    if let Some(outputs) = result.get_branch_outputs(branch_c_id) {
206        for (key, value) in outputs {
207            println!("  {} = {}", key, value);
208        }
209    }
210    
211    println!("\n🎯 Accessing specific branch outputs:");
212    if let Some(value) = result.get_from_branch(branch_a_id, "statistics") {
213        println!("  Branch {} 'statistics': {}", branch_a_id, value);
214    }
215    if let Some(value) = result.get_from_branch(branch_b_id, "trained_model") {
216        println!("  Branch {} 'trained_model': {}", branch_b_id, value);
217    }
218    if let Some(value) = result.get_from_branch(branch_c_id, "plot") {
219        println!("  Branch {} 'plot': {}", branch_c_id, value);
220    }
221    
222    println!();
223}
224
225fn demo_variant_per_node_access() {
226    println!("─────────────────────────────────────────────────────────");
227    println!("Demo 3: Variant Outputs with Per-Node Tracking");
228    println!("─────────────────────────────────────────────────────────\n");
229    
230    let mut graph = Graph::new();
231    
232    // Source node
233    graph.add(
234        |_: &HashMap<String, String>, _| {
235            let mut result = HashMap::new();
236            result.insert("base_value".to_string(), "10".to_string());
237            result
238        },
239        Some("Source"),
240        None,
241        Some(vec![("base_value", "data")])
242    );
243    
244    // Variant factory for scaling
245    fn make_scaler(factor: f64) -> impl Fn(&HashMap<String, String>, &HashMap<String, String>) -> HashMap<String, String> {
246        move |inputs: &HashMap<String, String>, _| {
247            let value = inputs.get("input_data").unwrap().parse::<f64>().unwrap();
248            let mut result = HashMap::new();
249            result.insert("scaled_value".to_string(), (value * factor).to_string());
250            result
251        }
252    }
253    
254    // Create variants with unique output names to preserve all results
255    graph.variant(
256        make_scaler,
257        vec![2.0, 3.0, 5.0],
258        Some("Scale"),
259        Some(vec![("data", "input_data")]),
260        Some(vec![("scaled_value", "result")])  // Note: will overwrite in global context
261    );
262    
263    let dag = graph.build();
264    let result = dag.execute_detailed(false, None);
265    
266    println!("🌍 Global Context (note: 'result' contains last variant's output):");
267    for (key, value) in &result.context {
268        println!("  {} = {}", key, value);
269    }
270    
271    println!("\n📦 Per-Node Outputs (each variant tracked separately):");
272    
273    // Node 0 is the source
274    // Nodes 1, 2, 3 are the variant nodes (2x, 3x, 5x scalers)
275    for node_id in 1..=3 {
276        println!("\nNode {} (Variant Scaler) outputs:", node_id);
277        if let Some(outputs) = result.get_node_outputs(node_id) {
278            for (key, value) in outputs {
279                println!("  {} = {}", key, value);
280            }
281        }
282    }
283    
284    println!("\n💡 Key Insight:");
285    println!("  - Global context has 'result' = {} (last variant overwrites)", result.get("result").unwrap());
286    println!("  - But per-node outputs preserve ALL variant results:");
287    println!("    Node 1 (2x): result = {}", result.get_from_node(1, "result").unwrap());
288    println!("    Node 2 (3x): result = {}", result.get_from_node(2, "result").unwrap());
289    println!("    Node 3 (5x): result = {}", result.get_from_node(3, "result").unwrap());
290    
291    println!();
292}
293
294fn demo_execution_history_tracking() {
295    println!("─────────────────────────────────────────────────────────");
296    println!("Demo 4: Execution History Tracking");
297    println!("─────────────────────────────────────────────────────────\n");
298    
299    let mut graph = Graph::new();
300    
301    // Multi-stage pipeline
302    graph.add(
303        |_: &HashMap<String, String>, _| {
304            let mut result = HashMap::new();
305            result.insert("raw".to_string(), "5".to_string());
306            result
307        },
308        Some("Load"),
309        None,
310        Some(vec![("raw", "input")])
311    );
312    
313    graph.add(
314        |inputs: &HashMap<String, String>, _| {
315            let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
316            let mut result = HashMap::new();
317            result.insert("cleaned".to_string(), (value + 1).to_string());
318            result
319        },
320        Some("Clean"),
321        Some(vec![("input", "x")]),
322        Some(vec![("cleaned", "clean_data")])
323    );
324    
325    graph.add(
326        |inputs: &HashMap<String, String>, _| {
327            let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
328            let mut result = HashMap::new();
329            result.insert("normalized".to_string(), (value * 10).to_string());
330            result
331        },
332        Some("Normalize"),
333        Some(vec![("clean_data", "x")]),
334        Some(vec![("normalized", "norm_data")])
335    );
336    
337    graph.add(
338        |inputs: &HashMap<String, String>, _| {
339            let value = inputs.get("x").unwrap().parse::<i32>().unwrap();
340            let mut result = HashMap::new();
341            result.insert("transformed".to_string(), format!("FINAL_{}", value));
342            result
343        },
344        Some("Transform"),
345        Some(vec![("norm_data", "x")]),
346        Some(vec![("transformed", "output")])
347    );
348    
349    let dag = graph.build();
350    let result = dag.execute_detailed(false, None);
351    
352    println!("📊 Execution History (Data Flow Tracking):");
353    println!();
354    println!("Step-by-step transformation:");
355    println!("  1. Load:      input = {}", result.get_from_node(0, "input").unwrap());
356    println!("  2. Clean:     clean_data = {}", result.get_from_node(1, "clean_data").unwrap());
357    println!("  3. Normalize: norm_data = {}", result.get_from_node(2, "norm_data").unwrap());
358    println!("  4. Transform: output = {}", result.get_from_node(3, "output").unwrap());
359    
360    println!("\n🔍 Debugging: Inspect any intermediate result:");
361    println!("  Need to debug the normalization step?");
362    println!("  Just check Node 2: {}", result.get_from_node(2, "norm_data").unwrap());
363    
364    println!("\n✅ Benefits of Per-Node Access:");
365    println!("  ✓ Track data transformations step-by-step");
366    println!("  ✓ Debug issues by inspecting intermediate values");
367    println!("  ✓ Validate each processing stage independently");
368    println!("  ✓ Preserve all variant outputs even with name collisions");
369    
370    println!();
371}