1use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
7
8use tang::Scalar;
9
10use crate::node::ExprId;
11use crate::with_graph;
12
13impl Add for ExprId {
16 type Output = Self;
17 #[inline]
18 fn add(self, rhs: Self) -> Self {
19 with_graph(|g| g.add(self, rhs))
20 }
21}
22
23impl Sub for ExprId {
24 type Output = Self;
25 #[inline]
26 fn sub(self, rhs: Self) -> Self {
27 let nb = with_graph(|g| g.neg(rhs));
29 with_graph(|g| g.add(self, nb))
30 }
31}
32
33impl Mul for ExprId {
34 type Output = Self;
35 #[inline]
36 fn mul(self, rhs: Self) -> Self {
37 with_graph(|g| g.mul(self, rhs))
38 }
39}
40
41impl Div for ExprId {
42 type Output = Self;
43 #[inline]
44 fn div(self, rhs: Self) -> Self {
45 let rb = with_graph(|g| g.recip(rhs));
47 with_graph(|g| g.mul(self, rb))
48 }
49}
50
51impl Neg for ExprId {
52 type Output = Self;
53 #[inline]
54 fn neg(self) -> Self {
55 with_graph(|g| g.neg(self))
56 }
57}
58
59impl AddAssign for ExprId {
60 #[inline]
61 fn add_assign(&mut self, rhs: Self) {
62 *self = *self + rhs;
63 }
64}
65
66impl SubAssign for ExprId {
67 #[inline]
68 fn sub_assign(&mut self, rhs: Self) {
69 *self = *self - rhs;
70 }
71}
72
73impl MulAssign for ExprId {
74 #[inline]
75 fn mul_assign(&mut self, rhs: Self) {
76 *self = *self * rhs;
77 }
78}
79
80impl DivAssign for ExprId {
81 #[inline]
82 fn div_assign(&mut self, rhs: Self) {
83 *self = *self / rhs;
84 }
85}
86
87impl Scalar for ExprId {
90 const ZERO: Self = ExprId::ZERO;
91 const ONE: Self = ExprId::ONE;
92 const TWO: Self = ExprId::TWO;
93 const HALF: Self = ExprId(u32::MAX - 1);
96 const PI: Self = ExprId(u32::MAX - 2);
97 const TAU: Self = ExprId(u32::MAX - 3);
98 const FRAC_PI_2: Self = ExprId(u32::MAX - 4);
99 const EPSILON: Self = ExprId(u32::MAX - 5);
100 const INFINITY: Self = ExprId(u32::MAX - 6);
101 const NEG_INFINITY: Self = ExprId(u32::MAX - 7);
102
103 #[inline]
104 fn sqrt(self) -> Self {
105 with_graph(|g| g.sqrt(self))
106 }
107
108 #[inline]
109 fn abs(self) -> Self {
110 let neg = with_graph(|g| g.neg(self));
114 with_graph(|g| g.select(self, self, neg))
115 }
116
117 #[inline]
118 fn sin(self) -> Self {
119 with_graph(|g| g.sin(self))
120 }
121
122 #[inline]
123 fn cos(self) -> Self {
124 let half_pi = Self::from_f64(std::f64::consts::FRAC_PI_2);
126 let shifted = with_graph(|g| g.add(self, half_pi));
127 with_graph(|g| g.sin(shifted))
128 }
129
130 #[inline]
131 fn tan(self) -> Self {
132 let s = self.sin();
134 let c = self.cos();
135 let rc = with_graph(|g| g.recip(c));
136 with_graph(|g| g.mul(s, rc))
137 }
138
139 #[inline]
140 fn asin(self) -> Self {
141 let one = ExprId::ONE;
143 let xx = with_graph(|g| g.mul(self, self));
144 let diff = with_graph(|g| {
145 let neg_xx = g.neg(xx);
146 g.add(one, neg_xx)
147 });
148 let sq = with_graph(|g| g.sqrt(diff));
149 with_graph(|g| g.atan2(self, sq))
150 }
151
152 #[inline]
153 fn acos(self) -> Self {
154 let one = ExprId::ONE;
156 let xx = with_graph(|g| g.mul(self, self));
157 let diff = with_graph(|g| {
158 let neg_xx = g.neg(xx);
159 g.add(one, neg_xx)
160 });
161 let sq = with_graph(|g| g.sqrt(diff));
162 with_graph(|g| g.atan2(sq, self))
163 }
164
165 #[inline]
166 fn atan2(self, other: Self) -> Self {
167 with_graph(|g| g.atan2(self, other))
168 }
169
170 #[inline]
171 fn sin_cos(self) -> (Self, Self) {
172 (self.sin(), self.cos())
173 }
174
175 #[inline]
176 fn min(self, other: Self) -> Self {
177 let diff = self - other;
181 with_graph(|g| g.select(diff, other, self))
182 }
183
184 #[inline]
185 fn max(self, other: Self) -> Self {
186 let diff = self - other;
188 with_graph(|g| g.select(diff, self, other))
189 }
190
191 #[inline]
192 fn clamp(self, lo: Self, hi: Self) -> Self {
193 self.max(lo).min(hi)
194 }
195
196 #[inline]
197 fn recip(self) -> Self {
198 with_graph(|g| g.recip(self))
199 }
200
201 #[inline]
202 fn powi(self, n: i32) -> Self {
203 match n {
204 0 => ExprId::ONE,
205 1 => self,
206 2 => with_graph(|g| g.mul(self, self)),
207 3 => {
208 let sq = with_graph(|g| g.mul(self, self));
209 with_graph(|g| g.mul(sq, self))
210 }
211 4 => {
212 let sq = with_graph(|g| g.mul(self, self));
213 with_graph(|g| g.mul(sq, sq))
214 }
215 -1 => with_graph(|g| g.recip(self)),
216 -2 => {
217 let sq = with_graph(|g| g.mul(self, self));
218 with_graph(|g| g.recip(sq))
219 }
220 _ => self.powf(Self::from_f64(n as f64)),
221 }
222 }
223
224 #[inline]
225 fn copysign(self, sign: Self) -> Self {
226 let ax = self.abs();
228 let ss = sign.signum();
229 with_graph(|g| g.mul(ax, ss))
230 }
231
232 #[inline]
233 fn signum(self) -> Self {
234 let xx = with_graph(|g| g.mul(self, self));
236 let abs_x = with_graph(|g| g.sqrt(xx));
237 let r = with_graph(|g| g.recip(abs_x));
238 with_graph(|g| g.mul(self, r))
239 }
240
241 #[inline]
242 fn floor(self) -> Self {
243 with_graph(|g| {
245 if let Some(v) = g.node(self).as_f64() {
246 g.lit(v.floor())
247 } else {
248 panic!("floor() requires a literal expression")
249 }
250 })
251 }
252
253 #[inline]
254 fn ceil(self) -> Self {
255 with_graph(|g| {
256 if let Some(v) = g.node(self).as_f64() {
257 g.lit(v.ceil())
258 } else {
259 panic!("ceil() requires a literal expression")
260 }
261 })
262 }
263
264 #[inline]
265 fn round(self) -> Self {
266 with_graph(|g| {
267 if let Some(v) = g.node(self).as_f64() {
268 g.lit(v.round())
269 } else {
270 panic!("round() requires a literal expression")
271 }
272 })
273 }
274
275 #[inline]
276 fn exp(self) -> Self {
277 let log2_e = Self::from_f64(std::f64::consts::LOG2_E);
279 let scaled = with_graph(|g| g.mul(self, log2_e));
280 with_graph(|g| g.exp2(scaled))
281 }
282
283 #[inline]
284 fn ln(self) -> Self {
285 let ln_2 = Self::from_f64(std::f64::consts::LN_2);
287 let l = with_graph(|g| g.log2(self));
288 with_graph(|g| g.mul(l, ln_2))
289 }
290
291 #[inline]
292 fn powf(self, p: Self) -> Self {
293 let l = with_graph(|g| g.log2(self));
295 let pl = with_graph(|g| g.mul(p, l));
296 with_graph(|g| g.exp2(pl))
297 }
298
299 #[inline]
300 fn sinh(self) -> Self {
301 let half = Self::from_f64(0.5);
303 let ex = self.exp();
304 let neg_x = with_graph(|g| g.neg(self));
305 let enx = Scalar::exp(neg_x);
306 let diff = ex - enx;
307 with_graph(|g| g.mul(half, diff))
308 }
309
310 #[inline]
311 fn cosh(self) -> Self {
312 let half = Self::from_f64(0.5);
314 let ex = self.exp();
315 let neg_x = with_graph(|g| g.neg(self));
316 let enx = Scalar::exp(neg_x);
317 let sum = ex + enx;
318 with_graph(|g| g.mul(half, sum))
319 }
320
321 #[inline]
322 fn tanh(self) -> Self {
323 let lit = Self::from_f64;
329 let a = self.abs().min(lit(20.0));
330 let e = (a * Self::TWO).exp();
331 let big = Self::ONE - Self::TWO / (e + Self::ONE);
332 let a2 = a * a;
333 let series = a
334 * (Self::ONE
335 + a2 * (lit(-1.0 / 3.0)
336 + a2 * (lit(2.0 / 15.0)
337 + a2 * (lit(-17.0 / 315.0) + a2 * lit(62.0 / 2835.0)))));
338 let r = Self::select(a - lit(0.05), big, series);
339 Self::select(self, r, -r)
340 }
341
342 #[inline]
343 fn acosh(self) -> Self {
344 let one = ExprId::ONE;
346 let xx = with_graph(|g| g.mul(self, self));
347 let diff = with_graph(|g| {
348 let neg_one = g.neg(one);
349 g.add(xx, neg_one)
350 });
351 let sq = with_graph(|g| g.sqrt(diff));
352 let sum = with_graph(|g| g.add(self, sq));
353 Scalar::ln(sum)
354 }
355
356 #[inline]
357 fn asinh(self) -> Self {
358 let one = ExprId::ONE;
360 let xx = with_graph(|g| g.mul(self, self));
361 let sum_inner = with_graph(|g| g.add(xx, one));
362 let sq = with_graph(|g| g.sqrt(sum_inner));
363 let sum = with_graph(|g| g.add(self, sq));
364 Scalar::ln(sum)
365 }
366
367 #[inline]
368 fn atanh(self) -> Self {
369 let half = Self::from_f64(0.5);
371 let one = ExprId::ONE;
372 let one_plus = with_graph(|g| g.add(one, self));
373 let neg_x = with_graph(|g| g.neg(self));
374 let one_minus = with_graph(|g| g.add(one, neg_x));
375 let ratio = one_plus / one_minus;
376 let l = Scalar::ln(ratio);
377 with_graph(|g| g.mul(half, l))
378 }
379
380 #[inline]
381 fn from_f64(v: f64) -> Self {
382 with_graph(|g| g.lit(v))
383 }
384
385 #[inline]
386 fn to_f64(self) -> f64 {
387 panic!("cannot evaluate symbolic ExprId to f64 — use ExprGraph::eval() instead")
388 }
389
390 #[inline]
391 fn from_i32(v: i32) -> Self {
392 with_graph(|g| g.lit(v as f64))
393 }
394
395 #[inline]
396 fn select(cond: Self, a: Self, b: Self) -> Self {
397 with_graph(|g| g.select(cond, a, b))
398 }
399}
400
401#[cfg(test)]
406mod tests {
407 use tang::Scalar;
408
409 fn eval1(f: impl Fn(crate::ExprId) -> crate::ExprId, x: f64) -> f64 {
411 let (g, out) = crate::trace(|| f(crate::ExprId::var(0)));
412 g.compile(out)(&[x])
413 }
414
415 #[test]
416 fn min_max_abs_are_exact() {
417 for (a, b) in [(1.0, 1e-17), (-3.0, 2.5), (1e200, -1e200), (1e-30, 2e-30)] {
418 let lo = eval1(|x| x.min(crate::ExprId::from_f64(b)), a);
419 let hi = eval1(|x| x.max(crate::ExprId::from_f64(b)), a);
420 assert_eq!(lo, a.min(b), "min({a}, {b})");
421 assert_eq!(hi, a.max(b), "max({a}, {b})");
422 }
423 for x in [-1e200, -2.5, 0.0, 1e-300, 3.0] {
424 assert_eq!(eval1(|v| v.abs(), x), x.abs(), "abs({x})");
425 }
426 }
427
428 #[test]
429 fn tanh_is_finite_and_accurate() {
430 for x in [
431 -2000.0, -30.0, -1.0, -0.04, -1e-8, 0.0, 1e-8, 0.03, 0.05, 0.5, 5.0, 25.0, 2000.0,
432 ] {
433 let got = eval1(|v| v.tanh(), x);
434 let want: f64 = x.tanh();
435 assert!(got.is_finite(), "tanh({x}) = {got}");
436 let err = (got - want).abs() / want.abs().max(1e-300);
437 assert!(
438 want == 0.0 && got == 0.0 || err < 4e-15,
439 "tanh({x}): {got} vs {want}, rel {err:e}"
440 );
441 }
442 }
443
444 use crate::{trace, ExprId};
445
446 #[test]
447 fn basic_arithmetic() {
448 let (g, result) = trace(|| {
449 let x = ExprId::from_f64(3.0);
450 let y = ExprId::from_f64(4.0);
451 x + y
452 });
453 let val = g.eval::<f64>(result, &[]);
455 assert!((val - 7.0).abs() < 1e-10);
456 }
457
458 #[test]
459 fn var_trace() {
460 let (g, result) = trace(|| {
461 let x: ExprId = Scalar::from_f64(0.0); x
463 });
464 assert_eq!(result, ExprId::ZERO);
465 assert_eq!(g.len(), 3); }
467
468 #[test]
469 fn constants_are_lits() {
470 let (_g, (half, pi)) = trace(|| {
471 let h = ExprId::from_f64(0.5);
472 let p = ExprId::from_f64(std::f64::consts::PI);
473 (h, p)
474 });
475 assert_ne!(half, pi);
476 }
477}