1use crate::{Module, ModuleBase, Parameter};
7use torsh_core::device::DeviceType;
8use torsh_core::error::{Result, TorshError};
9use torsh_tensor::Tensor;
10
11#[cfg(feature = "std")]
13use std::{boxed::Box, collections::HashMap, vec::Vec};
14
15#[cfg(not(feature = "std"))]
16use alloc::{boxed::Box, vec::Vec};
17
18#[cfg(not(feature = "std"))]
19use hashbrown::HashMap;
20
21use parking_lot::Mutex;
23
24#[derive(Debug, Clone)]
29pub enum GraphNode {
30 Module(String),
32 Conditional {
34 condition: String,
35 true_branch: Box<GraphNode>,
36 false_branch: Option<Box<GraphNode>>,
37 },
38 Sequence(Vec<GraphNode>),
40 Parallel {
42 nodes: Vec<GraphNode>,
43 combiner: String, },
45 Loop {
47 body: Box<GraphNode>,
48 condition: String,
49 max_iterations: usize,
50 },
51 Function(String),
53}
54
55pub struct DynamicGraph {
89 base: ModuleBase,
90 modules: HashMap<String, Box<dyn Module>>,
92 conditions: HashMap<String, Box<dyn Fn(&Tensor) -> bool + Send + Sync>>,
94 combiners: HashMap<String, Box<dyn Fn(Vec<Tensor>) -> Result<Tensor> + Send + Sync>>,
96 functions: HashMap<String, Box<dyn Fn(&Tensor) -> Result<Tensor> + Send + Sync>>,
98 graph: GraphNode,
100 execution_history: Mutex<Vec<String>>,
102}
103
104impl DynamicGraph {
105 pub fn new() -> Self {
107 let mut graph = Self {
108 base: ModuleBase::new(),
109 modules: HashMap::new(),
110 conditions: HashMap::new(),
111 combiners: HashMap::new(),
112 functions: HashMap::new(),
113 graph: GraphNode::Sequence(Vec::new()),
114 execution_history: Mutex::new(Vec::new()),
115 };
116
117 graph.add_combiner(
119 "concat".to_string(),
120 Box::new(|tensors: Vec<Tensor>| {
121 if tensors.is_empty() {
122 return Err(TorshError::InvalidArgument(
123 "No tensors to concatenate".to_string(),
124 ));
125 }
126
127 let ndim = tensors[0].ndim();
129 if ndim == 0 {
130 return Err(TorshError::InvalidArgument(
131 "Cannot concatenate 0-dimensional tensors".to_string(),
132 ));
133 }
134
135 let concat_dim = (ndim - 1) as i32; let tensor_refs: Vec<&Tensor> = tensors.iter().collect();
140 Tensor::cat(&tensor_refs, concat_dim)
141 .map_err(|e| TorshError::Other(format!("Concatenation failed: {}", e)))
142 }),
143 );
144
145 graph.add_combiner(
146 "add".to_string(),
147 Box::new(|tensors: Vec<Tensor>| {
148 if tensors.is_empty() {
149 return Err(TorshError::InvalidArgument("No tensors to add".to_string()));
150 }
151 let mut result = tensors[0].clone();
152 for tensor in tensors.iter().skip(1) {
153 result = result.add_op(tensor)?;
154 }
155 Ok(result)
156 }),
157 );
158
159 graph.add_combiner(
160 "mean".to_string(),
161 Box::new(|tensors: Vec<Tensor>| {
162 if tensors.is_empty() {
163 return Err(TorshError::InvalidArgument(
164 "No tensors to average".to_string(),
165 ));
166 }
167 let mut result = tensors[0].clone();
168 for tensor in tensors.iter().skip(1) {
169 result = result.add_op(tensor)?;
170 }
171 let count = tensors.len() as f32;
172 result = result.div_scalar(count)?;
173 Ok(result)
174 }),
175 );
176
177 graph
178 }
179
180 pub fn add_module<M: Module + 'static>(&mut self, name: String, module: M) {
182 self.modules.insert(name, Box::new(module));
183 }
184
185 pub fn add_condition<F>(&mut self, name: String, condition: F)
187 where
188 F: Fn(&Tensor) -> bool + Send + Sync + 'static,
189 {
190 self.conditions.insert(name, Box::new(condition));
191 }
192
193 pub fn add_combiner<F>(&mut self, name: String, combiner: F)
195 where
196 F: Fn(Vec<Tensor>) -> Result<Tensor> + Send + Sync + 'static,
197 {
198 self.combiners.insert(name, Box::new(combiner));
199 }
200
201 pub fn add_function<F>(&mut self, name: String, function: F)
203 where
204 F: Fn(&Tensor) -> Result<Tensor> + Send + Sync + 'static,
205 {
206 self.functions.insert(name, Box::new(function));
207 }
208
209 pub fn set_graph(&mut self, graph: GraphNode) {
211 self.graph = graph;
212 }
213
214 pub fn sequential(module_names: Vec<String>) -> GraphNode {
216 GraphNode::Sequence(
217 module_names
218 .into_iter()
219 .map(|name| GraphNode::Module(name))
220 .collect(),
221 )
222 }
223
224 pub fn conditional(
226 condition: String,
227 true_branch: GraphNode,
228 false_branch: Option<GraphNode>,
229 ) -> GraphNode {
230 GraphNode::Conditional {
231 condition,
232 true_branch: Box::new(true_branch),
233 false_branch: false_branch.map(Box::new),
234 }
235 }
236
237 pub fn parallel(nodes: Vec<GraphNode>, combiner: String) -> GraphNode {
239 GraphNode::Parallel { nodes, combiner }
240 }
241
242 pub fn loop_graph(body: GraphNode, condition: String, max_iterations: usize) -> GraphNode {
244 GraphNode::Loop {
245 body: Box::new(body),
246 condition,
247 max_iterations,
248 }
249 }
250
251 fn execute_node(&self, node: &GraphNode, input: &Tensor) -> Result<Tensor> {
253 let mut history = self.execution_history.lock();
254
255 match node {
256 GraphNode::Module(name) => {
257 history.push(format!("Module: {}", name));
258 let module = self.modules.get(name).ok_or_else(|| {
259 TorshError::InvalidArgument(format!("Module '{}' not found", name))
260 })?;
261 module.forward(input)
262 }
263
264 GraphNode::Conditional {
265 condition,
266 true_branch,
267 false_branch,
268 } => {
269 history.push(format!("Conditional: {}", condition));
270 let cond_fn = self.conditions.get(condition).ok_or_else(|| {
271 TorshError::InvalidArgument(format!("Condition '{}' not found", condition))
272 })?;
273
274 if cond_fn(input) {
275 history.push("Taking true branch".to_string());
276 self.execute_node(true_branch, input)
277 } else if let Some(false_branch) = false_branch {
278 history.push("Taking false branch".to_string());
279 self.execute_node(false_branch, input)
280 } else {
281 history.push("No false branch, returning input".to_string());
282 Ok(input.clone())
283 }
284 }
285
286 GraphNode::Sequence(nodes) => {
287 history.push("Sequence execution".to_string());
288 let mut output = input.clone();
289 for node in nodes {
290 output = self.execute_node(node, &output)?;
291 }
292 Ok(output)
293 }
294
295 GraphNode::Parallel { nodes, combiner } => {
296 history.push(format!("Parallel execution with combiner: {}", combiner));
297 let mut results = Vec::new();
298 for node in nodes {
299 results.push(self.execute_node(node, input)?);
300 }
301
302 let combiner_fn = self.combiners.get(combiner).ok_or_else(|| {
303 TorshError::InvalidArgument(format!("Combiner '{}' not found", combiner))
304 })?;
305 combiner_fn(results)
306 }
307
308 GraphNode::Loop {
309 body,
310 condition,
311 max_iterations,
312 } => {
313 history.push(format!("Loop execution with condition: {}", condition));
314 let cond_fn = self.conditions.get(condition).ok_or_else(|| {
315 TorshError::InvalidArgument(format!("Condition '{}' not found", condition))
316 })?;
317
318 let mut output = input.clone();
319 let mut iterations = 0;
320
321 while cond_fn(&output) && iterations < *max_iterations {
322 output = self.execute_node(body, &output)?;
323 iterations += 1;
324 history.push(format!("Loop iteration: {}", iterations));
325 }
326
327 Ok(output)
328 }
329
330 GraphNode::Function(name) => {
331 history.push(format!("Function: {}", name));
332 let function = self.functions.get(name).ok_or_else(|| {
333 TorshError::InvalidArgument(format!("Function '{}' not found", name))
334 })?;
335 function(input)
336 }
337 }
338 }
339
340 pub fn get_execution_history(&self) -> Vec<String> {
342 self.execution_history.lock().clone()
343 }
344
345 pub fn clear_execution_history(&self) {
347 self.execution_history.lock().clear();
348 }
349
350 pub fn modify_graph<F>(&mut self, modifier: F)
352 where
353 F: FnOnce(&mut GraphNode),
354 {
355 modifier(&mut self.graph);
356 }
357
358 pub fn get_module(&self, name: &str) -> Option<&dyn Module> {
360 self.modules.get(name).map(|m| m.as_ref())
361 }
362
363 pub fn replace_module<M: Module + 'static>(&mut self, name: String, module: M) {
365 self.modules.insert(name, Box::new(module));
366 }
367
368 pub fn remove_module(&mut self, name: &str) -> Option<Box<dyn Module>> {
370 self.modules.remove(name)
371 }
372
373 pub fn module_count(&self) -> usize {
375 self.modules.len()
376 }
377
378 pub fn module_names(&self) -> Vec<&String> {
380 self.modules.keys().collect()
381 }
382
383 pub fn condition_names(&self) -> Vec<&String> {
385 self.conditions.keys().collect()
386 }
387
388 pub fn combiner_names(&self) -> Vec<&String> {
390 self.combiners.keys().collect()
391 }
392
393 pub fn function_names(&self) -> Vec<&String> {
395 self.functions.keys().collect()
396 }
397}
398
399impl Default for DynamicGraph {
400 fn default() -> Self {
401 Self::new()
402 }
403}
404
405impl Module for DynamicGraph {
406 fn forward(&self, input: &Tensor) -> Result<Tensor> {
407 self.clear_execution_history();
408 self.execute_node(&self.graph, input)
409 }
410
411 fn parameters(&self) -> HashMap<String, Parameter> {
412 let mut params = HashMap::new();
413
414 for (module_name, module) in &self.modules {
415 for (param_name, param) in module.parameters() {
416 params.insert(format!("{}.{}", module_name, param_name), param);
417 }
418 }
419
420 params
421 }
422
423 fn named_parameters(&self) -> HashMap<String, Parameter> {
424 let mut params = HashMap::new();
425
426 for (module_name, module) in &self.modules {
427 for (param_name, param) in module.named_parameters() {
428 params.insert(format!("{}.{}", module_name, param_name), param);
429 }
430 }
431
432 params
433 }
434
435 fn train(&mut self) {
436 self.base.set_training(true);
437 for module in self.modules.values_mut() {
438 module.train();
439 }
440 }
441
442 fn eval(&mut self) {
443 self.base.set_training(false);
444 for module in self.modules.values_mut() {
445 module.eval();
446 }
447 }
448
449 fn training(&self) -> bool {
450 self.base.training()
451 }
452
453 fn set_training(&mut self, training: bool) {
454 self.base.set_training(training);
455 for module in self.modules.values_mut() {
456 module.set_training(training);
457 }
458 }
459
460 fn to_device(&mut self, device: DeviceType) -> Result<()> {
461 self.base.to_device(device)?;
462 for module in self.modules.values_mut() {
463 module.to_device(device)?;
464 }
465 Ok(())
466 }
467
468 fn children(&self) -> Vec<&dyn Module> {
469 self.modules.values().map(|m| m.as_ref()).collect()
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 struct MockModule {
479 base: ModuleBase,
480 _id: i32,
481 }
482
483 impl MockModule {
484 fn new(id: i32) -> Self {
485 Self {
486 base: ModuleBase::new(),
487 _id: id,
488 }
489 }
490 }
491
492 impl Module for MockModule {
493 fn forward(&self, input: &Tensor) -> Result<Tensor> {
494 Ok(input.clone())
496 }
497
498 fn parameters(&self) -> HashMap<String, Parameter> {
499 HashMap::new()
500 }
501
502 fn named_parameters(&self) -> HashMap<String, Parameter> {
503 HashMap::new()
504 }
505
506 fn train(&mut self) {
507 self.base.set_training(true);
508 }
509
510 fn eval(&mut self) {
511 self.base.set_training(false);
512 }
513
514 fn training(&self) -> bool {
515 self.base.training()
516 }
517
518 fn set_training(&mut self, training: bool) {
519 self.base.set_training(training);
520 }
521
522 fn to_device(&mut self, device: DeviceType) -> Result<()> {
523 self.base.to_device(device)
524 }
525 }
526
527 #[test]
528 fn test_dynamic_graph_creation() {
529 let graph = DynamicGraph::new();
530 assert_eq!(graph.module_count(), 0);
531 assert!(graph.module_names().is_empty());
532 assert!(graph.training());
533 }
534
535 #[test]
536 fn test_module_management() {
537 let mut graph = DynamicGraph::new();
538
539 graph.add_module("mock1".to_string(), MockModule::new(1));
540 graph.add_module("mock2".to_string(), MockModule::new(2));
541
542 assert_eq!(graph.module_count(), 2);
543 assert!(graph.get_module("mock1").is_some());
544 assert!(graph.get_module("nonexistent").is_none());
545
546 let removed = graph.remove_module("mock1");
547 assert!(removed.is_some());
548 assert_eq!(graph.module_count(), 1);
549 }
550
551 #[test]
552 fn test_graph_node_creation() {
553 let seq_graph =
555 DynamicGraph::sequential(vec!["module1".to_string(), "module2".to_string()]);
556
557 match seq_graph {
558 GraphNode::Sequence(nodes) => {
559 assert_eq!(nodes.len(), 2);
560 }
561 _ => panic!("Expected Sequence node"),
562 }
563
564 let cond_graph = DynamicGraph::conditional(
566 "test_condition".to_string(),
567 GraphNode::Module("true_module".to_string()),
568 Some(GraphNode::Module("false_module".to_string())),
569 );
570
571 match cond_graph {
572 GraphNode::Conditional { condition, .. } => {
573 assert_eq!(condition, "test_condition");
574 }
575 _ => panic!("Expected Conditional node"),
576 }
577 }
578
579 #[test]
580 fn test_default_combiners() {
581 let graph = DynamicGraph::new();
582
583 let combiners = graph.combiner_names();
585 assert!(combiners.iter().any(|&name| name == "add"));
586 assert!(combiners.iter().any(|&name| name == "mean"));
587 assert!(combiners.iter().any(|&name| name == "concat"));
588 }
589
590 #[test]
591 fn test_execution_history() {
592 let graph = DynamicGraph::new();
593
594 assert!(graph.get_execution_history().is_empty());
595
596 graph.clear_execution_history();
598 assert!(graph.get_execution_history().is_empty());
599 }
600}