1use core::cell::RefCell;
21use core::ops::{Add, Div, Mul, Neg, Sub};
22
23#[derive(Clone, Copy)]
25struct Node {
26 dep: [usize; 2],
27 weight: [f64; 2],
28}
29
30#[derive(Default)]
33pub struct Tape {
34 nodes: RefCell<Vec<Node>>,
35}
36
37impl Tape {
38 pub fn new() -> Self {
40 Tape { nodes: RefCell::new(Vec::new()) }
41 }
42
43 pub fn len(&self) -> usize {
45 self.nodes.borrow().len()
46 }
47
48 pub fn is_empty(&self) -> bool {
50 self.nodes.borrow().is_empty()
51 }
52
53 pub fn var(&self, value: f64) -> Var<'_> {
55 let index = self.push([usize::MAX, usize::MAX], [0.0, 0.0], true);
56 Var { tape: self, index, value }
57 }
58
59 pub fn constant(&self, value: f64) -> Var<'_> {
61 self.var(value)
62 }
63
64 fn push(&self, dep: [usize; 2], weight: [f64; 2], leaf: bool) -> usize {
65 let mut nodes = self.nodes.borrow_mut();
66 let index = nodes.len();
67 let dep = if leaf { [index, index] } else { dep };
69 nodes.push(Node { dep, weight });
70 index
71 }
72}
73
74#[derive(Clone, Copy)]
76pub struct Var<'t> {
77 tape: &'t Tape,
78 index: usize,
79 value: f64,
80}
81
82pub struct Grad(Vec<f64>);
84
85impl Grad {
86 pub fn wrt(&self, v: Var<'_>) -> f64 {
88 self.0[v.index]
89 }
90}
91
92impl<'t> Var<'t> {
93 pub fn value(&self) -> f64 {
95 self.value
96 }
97
98 fn unary(self, value: f64, d: f64) -> Var<'t> {
99 let index = self.tape.push([self.index, self.index], [d, 0.0], false);
100 Var { tape: self.tape, index, value }
101 }
102
103 fn binary(self, rhs: Var<'t>, value: f64, da: f64, db: f64) -> Var<'t> {
104 let index = self.tape.push([self.index, rhs.index], [da, db], false);
105 Var { tape: self.tape, index, value }
106 }
107
108 pub fn backward(&self) -> Grad {
110 let nodes = self.tape.nodes.borrow();
111 let mut grad = vec![0.0; nodes.len()];
112 grad[self.index] = 1.0;
113 for i in (0..nodes.len()).rev() {
114 let g = grad[i];
115 if g != 0.0 {
116 let n = nodes[i];
117 grad[n.dep[0]] += n.weight[0] * g;
118 grad[n.dep[1]] += n.weight[1] * g;
119 }
120 }
121 Grad(grad)
122 }
123
124 pub fn sin(self) -> Var<'t> {
128 self.unary(self.value.sin(), self.value.cos())
129 }
130 pub fn cos(self) -> Var<'t> {
132 self.unary(self.value.cos(), -self.value.sin())
133 }
134 pub fn exp(self) -> Var<'t> {
136 let e = self.value.exp();
137 self.unary(e, e)
138 }
139 pub fn ln(self) -> Var<'t> {
141 self.unary(self.value.ln(), 1.0 / self.value)
142 }
143 pub fn tanh(self) -> Var<'t> {
145 let t = self.value.tanh();
146 self.unary(t, 1.0 - t * t)
147 }
148 pub fn sqrt(self) -> Var<'t> {
150 let s = self.value.sqrt();
151 self.unary(s, 0.5 / s)
152 }
153 pub fn powf(self, n: f64) -> Var<'t> {
155 self.unary(self.value.powf(n), n * self.value.powf(n - 1.0))
156 }
157 pub fn sigmoid(self) -> Var<'t> {
159 let s = 1.0 / (1.0 + (-self.value).exp());
160 self.unary(s, s * (1.0 - s))
161 }
162 pub fn relu(self) -> Var<'t> {
164 let d = if self.value > 0.0 { 1.0 } else { 0.0 };
165 self.unary(self.value.max(0.0), d)
166 }
167}
168
169impl<'t> Add for Var<'t> {
170 type Output = Var<'t>;
171 fn add(self, rhs: Var<'t>) -> Var<'t> {
172 self.binary(rhs, self.value + rhs.value, 1.0, 1.0)
173 }
174}
175impl<'t> Sub for Var<'t> {
176 type Output = Var<'t>;
177 fn sub(self, rhs: Var<'t>) -> Var<'t> {
178 self.binary(rhs, self.value - rhs.value, 1.0, -1.0)
179 }
180}
181impl<'t> Mul for Var<'t> {
182 type Output = Var<'t>;
183 fn mul(self, rhs: Var<'t>) -> Var<'t> {
184 self.binary(rhs, self.value * rhs.value, rhs.value, self.value)
185 }
186}
187impl<'t> Div for Var<'t> {
188 type Output = Var<'t>;
189 fn div(self, rhs: Var<'t>) -> Var<'t> {
190 let v = self.value / rhs.value;
191 self.binary(rhs, v, 1.0 / rhs.value, -self.value / (rhs.value * rhs.value))
192 }
193}
194impl<'t> Neg for Var<'t> {
195 type Output = Var<'t>;
196 fn neg(self) -> Var<'t> {
197 self.unary(-self.value, -1.0)
198 }
199}
200
201impl<'t> Add<f64> for Var<'t> {
203 type Output = Var<'t>;
204 fn add(self, rhs: f64) -> Var<'t> {
205 self.unary(self.value + rhs, 1.0)
206 }
207}
208impl<'t> Mul<f64> for Var<'t> {
209 type Output = Var<'t>;
210 fn mul(self, rhs: f64) -> Var<'t> {
211 self.unary(self.value * rhs, rhs)
212 }
213}
214impl<'t> Sub<f64> for Var<'t> {
215 type Output = Var<'t>;
216 fn sub(self, rhs: f64) -> Var<'t> {
217 self.unary(self.value - rhs, 1.0)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 fn fd_grad(f: impl Fn(&[f64]) -> f64, x: &[f64]) -> Vec<f64> {
227 let h = 1e-6;
228 (0..x.len())
229 .map(|i| {
230 let mut xp = x.to_vec();
231 let mut xm = x.to_vec();
232 xp[i] += h;
233 xm[i] -= h;
234 (f(&xp) - f(&xm)) / (2.0 * h)
235 })
236 .collect()
237 }
238
239 #[test]
240 fn product_and_quotient_rules_are_exact() {
241 let t = Tape::new();
243 let x = t.var(3.0);
244 let y = t.var(4.0);
245 let z = x * y;
246 let g = z.backward();
247 assert!((g.wrt(x) - 4.0).abs() < 1e-12 && (g.wrt(y) - 3.0).abs() < 1e-12);
248
249 let t2 = Tape::new();
250 let a = t2.var(3.0);
251 let b = t2.var(4.0);
252 let q = a / b;
253 let gq = q.backward();
254 assert!((gq.wrt(a) - 0.25).abs() < 1e-12, "∂(a/b)/∂a = 1/b");
255 assert!((gq.wrt(b) - (-3.0 / 16.0)).abs() < 1e-12, "∂(a/b)/∂b = −a/b²");
256 }
257
258 #[test]
259 fn tanh_derivative_matches_one_minus_tanh_squared() {
260 let t = Tape::new();
261 let x = t.var(0.7);
262 let y = x.tanh();
263 let g = y.backward();
264 let expect = 1.0 - 0.7_f64.tanh().powi(2);
265 assert!((g.wrt(x) - expect).abs() < 1e-12, "tanh' = 1 − tanh²");
266 }
267
268 #[test]
269 fn gradient_of_a_nonlinear_multivariable_function_matches_finite_differences() {
270 let f = |v: &[f64]| {
273 let (x, y, z) = (v[0], v[1], v[2]);
274 (x * y).sin() + z.exp() / (1.0 + x * x) - (y + z).tanh()
275 };
276 let x0 = [0.6, -1.3, 0.4];
277 let t = Tape::new();
278 let x = t.var(x0[0]);
279 let y = t.var(x0[1]);
280 let z = t.var(x0[2]);
281 let one = t.constant(1.0);
282 let out = (x * y).sin() + z.exp() / (one + x * x) - (y + z).tanh();
283 let g = out.backward();
284 let fd = fd_grad(f, &x0);
285 for (i, &v) in [g.wrt(x), g.wrt(y), g.wrt(z)].iter().enumerate() {
286 assert!((v - fd[i]).abs() < 1e-6, "grad[{i}]: autodiff {v} vs fd {}", fd[i]);
287 }
288 }
289
290 #[test]
291 fn a_shared_subexpression_accumulates_both_paths() {
292 let t = Tape::new();
295 let x = t.var(5.0);
296 let f = x * x + x;
297 let g = f.backward();
298 assert!((g.wrt(x) - 11.0).abs() < 1e-12, "∂(x²+x)/∂x = 2x+1 = 11");
299 }
300
301 #[test]
302 fn sigmoid_and_relu_gradients_are_correct() {
303 let t = Tape::new();
304 let x = t.var(0.5);
305 let s = x.sigmoid();
306 let gs = s.backward();
307 let sv = 1.0 / (1.0 + (-0.5_f64).exp());
308 assert!((gs.wrt(x) - sv * (1.0 - sv)).abs() < 1e-12, "σ' = σ(1−σ)");
309
310 let t2 = Tape::new();
311 let xp = t2.var(2.0);
312 let xn = t2.var(-2.0);
313 assert!((xp.relu().backward().wrt(xp) - 1.0).abs() < 1e-12, "ReLU'(+) = 1");
314 assert!(xn.relu().backward().wrt(xn).abs() < 1e-12, "ReLU'(−) = 0");
315 }
316}