tuple_api_demo/
tuple_api_demo.rs

1//! Demonstration of the tuple-based API for variable mapping
2//!
3//! This example shows how to use the new tuple-based API where:
4//! - Inputs: (broadcast_var, impl_var) - context variable mapped to function parameter name
5//! - Outputs: (impl_var, broadcast_var) - function return value mapped to context variable
6//! - Merge inputs: (branch_id, broadcast_var, impl_var) - branch-specific variable resolution
7
8use graph_sp::Graph;
9use std::collections::HashMap;
10
11fn main() {
12    println!("=== Tuple-Based API Demo ===\n");
13
14    let mut graph = Graph::new();
15
16    // Source node - no inputs, produces "dataset" in context
17    fn data_source(_inputs: &HashMap<String, String>, _variant: &HashMap<String, String>) -> HashMap<String, String> {
18        let mut outputs = HashMap::new();
19        outputs.insert("raw".to_string(), "Sample Data".to_string());
20        outputs
21    }
22
23    graph.add(
24        data_source,
25        Some("Source"),
26        None,  // No inputs
27        Some(vec![("raw", "dataset")])  // Function returns "raw", stored as "dataset" in context
28    );
29
30    println!("✓ Added source node");
31    println!("  Output mapping: function's 'raw' → context's 'dataset'\n");
32
33    // Process node - consumes "dataset" from context as "input_data", produces "processed_data" to context as "result"
34    fn processor(inputs: &HashMap<String, String>, _variant: &HashMap<String, String>) -> HashMap<String, String> {
35        let default = String::new();
36        let data = inputs.get("input_data").unwrap_or(&default);
37        let mut outputs = HashMap::new();
38        outputs.insert("processed_data".to_string(), format!("Processed: {}", data));
39        outputs
40    }
41
42    graph.add(
43        processor,
44        Some("Process"),
45        Some(vec![("dataset", "input_data")]),      // Context's "dataset" → function's "input_data"
46        Some(vec![("processed_data", "result")])    // Function's "processed_data" → context's "result"
47    );
48
49    println!("✓ Added processor node");
50    println!("  Input mapping: context's 'dataset' → function's 'input_data'");
51    println!("  Output mapping: function's 'processed_data' → context's 'result'\n");
52
53    // Create branches that both use the same variable names internally
54    let mut branch_a = Graph::new();
55    fn transform_a(inputs: &HashMap<String, String>, _variant: &HashMap<String, String>) -> HashMap<String, String> {
56        let default = String::new();
57        let data = inputs.get("x").unwrap_or(&default);
58        let mut outputs = HashMap::new();
59        outputs.insert("y".to_string(), format!("{} [Path A]", data));
60        outputs
61    }
62    branch_a.add(
63        transform_a,
64        Some("Transform A"),
65        Some(vec![("result", "x")]),  // Context's "result" → function's "x"
66        Some(vec![("y", "output")])    // Function's "y" → context's "output"
67    );
68
69    let mut branch_b = Graph::new();
70    fn transform_b(inputs: &HashMap<String, String>, _variant: &HashMap<String, String>) -> HashMap<String, String> {
71        let default = String::new();
72        let data = inputs.get("x").unwrap_or(&default);
73        let mut outputs = HashMap::new();
74        outputs.insert("y".to_string(), format!("{} [Path B]", data));
75        outputs
76    }
77    branch_b.add(
78        transform_b,
79        Some("Transform B"),
80        Some(vec![("result", "x")]),  // Same variable names as branch_a
81        Some(vec![("y", "output")])    // Same variable names as branch_a
82    );
83
84    println!("✓ Created two branches");
85    println!("  Both branches use same internal variable names (x, y)");
86    println!("  Both map 'result' → 'x' and 'y' → 'output'\n");
87
88    // Branch from the processor
89    let branch_a_id = graph.branch(branch_a);
90    let branch_b_id = graph.branch(branch_b);
91
92    println!("✓ Added branches to graph");
93    println!("  Branch A ID: {}", branch_a_id);
94    println!("  Branch B ID: {}\n", branch_b_id);
95
96    // Merge branches with branch-specific variable resolution
97    fn combine(inputs: &HashMap<String, String>, _variant: &HashMap<String, String>) -> HashMap<String, String> {
98        let default = String::new();
99        let a = inputs.get("from_a").unwrap_or(&default);
100        let b = inputs.get("from_b").unwrap_or(&default);
101        let mut outputs = HashMap::new();
102        outputs.insert("merged".to_string(), format!("Combined: {} + {}", a, b));
103        outputs
104    }
105
106    graph.merge(
107        combine,
108        Some("Combine"),
109        vec![
110            (branch_a_id, "output", "from_a"),  // Branch A's "output" → merge function's "from_a"
111            (branch_b_id, "output", "from_b")   // Branch B's "output" → merge function's "from_b"
112        ],
113        Some(vec![("merged", "final_result")])  // Merge function's "merged" → context's "final_result"
114    );
115
116    println!("✓ Added merge node");
117    println!("  Branch-specific input mapping:");
118    println!("    Branch {} 'output' → merge function's 'from_a'", branch_a_id);
119    println!("    Branch {} 'output' → merge function's 'from_b'", branch_b_id);
120    println!("  Output mapping: merge function's 'merged' → context's 'final_result'\n");
121
122    // Variant example with factory pattern
123    fn make_multiplier(factor: f64) -> impl Fn(&HashMap<String, String>, &HashMap<String, String>) -> HashMap<String, String> {
124        move |inputs, _variant| {
125            let default = "1.0".to_string();
126            let data = inputs.get("value").unwrap_or(&default);
127            if let Ok(val) = data.parse::<f64>() {
128                let mut outputs = HashMap::new();
129                outputs.insert("scaled".to_string(), (val * factor).to_string());
130                outputs
131            } else {
132                HashMap::new()
133            }
134        }
135    }
136
137    graph.variant(
138        make_multiplier,
139        vec![2.0, 3.0, 5.0],
140        Some("Multiply"),
141        Some(vec![("final_result", "value")]),  // Context's "final_result" → function's "value"
142        Some(vec![("scaled", "multiplied")])    // Function's "scaled" → context's "multiplied"
143    );
144
145    println!("✓ Added variant nodes with parameter sweep");
146    println!("  Three variants: factor = 2.0, 3.0, 5.0");
147    println!("  Input mapping: context's 'final_result' → function's 'value'");
148    println!("  Output mapping: function's 'scaled' → context's 'multiplied'\n");
149
150    println!("=== Summary ===");
151    println!("The tuple-based API provides clear separation between:");
152    println!("1. Context variable names (broadcast vars) - shared across the graph");
153    println!("2. Function parameter names (impl vars) - internal to each function");
154    println!("\nThis allows:");
155    println!("- Branches to use consistent internal naming (x, y)");
156    println!("- Merge to distinguish branch outputs using branch IDs");
157    println!("- Clear data flow visualization and debugging");
158}