1use std::collections::HashMap;
8
9pub type NodeId = u64;
11
12#[derive(Clone, Debug, PartialEq)]
14pub enum AutogradOp {
15 Input,
17 Add { lhs: NodeId, rhs: NodeId },
19 Mul { lhs: NodeId, rhs: NodeId },
21 Neg { input: NodeId },
23 Exp { input: NodeId },
25 Ln { input: NodeId },
27 Pow { base: NodeId, exponent: f64 },
29}
30
31#[derive(Clone, Debug)]
33pub struct AutogradNode {
34 pub id: NodeId,
36 pub op: AutogradOp,
38 pub value: f64,
40 pub grad: f64,
42 pub requires_grad: bool,
44}
45
46pub struct AutogradGraph {
53 pub nodes: HashMap<NodeId, AutogradNode>,
55 next_id: NodeId,
56}
57
58impl AutogradGraph {
59 pub fn new() -> Self {
61 Self {
62 nodes: HashMap::new(),
63 next_id: 0,
64 }
65 }
66
67 fn alloc_id(&mut self) -> NodeId {
72 let id = self.next_id;
73 self.next_id += 1;
74 id
75 }
76
77 fn insert(&mut self, op: AutogradOp, value: f64, requires_grad: bool) -> NodeId {
78 let id = self.alloc_id();
79 self.nodes.insert(
80 id,
81 AutogradNode {
82 id,
83 op,
84 value,
85 grad: 0.0,
86 requires_grad,
87 },
88 );
89 id
90 }
91
92 pub fn input(&mut self, value: f64) -> NodeId {
98 self.insert(AutogradOp::Input, value, true)
99 }
100
101 pub fn constant(&mut self, value: f64) -> NodeId {
104 self.insert(AutogradOp::Input, value, false)
105 }
106
107 pub fn add(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
109 let value = self.nodes[&lhs].value + self.nodes[&rhs].value;
110 self.insert(AutogradOp::Add { lhs, rhs }, value, true)
111 }
112
113 pub fn mul(&mut self, lhs: NodeId, rhs: NodeId) -> NodeId {
115 let value = self.nodes[&lhs].value * self.nodes[&rhs].value;
116 self.insert(AutogradOp::Mul { lhs, rhs }, value, true)
117 }
118
119 pub fn neg(&mut self, input: NodeId) -> NodeId {
121 let value = -self.nodes[&input].value;
122 self.insert(AutogradOp::Neg { input }, value, true)
123 }
124
125 pub fn exp(&mut self, input: NodeId) -> NodeId {
127 let value = self.nodes[&input].value.exp();
128 self.insert(AutogradOp::Exp { input }, value, true)
129 }
130
131 pub fn ln(&mut self, input: NodeId) -> NodeId {
133 let value = self.nodes[&input].value.ln();
134 self.insert(AutogradOp::Ln { input }, value, true)
135 }
136
137 pub fn pow(&mut self, base: NodeId, exponent: f64) -> NodeId {
139 let value = self.nodes[&base].value.powf(exponent);
140 self.insert(AutogradOp::Pow { base, exponent }, value, true)
141 }
142
143 pub fn backward(&mut self, output_id: NodeId) {
153 if let Some(node) = self.nodes.get_mut(&output_id) {
155 node.grad = 1.0;
156 }
157
158 let topo = self.topo_sort(output_id);
160
161 for &nid in topo.iter().rev() {
163 let (op, grad_out, out_val, rg) = {
165 let node = match self.nodes.get(&nid) {
166 Some(n) => n,
167 None => continue,
168 };
169 (node.op.clone(), node.grad, node.value, node.requires_grad)
170 };
171
172 if !rg {
173 continue;
174 }
175
176 match op {
177 AutogradOp::Input => {
178 }
180 AutogradOp::Add { lhs, rhs } => {
181 self.accumulate_grad(lhs, grad_out);
183 self.accumulate_grad(rhs, grad_out);
184 }
185 AutogradOp::Mul { lhs, rhs } => {
186 let lhs_val = self.nodes[&lhs].value;
188 let rhs_val = self.nodes[&rhs].value;
189 self.accumulate_grad(lhs, grad_out * rhs_val);
190 self.accumulate_grad(rhs, grad_out * lhs_val);
191 }
192 AutogradOp::Neg { input } => {
193 self.accumulate_grad(input, -grad_out);
195 }
196 AutogradOp::Exp { input } => {
197 self.accumulate_grad(input, grad_out * out_val);
199 }
200 AutogradOp::Ln { input } => {
201 let input_val = self.nodes[&input].value;
203 self.accumulate_grad(input, grad_out / input_val);
204 }
205 AutogradOp::Pow { base, exponent } => {
206 let base_val = self.nodes[&base].value;
208 let grad = grad_out * exponent * base_val.powf(exponent - 1.0);
209 self.accumulate_grad(base, grad);
210 }
211 }
212 }
213 }
214
215 fn accumulate_grad(&mut self, node_id: NodeId, delta: f64) {
218 if let Some(node) = self.nodes.get_mut(&node_id) {
219 if node.requires_grad {
220 node.grad += delta;
221 }
222 }
223 }
224
225 fn topo_sort(&self, start: NodeId) -> Vec<NodeId> {
228 let mut order: Vec<NodeId> = Vec::new();
229 let mut visited: HashMap<NodeId, bool> = HashMap::new(); let mut stack: Vec<(NodeId, bool)> = vec![(start, false)];
231
232 while let Some((nid, processed)) = stack.pop() {
233 if processed {
234 order.push(nid);
235 continue;
236 }
237 if visited.contains_key(&nid) {
238 continue; }
240 visited.insert(nid, false);
242 stack.push((nid, true)); if let Some(node) = self.nodes.get(&nid) {
246 match &node.op {
247 AutogradOp::Input => {}
248 AutogradOp::Add { lhs, rhs } => {
249 if !visited.contains_key(lhs) {
250 stack.push((*lhs, false));
251 }
252 if !visited.contains_key(rhs) {
253 stack.push((*rhs, false));
254 }
255 }
256 AutogradOp::Mul { lhs, rhs } => {
257 if !visited.contains_key(lhs) {
258 stack.push((*lhs, false));
259 }
260 if !visited.contains_key(rhs) {
261 stack.push((*rhs, false));
262 }
263 }
264 AutogradOp::Neg { input }
265 | AutogradOp::Exp { input }
266 | AutogradOp::Ln { input } => {
267 if !visited.contains_key(input) {
268 stack.push((*input, false));
269 }
270 }
271 AutogradOp::Pow { base, .. } => {
272 if !visited.contains_key(base) {
273 stack.push((*base, false));
274 }
275 }
276 }
277 }
278 }
279 order
280 }
281
282 pub fn grad(&self, node_id: NodeId) -> Option<f64> {
289 self.nodes
290 .get(&node_id)
291 .and_then(|n| if n.requires_grad { Some(n.grad) } else { None })
292 }
293
294 pub fn value(&self, node_id: NodeId) -> Option<f64> {
296 self.nodes.get(&node_id).map(|n| n.value)
297 }
298
299 pub fn zero_grad(&mut self) {
301 for node in self.nodes.values_mut() {
302 node.grad = 0.0;
303 }
304 }
305}
306
307impl Default for AutogradGraph {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313#[cfg(test)]
318mod tests {
319 use super::*;
320
321 const EPS: f64 = 1e-9;
322
323 fn approx_eq(a: f64, b: f64) -> bool {
324 (a - b).abs() < EPS
325 }
326
327 #[test]
332 fn test_input_node_value() {
333 let mut g = AutogradGraph::new();
334 let x = g.input(std::f64::consts::PI);
335 assert!(approx_eq(
336 g.value(x).expect("test: should succeed"),
337 std::f64::consts::PI
338 ));
339 }
340
341 #[test]
342 fn test_add_forward() {
343 let mut g = AutogradGraph::new();
344 let x = g.input(2.0);
345 let y = g.input(3.0);
346 let z = g.add(x, y);
347 assert!(approx_eq(g.value(z).expect("test: should succeed"), 5.0));
348 }
349
350 #[test]
351 fn test_mul_forward() {
352 let mut g = AutogradGraph::new();
353 let x = g.input(4.0);
354 let y = g.input(5.0);
355 let z = g.mul(x, y);
356 assert!(approx_eq(g.value(z).expect("test: should succeed"), 20.0));
357 }
358
359 #[test]
360 fn test_neg_forward() {
361 let mut g = AutogradGraph::new();
362 let x = g.input(7.0);
363 let z = g.neg(x);
364 assert!(approx_eq(g.value(z).expect("test: should succeed"), -7.0));
365 }
366
367 #[test]
368 fn test_exp_forward() {
369 let mut g = AutogradGraph::new();
370 let x = g.input(1.0);
371 let z = g.exp(x);
372 assert!(approx_eq(
373 g.value(z).expect("test: should succeed"),
374 std::f64::consts::E
375 ));
376 }
377
378 #[test]
379 fn test_ln_forward() {
380 let mut g = AutogradGraph::new();
381 let x = g.input(std::f64::consts::E);
382 let z = g.ln(x);
383 assert!(approx_eq(g.value(z).expect("test: should succeed"), 1.0));
384 }
385
386 #[test]
387 fn test_pow_forward() {
388 let mut g = AutogradGraph::new();
389 let x = g.input(3.0);
390 let z = g.pow(x, 3.0);
391 assert!(approx_eq(g.value(z).expect("test: should succeed"), 27.0));
392 }
393
394 #[test]
399 fn test_backward_add_grad_splits() {
400 let mut g = AutogradGraph::new();
402 let x = g.input(2.0);
403 let y = g.input(3.0);
404 let z = g.add(x, y);
405 g.backward(z);
406 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 1.0));
407 assert!(approx_eq(g.grad(y).expect("test: should succeed"), 1.0));
408 }
409
410 #[test]
411 fn test_backward_mul_chain_rule() {
412 let mut g = AutogradGraph::new();
414 let x = g.input(3.0);
415 let y = g.input(4.0);
416 let z = g.mul(x, y);
417 g.backward(z);
418 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 4.0));
419 assert!(approx_eq(g.grad(y).expect("test: should succeed"), 3.0));
420 }
421
422 #[test]
423 fn test_backward_neg_grad() {
424 let mut g = AutogradGraph::new();
426 let x = g.input(5.0);
427 let z = g.neg(x);
428 g.backward(z);
429 assert!(approx_eq(g.grad(x).expect("test: should succeed"), -1.0));
430 }
431
432 #[test]
433 fn test_backward_exp_grad() {
434 let mut g = AutogradGraph::new();
436 let x = g.input(2.0);
437 let z = g.exp(x);
438 g.backward(z);
439 let expected = 2.0_f64.exp();
440 assert!(approx_eq(
441 g.grad(x).expect("test: should succeed"),
442 expected
443 ));
444 }
445
446 #[test]
447 fn test_backward_ln_grad() {
448 let mut g = AutogradGraph::new();
450 let x = g.input(4.0);
451 let z = g.ln(x);
452 g.backward(z);
453 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 0.25));
454 }
455
456 #[test]
457 fn test_backward_pow_grad() {
458 let mut g = AutogradGraph::new();
460 let x = g.input(2.0);
461 let z = g.pow(x, 3.0);
462 g.backward(z);
463 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 12.0));
464 }
465
466 #[test]
471 fn test_chain_rule_x_squared() {
472 let mut g = AutogradGraph::new();
474 let x = g.input(3.0);
475 let z = g.mul(x, x);
476 g.backward(z);
477 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 6.0));
478 }
479
480 #[test]
481 fn test_chain_rule_x_plus_y_times_x() {
482 let mut g = AutogradGraph::new();
486 let x = g.input(2.0);
487 let y = g.input(3.0);
488 let s = g.add(x, y);
489 let z = g.mul(s, x);
490 g.backward(z);
491 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 7.0));
492 assert!(approx_eq(g.grad(y).expect("test: should succeed"), 2.0));
493 }
494
495 #[test]
496 fn test_chain_rule_exp_of_mul() {
497 let mut g = AutogradGraph::new();
501 let x = g.input(1.0);
502 let y = g.input(2.0);
503 let p = g.mul(x, y);
504 let z = g.exp(p);
505 g.backward(z);
506 let expected = 2.0 * 2.0_f64.exp();
507 assert!(approx_eq(
508 g.grad(x).expect("test: should succeed"),
509 expected
510 ));
511 }
512
513 #[test]
514 fn test_chain_rule_ln_of_pow() {
515 let mut g = AutogradGraph::new();
517 let x = g.input(3.0);
518 let p = g.pow(x, 2.0);
519 let z = g.ln(p);
520 g.backward(z);
521 assert!(approx_eq(
522 g.grad(x).expect("test: should succeed"),
523 2.0 / 3.0
524 ));
525 }
526
527 #[test]
528 fn test_chain_rule_neg_of_add() {
529 let mut g = AutogradGraph::new();
531 let x = g.input(1.0);
532 let y = g.input(2.0);
533 let s = g.add(x, y);
534 let z = g.neg(s);
535 g.backward(z);
536 assert!(approx_eq(g.grad(x).expect("test: should succeed"), -1.0));
537 assert!(approx_eq(g.grad(y).expect("test: should succeed"), -1.0));
538 }
539
540 #[test]
545 fn test_constant_node_no_grad() {
546 let mut g = AutogradGraph::new();
547 let c = g.constant(5.0);
548 let x = g.input(3.0);
549 let z = g.mul(x, c);
550 g.backward(z);
551 assert!(g.grad(c).is_none());
553 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 5.0));
555 }
556
557 #[test]
558 fn test_constant_value_accessible() {
559 let mut g = AutogradGraph::new();
560 let c = g.constant(42.0);
561 assert!(approx_eq(g.value(c).expect("test: should succeed"), 42.0));
562 }
563
564 #[test]
569 fn test_zero_grad_resets() {
570 let mut g = AutogradGraph::new();
571 let x = g.input(2.0);
572 let y = g.input(3.0);
573 let z = g.add(x, y);
574 g.backward(z);
575 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 1.0));
576 g.zero_grad();
577 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 0.0));
578 assert!(approx_eq(g.grad(y).expect("test: should succeed"), 0.0));
579 }
580
581 #[test]
582 fn test_zero_grad_then_recompute() {
583 let mut g = AutogradGraph::new();
584 let x = g.input(3.0);
585 let z = g.pow(x, 2.0);
586 g.backward(z);
587 g.zero_grad();
588 g.backward(z);
590 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 6.0));
591 }
592
593 #[test]
598 fn test_grad_none_for_nonexistent_node() {
599 let g = AutogradGraph::new();
600 assert!(g.grad(9999).is_none());
601 }
602
603 #[test]
604 fn test_value_none_for_nonexistent_node() {
605 let g = AutogradGraph::new();
606 assert!(g.value(9999).is_none());
607 }
608
609 #[test]
610 fn test_backward_single_input_node() {
611 let mut g = AutogradGraph::new();
613 let x = g.input(5.0);
614 g.backward(x);
615 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 1.0));
616 }
617
618 #[test]
619 fn test_multiple_uses_accumulate_correctly() {
620 let mut g = AutogradGraph::new();
622 let x = g.input(3.0);
623 let x2 = g.mul(x, x);
624 let z = g.add(x2, x);
625 g.backward(z);
626 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 7.0));
627 }
628
629 #[test]
630 fn test_deep_chain_add() {
631 let mut g = AutogradGraph::new();
633 let x = g.input(1.0);
634 let a = g.add(x, x);
635 let z = g.add(a, x);
636 g.backward(z);
637 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 3.0));
638 }
639
640 #[test]
641 fn test_pow_fractional_exponent() {
642 let mut g = AutogradGraph::new();
644 let x = g.input(4.0);
645 let z = g.pow(x, 0.5);
646 g.backward(z);
647 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 0.25));
648 }
649
650 #[test]
651 fn test_output_grad_is_one() {
652 let mut g = AutogradGraph::new();
654 let x = g.input(2.0);
655 let z = g.exp(x);
656 g.backward(z);
657 assert!(approx_eq(g.nodes[&z].grad, 1.0));
658 }
659
660 #[test]
661 fn test_default_graph_is_empty() {
662 let g = AutogradGraph::default();
663 assert!(g.nodes.is_empty());
664 }
665
666 #[test]
667 fn test_autograd_op_clone_and_eq() {
668 let op1 = AutogradOp::Add { lhs: 0, rhs: 1 };
669 let op2 = op1.clone();
670 assert_eq!(op1, op2);
671
672 let op3 = AutogradOp::Pow {
673 base: 2,
674 exponent: 3.0,
675 };
676 assert_ne!(op1, op3);
677 }
678
679 #[test]
680 fn test_ln_of_exp_identity() {
681 let mut g = AutogradGraph::new();
683 let x = g.input(2.5);
684 let e = g.exp(x);
685 let z = g.ln(e);
686 assert!((g.value(z).expect("test: should succeed") - 2.5).abs() < 1e-9);
688 g.backward(z);
689 assert!(approx_eq(g.grad(x).expect("test: should succeed"), 1.0));
690 }
691}