Skip to main content

fidget_core/context/
tree.rs

1//! Context-free math trees
2use super::op::{BinaryOpcode, UnaryOpcode};
3use crate::{context::NotAVar, var::Var};
4use ordered_float::OrderedFloat;
5use std::{cmp::Ordering, sync::Arc};
6
7/// Opcode type for trees
8///
9/// This is equivalent to [`Op`](crate::context::Op), but also includes the
10/// [`RemapAxes`](TreeOp::RemapAxes) and [`TreeOp::RemapAffine`] operations for
11/// lazy remapping.
12#[derive(Debug)]
13#[allow(missing_docs)]
14pub enum TreeOp {
15    /// Input (an arbitrary [`Var`])
16    Input(Var),
17    Const(f32),
18    Binary(BinaryOpcode, Arc<TreeOp>, Arc<TreeOp>),
19    Unary(UnaryOpcode, Arc<TreeOp>),
20    /// Lazy remapping of trees
21    ///
22    /// When imported into a `Context`, all `x/y/z` clauses within `target` will
23    /// be replaced with the provided `x/y/z` trees.
24    ///
25    /// If the transform is affine, then `RemapAffine` should be preferred,
26    /// because it flattens sequences of affine transformations.
27    RemapAxes {
28        target: Arc<TreeOp>,
29        x: Arc<TreeOp>,
30        y: Arc<TreeOp>,
31        z: Arc<TreeOp>,
32    },
33    /// Lazy affine transforms
34    ///
35    /// When imported into a `Context`, the `x/y/z` clauses within `target` will
36    /// be transformed with the provided affine matrix.
37    RemapAffine {
38        target: Arc<TreeOp>,
39        mat: nalgebra::Affine3<f32>,
40    },
41}
42
43impl Drop for TreeOp {
44    fn drop(&mut self) {
45        // Early exit for TreeOps which have limited recursion
46        if self.eligible_for_fast_drop() {
47            return;
48        }
49
50        let mut todo = vec![std::mem::replace(self, TreeOp::Const(0.0))];
51        let empty = Arc::new(TreeOp::Const(0.0));
52        while let Some(mut t) = todo.pop() {
53            for t in t.iter_children_mut() {
54                let arg = std::mem::replace(t, empty.clone());
55                todo.extend(Arc::into_inner(arg));
56            }
57            drop(t);
58        }
59    }
60}
61
62/// `TreeOp` equality uses [`OrderedFloat`] semantics for comparisons
63///
64/// This is subtle, but it ensures consistent behavior while using pointer
65/// equality for short-circuiting recursive checks.  If we instead used standard
66/// floating-point semantics, two `NAN` trees would be considered equal if one
67/// is a clone of the other (due to pointer equality) but unequal if they were
68/// constructed separately (due to float semantics).
69///
70/// In addition, this makes equality reflexive, allowing us to implement `Eq`
71/// (and to store values in hashmaps).
72impl PartialEq for TreeOp {
73    fn eq(&self, other: &Self) -> bool {
74        if std::ptr::eq(self, other) {
75            return true;
76        }
77        // Heap recursion using a `Vec`, to avoid blowing up the stack
78        let mut todo = vec![(self, other)];
79        while let Some((a, b)) = todo.pop() {
80            // Pointer equality lets us short-circuit deep checks
81            if std::ptr::eq(a, b) {
82                continue;
83            }
84            // Otherwise, we check opcodes then recurse
85            match (a, b) {
86                (TreeOp::Input(a), TreeOp::Input(b)) => {
87                    if *a != *b {
88                        return false;
89                    }
90                }
91                (TreeOp::Const(a), TreeOp::Const(b)) => {
92                    if OrderedFloat(*a) != OrderedFloat(*b) {
93                        return false;
94                    }
95                }
96                (TreeOp::Unary(op_a, ..), TreeOp::Unary(op_b, ..)) => {
97                    if *op_a != *op_b {
98                        return false;
99                    }
100                }
101                (TreeOp::Binary(op_a, ..), TreeOp::Binary(op_b, ..)) => {
102                    if *op_a != *op_b {
103                        return false;
104                    }
105                }
106                (TreeOp::RemapAxes { .. }, TreeOp::RemapAxes { .. }) => {
107                    // No non-recursive state to compare
108                }
109                (
110                    TreeOp::RemapAffine { mat: mat_a, .. },
111                    TreeOp::RemapAffine { mat: mat_b, .. },
112                ) => {
113                    if mat_a
114                        .matrix()
115                        .iter()
116                        .zip(mat_b.matrix().iter())
117                        .any(|(a, b)| OrderedFloat(*a) != OrderedFloat(*b))
118                    {
119                        return false;
120                    }
121                }
122                _ => return false,
123            }
124            todo.extend(
125                a.iter_children()
126                    .zip(b.iter_children())
127                    .map(|(a, b)| (a.as_ref(), b.as_ref())),
128            )
129        }
130        true
131    }
132}
133
134impl Eq for TreeOp {}
135
136impl TreeOp {
137    /// Checks whether the given tree is eligible for fast dropping
138    ///
139    /// Fast dropping uses the normal `Drop` implementation, which recurses on
140    /// the stack and can overflow for deep trees.  A recursive tree is only
141    /// eligible for fast dropping if all of its children are non-recursive.
142    fn eligible_for_fast_drop(&self) -> bool {
143        self.iter_children().all(|c| c.does_not_recurse())
144    }
145
146    /// Returns `true` if the given child does not recurse
147    fn does_not_recurse(&self) -> bool {
148        matches!(self, TreeOp::Const(..) | TreeOp::Input(..))
149    }
150
151    fn iter_children(&self) -> impl Iterator<Item = &Arc<TreeOp>> {
152        match self {
153            TreeOp::Const(..) | TreeOp::Input(..) => [None, None, None, None],
154            TreeOp::Unary(_op, arg) => [Some(arg), None, None, None],
155            TreeOp::Binary(_op, lhs, rhs) => [Some(lhs), Some(rhs), None, None],
156            TreeOp::RemapAxes { target, x, y, z } => {
157                [Some(target), Some(x), Some(y), Some(z)]
158            }
159            TreeOp::RemapAffine { target, .. } => {
160                [Some(target), None, None, None]
161            }
162        }
163        .into_iter()
164        .flatten()
165    }
166
167    fn iter_children_mut(&mut self) -> impl Iterator<Item = &mut Arc<TreeOp>> {
168        match self {
169            TreeOp::Const(..) | TreeOp::Input(..) => [None, None, None, None],
170            TreeOp::Unary(_op, arg) => [Some(arg), None, None, None],
171            TreeOp::Binary(_op, lhs, rhs) => [Some(lhs), Some(rhs), None, None],
172            TreeOp::RemapAxes { target, x, y, z } => {
173                [Some(target), Some(x), Some(y), Some(z)]
174            }
175            TreeOp::RemapAffine { target, .. } => {
176                [Some(target), None, None, None]
177            }
178        }
179        .into_iter()
180        .flatten()
181    }
182}
183
184impl std::hash::Hash for TreeOp {
185    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
186        let mut todo = vec![self];
187        while let Some(t) = todo.pop() {
188            std::mem::discriminant(t).hash(state);
189            match t {
190                TreeOp::Input(i) => i.hash(state),
191                TreeOp::Const(v) => OrderedFloat(*v).hash(state),
192                TreeOp::Binary(op, _lhs, _rhs) => op.hash(state),
193                TreeOp::Unary(op, _arg) => op.hash(state),
194                TreeOp::RemapAxes { .. } => (), // no non-recursive state
195                TreeOp::RemapAffine { target: _, mat } => mat
196                    .matrix()
197                    .iter()
198                    .for_each(|f| OrderedFloat(*f).hash(state)),
199            }
200            todo.extend(t.iter_children().map(|t| t.as_ref()))
201        }
202    }
203}
204
205impl From<f32> for Tree {
206    fn from(v: f32) -> Tree {
207        Tree::constant(v)
208    }
209}
210
211impl From<i32> for Tree {
212    fn from(v: i32) -> Tree {
213        Tree::constant(v as f32)
214    }
215}
216
217impl From<Var> for Tree {
218    fn from(v: Var) -> Tree {
219        Tree(Arc::new(TreeOp::Input(v)))
220    }
221}
222
223impl From<TreeOp> for Tree {
224    fn from(t: TreeOp) -> Tree {
225        Tree(Arc::new(t))
226    }
227}
228
229/// Owned handle for a standalone math tree
230///
231/// Note that trees use [`OrderedFloat`] semantics for comparisons and hashing,
232/// not floating-point semantics.  This makes it possible to quickly compare
233/// trees by checking inner [`TreeOp`] pointer equality, and makes equality
234/// reflexive (so that trees can derive `Eq` instead of just `PartialEq`).  It
235/// would be surprising if this mattered to anyone, because it only affects
236/// behavior around `NAN` values, and storing `NAN` values in a tree constant or
237/// transform matrix would be a strange thing to do.
238#[derive(Clone, Debug, PartialEq, Eq, Hash, facet::Facet)]
239pub struct Tree(#[facet(opaque)] Arc<TreeOp>);
240
241impl std::ops::Deref for Tree {
242    type Target = TreeOp;
243    fn deref(&self) -> &Self::Target {
244        &self.0
245    }
246}
247
248impl Tree {
249    /// Returns an `(x, y, z)` tuple
250    pub fn axes() -> (Self, Self, Self) {
251        (Self::x(), Self::y(), Self::z())
252    }
253
254    /// Returns a pointer to the inner [`TreeOp`]
255    ///
256    /// This can be used as a strong (but not unique) identity.
257    pub fn as_ptr(&self) -> *const TreeOp {
258        Arc::as_ptr(&self.0)
259    }
260
261    /// Shallow (pointer) equality check
262    pub fn ptr_eq(&self, other: &Self) -> bool {
263        std::ptr::eq(self.as_ptr(), other.as_ptr())
264    }
265
266    /// Borrow the inner `Arc<TreeOp>`
267    pub(crate) fn arc(&self) -> &Arc<TreeOp> {
268        &self.0
269    }
270
271    /// Remaps the axes of the given tree
272    ///
273    /// If the mapping is affine, then [`remap_affine`](Self::remap_affine)
274    /// should be preferred.
275    ///
276    /// The remapping is lazy; it is not evaluated until the tree is imported
277    /// into a `Context`.
278    pub fn remap_xyz(&self, x: Tree, y: Tree, z: Tree) -> Tree {
279        Self(Arc::new(TreeOp::RemapAxes {
280            target: self.0.clone(),
281            x: x.0,
282            y: y.0,
283            z: z.0,
284        }))
285    }
286
287    /// Performs an affine remapping of the given tree
288    ///
289    /// The remapping is lazy; it is not evaluated until the tree is imported
290    /// into a `Context`.
291    pub fn remap_affine(&self, mat: nalgebra::Affine3<f32>) -> Tree {
292        // Flatten affine trees
293        let out = match &*self.0 {
294            TreeOp::RemapAffine { target, mat: next } => TreeOp::RemapAffine {
295                target: target.clone(),
296                mat: next * mat,
297            },
298            _ => TreeOp::RemapAffine {
299                target: self.0.clone(),
300                mat,
301            },
302        };
303        Self(out.into())
304    }
305
306    /// Returns the inner [`Var`] if this is an input tree, or `None`
307    pub fn var(&self) -> Option<Var> {
308        if let TreeOp::Input(v) = &*self.0 {
309            Some(*v)
310        } else {
311            None
312        }
313    }
314
315    /// Performs symbolic differentiation with respect to the given variable
316    pub fn deriv(&self, v: Var) -> Tree {
317        let mut ctx = crate::Context::new();
318        let node = ctx.import(self);
319        ctx.deriv(node, v).and_then(|d| ctx.export(d)).unwrap()
320    }
321
322    /// Raises this tree to the power of an integer using exponentiation by squaring
323    pub fn pow(&self, mut n: i64) -> Self {
324        // TODO should this also be in `Context`?
325        let mut x = match n.cmp(&0) {
326            Ordering::Less => {
327                n = -n;
328                self.recip()
329            }
330            Ordering::Equal => {
331                return Tree::from(1.0);
332            }
333            Ordering::Greater => self.clone(),
334        };
335        let mut y: Option<Tree> = None;
336        while n > 1 {
337            if n % 2 == 1 {
338                y = match y {
339                    Some(y) => Some(x.clone() * y),
340                    None => Some(x.clone()),
341                };
342                n -= 1;
343            }
344            x = x.square();
345            n /= 2;
346        }
347        if let Some(y) = y {
348            x *= y;
349        }
350        x
351    }
352}
353
354impl TryFrom<Tree> for Var {
355    type Error = NotAVar;
356    fn try_from(t: Tree) -> Result<Var, NotAVar> {
357        t.var().ok_or(NotAVar)
358    }
359}
360
361/// See [`Context`](crate::Context) for documentation of these functions
362#[allow(missing_docs)]
363impl Tree {
364    pub fn x() -> Self {
365        Tree(Arc::new(TreeOp::Input(Var::X)))
366    }
367    pub fn y() -> Self {
368        Tree(Arc::new(TreeOp::Input(Var::Y)))
369    }
370    pub fn z() -> Self {
371        Tree(Arc::new(TreeOp::Input(Var::Z)))
372    }
373    pub fn constant(f: f32) -> Self {
374        Tree(Arc::new(TreeOp::Const(f)))
375    }
376    fn op_unary(a: Tree, op: UnaryOpcode) -> Self {
377        Tree(Arc::new(TreeOp::Unary(op, a.0)))
378    }
379    fn op_binary(a: Tree, b: Tree, op: BinaryOpcode) -> Self {
380        Tree(Arc::new(TreeOp::Binary(op, a.0, b.0)))
381    }
382    pub fn square(&self) -> Self {
383        Self::op_unary(self.clone(), UnaryOpcode::Square)
384    }
385    pub fn floor(&self) -> Self {
386        Self::op_unary(self.clone(), UnaryOpcode::Floor)
387    }
388    pub fn ceil(&self) -> Self {
389        Self::op_unary(self.clone(), UnaryOpcode::Ceil)
390    }
391    pub fn round(&self) -> Self {
392        Self::op_unary(self.clone(), UnaryOpcode::Round)
393    }
394    pub fn sqrt(&self) -> Self {
395        Self::op_unary(self.clone(), UnaryOpcode::Sqrt)
396    }
397    pub fn max<T: Into<Tree>>(&self, other: T) -> Self {
398        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Max)
399    }
400    pub fn min<T: Into<Tree>>(&self, other: T) -> Self {
401        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Min)
402    }
403    pub fn compare<T: Into<Tree>>(&self, other: T) -> Self {
404        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Compare)
405    }
406    pub fn mix<T: Into<Tree>>(&self, other: T) -> Self {
407        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Mix)
408    }
409    pub fn modulo<T: Into<Tree>>(&self, other: T) -> Self {
410        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Mod)
411    }
412    pub fn and<T: Into<Tree>>(&self, other: T) -> Self {
413        Self::op_binary(self.clone(), other.into(), BinaryOpcode::And)
414    }
415    pub fn or<T: Into<Tree>>(&self, other: T) -> Self {
416        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Or)
417    }
418    pub fn atan2<T: Into<Tree>>(&self, other: T) -> Self {
419        Self::op_binary(self.clone(), other.into(), BinaryOpcode::Atan)
420    }
421    pub fn neg(&self) -> Self {
422        Self::op_unary(self.clone(), UnaryOpcode::Neg)
423    }
424    pub fn recip(&self) -> Self {
425        Self::op_unary(self.clone(), UnaryOpcode::Recip)
426    }
427    pub fn sin(&self) -> Self {
428        Self::op_unary(self.clone(), UnaryOpcode::Sin)
429    }
430    pub fn rand(&self) -> Tree {
431        Self::op_unary(self.clone(), UnaryOpcode::Rand)
432    }
433    pub fn cos(&self) -> Self {
434        Self::op_unary(self.clone(), UnaryOpcode::Cos)
435    }
436    pub fn tan(&self) -> Self {
437        Self::op_unary(self.clone(), UnaryOpcode::Tan)
438    }
439    pub fn asin(&self) -> Self {
440        Self::op_unary(self.clone(), UnaryOpcode::Asin)
441    }
442    pub fn acos(&self) -> Self {
443        Self::op_unary(self.clone(), UnaryOpcode::Acos)
444    }
445    pub fn atan(&self) -> Self {
446        Self::op_unary(self.clone(), UnaryOpcode::Atan)
447    }
448    pub fn exp(&self) -> Self {
449        Self::op_unary(self.clone(), UnaryOpcode::Exp)
450    }
451    pub fn ln(&self) -> Self {
452        Self::op_unary(self.clone(), UnaryOpcode::Ln)
453    }
454    pub fn not(&self) -> Self {
455        Self::op_unary(self.clone(), UnaryOpcode::Not)
456    }
457    pub fn abs(&self) -> Self {
458        Self::op_unary(self.clone(), UnaryOpcode::Abs)
459    }
460}
461
462macro_rules! impl_binary {
463    ($op:ident, $op_assign:ident, $base_fn:ident, $assign_fn:ident) => {
464        impl<A: Into<Tree>> std::ops::$op<A> for Tree {
465            type Output = Self;
466
467            fn $base_fn(self, other: A) -> Self {
468                Self::op_binary(self, other.into(), BinaryOpcode::$op)
469            }
470        }
471        impl<A: Into<Tree>> std::ops::$op_assign<A> for Tree {
472            fn $assign_fn(&mut self, other: A) {
473                use std::ops::$op;
474                let mut next = self.clone().$base_fn(other.into());
475                std::mem::swap(self, &mut next);
476            }
477        }
478        impl std::ops::$op<Tree> for f32 {
479            type Output = Tree;
480            fn $base_fn(self, other: Tree) -> Tree {
481                Tree::op_binary(self.into(), other, BinaryOpcode::$op)
482            }
483        }
484    };
485}
486
487impl_binary!(Add, AddAssign, add, add_assign);
488impl_binary!(Sub, SubAssign, sub, sub_assign);
489impl_binary!(Mul, MulAssign, mul, mul_assign);
490impl_binary!(Div, DivAssign, div, div_assign);
491
492impl std::ops::Neg for Tree {
493    type Output = Tree;
494    fn neg(self) -> Self::Output {
495        Tree::op_unary(self, UnaryOpcode::Neg)
496    }
497}
498
499#[cfg(test)]
500mod test {
501    use super::*;
502    use crate::Context;
503    use std::hash::BuildHasher;
504
505    #[test]
506    fn tree_x() {
507        let x1 = Tree::x();
508        let x2 = Tree::x();
509        assert!(!x1.ptr_eq(&x2)); // shallow equality
510        assert_eq!(x1, x2); // deep equality
511
512        let mut ctx = Context::new();
513        let x1 = ctx.import(&x1);
514        let x2 = ctx.import(&x2);
515        assert_eq!(x1, x2);
516    }
517
518    #[test]
519    fn test_remap_xyz() {
520        // Remapping X
521        let s = Tree::x() + 1.0;
522
523        let v = s.remap_xyz(Tree::y(), Tree::z(), Tree::x());
524        let mut ctx = Context::new();
525        let v_ = ctx.import(&v);
526        assert_eq!(ctx.eval_xyz(v_, 0.0, 1.0, 0.0).unwrap(), 2.0);
527
528        let v = s.remap_xyz(Tree::z(), Tree::x(), Tree::y());
529        let mut ctx = Context::new();
530        let v_ = ctx.import(&v);
531        assert_eq!(ctx.eval_xyz(v_, 0.0, 0.0, 1.0).unwrap(), 2.0);
532
533        let v = s.remap_xyz(Tree::x(), Tree::y(), Tree::z());
534        let mut ctx = Context::new();
535        let v_ = ctx.import(&v);
536        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 2.0);
537
538        // Remapping Y
539        let s = Tree::y() + 1.0;
540
541        let v = s.remap_xyz(Tree::y(), Tree::z(), Tree::x());
542        let mut ctx = Context::new();
543        let v_ = ctx.import(&v);
544        assert_eq!(ctx.eval_xyz(v_, 0.0, 0.0, 1.0).unwrap(), 2.0);
545
546        let v = s.remap_xyz(Tree::z(), Tree::x(), Tree::y());
547        let mut ctx = Context::new();
548        let v_ = ctx.import(&v);
549        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 2.0);
550
551        let v = s.remap_xyz(Tree::x(), Tree::y(), Tree::z());
552        let mut ctx = Context::new();
553        let v_ = ctx.import(&v);
554        assert_eq!(ctx.eval_xyz(v_, 0.0, 1.0, 0.0).unwrap(), 2.0);
555
556        // Remapping Z
557        let s = Tree::z() + 1.0;
558
559        let v = s.remap_xyz(Tree::y(), Tree::z(), Tree::x());
560        let mut ctx = Context::new();
561        let v_ = ctx.import(&v);
562        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 2.0);
563
564        let v = s.remap_xyz(Tree::z(), Tree::x(), Tree::y());
565        let mut ctx = Context::new();
566        let v_ = ctx.import(&v);
567        assert_eq!(ctx.eval_xyz(v_, 0.0, 1.0, 0.0).unwrap(), 2.0);
568
569        let v = s.remap_xyz(Tree::x(), Tree::y(), Tree::z());
570        let mut ctx = Context::new();
571        let v_ = ctx.import(&v);
572        assert_eq!(ctx.eval_xyz(v_, 0.0, 0.0, 1.0).unwrap(), 2.0);
573
574        // Test remapping to a constant
575        let s = Tree::x() + 1.0;
576        let one = Tree::constant(3.0);
577        let v = s.remap_xyz(one, Tree::y(), Tree::z());
578        let v_ = ctx.import(&v);
579        assert_eq!(ctx.eval_xyz(v_, 0.0, 1.0, 0.0).unwrap(), 4.0);
580    }
581
582    #[test]
583    fn test_remap_affine() {
584        let s = Tree::x();
585        // Two rotations by 45° -> 90°
586        let t = nalgebra::convert(nalgebra::Rotation3::<f32>::from_axis_angle(
587            &nalgebra::Vector3::<f32>::z_axis(),
588            -std::f32::consts::FRAC_PI_4,
589        ));
590        let s = s.remap_affine(t);
591        let s = s.remap_affine(t);
592
593        let TreeOp::RemapAffine { target, .. } = &*s else {
594            panic!("invalid shape");
595        };
596        assert!(matches!(&**target, TreeOp::Input(Var::X)));
597
598        let mut ctx = Context::new();
599        let v_ = ctx.import(&s);
600
601        assert!((ctx.eval_xyz(v_, 0.0, 1.0, 0.0).unwrap() - 1.0).abs() < 1e-6);
602        assert!(
603            (ctx.eval_xyz(v_, 0.0, -2.0, 0.0).unwrap() - -2.0).abs() < 1e-6
604        );
605    }
606
607    #[test]
608    fn test_remap_order() {
609        let translate = nalgebra::convert(nalgebra::Translation3::<f32>::new(
610            3.0, 0.0, 0.0,
611        ));
612        let scale =
613            nalgebra::convert(nalgebra::Scale3::<f32>::new(0.5, 0.5, 0.5));
614
615        let s = Tree::x();
616        let s = s.remap_affine(translate);
617        let s = s.remap_affine(scale);
618
619        // Confirm that we didn't stack up RemapAffine nodes
620        let TreeOp::RemapAffine { target, .. } = &*s else {
621            panic!("invalid shape");
622        };
623        assert!(matches!(&**target, TreeOp::Input(Var::X)));
624
625        // Basic evaluation testing
626        let mut ctx = Context::new();
627        let v_ = ctx.import(&s);
628        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 3.5);
629        assert_eq!(ctx.eval_xyz(v_, 2.0, 0.0, 0.0).unwrap(), 4.0);
630
631        // Do the same thing but testing collapsing in `Context::import`
632        let manual = TreeOp::RemapAffine {
633            target: Arc::new(TreeOp::RemapAffine {
634                target: TreeOp::Input(Var::X).into(),
635                mat: scale,
636            }),
637            mat: translate,
638        }
639        .into();
640        let mut ctx = Context::new();
641        let v_ = ctx.import(&manual);
642        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 3.5);
643        assert_eq!(ctx.eval_xyz(v_, 2.0, 0.0, 0.0).unwrap(), 4.0);
644
645        // Swap the order and make sure it still works
646        let s = Tree::x();
647        let s = s.remap_affine(scale);
648        let s = s.remap_affine(translate);
649
650        let mut ctx = Context::new();
651        let v_ = ctx.import(&s);
652        assert_eq!(ctx.eval_xyz(v_, 1.0, 0.0, 0.0).unwrap(), 2.0);
653        assert_eq!(ctx.eval_xyz(v_, 2.0, 0.0, 0.0).unwrap(), 2.5);
654    }
655
656    #[test]
657    fn deep_recursion_drop() {
658        let mut x = Tree::x();
659        for _ in 0..1_000_000 {
660            x += 1.0;
661        }
662        drop(x);
663        // we should not panic here!
664    }
665
666    #[test]
667    fn deep_recursion_eq() {
668        let mut x1 = Tree::x();
669        for _ in 0..1_000_000 {
670            x1 += 1.0;
671        }
672        let mut x2 = Tree::x();
673        for _ in 0..1_000_000 {
674            x2 += 1.0;
675        }
676        assert_eq!(x1, x2);
677    }
678
679    #[test]
680    fn deep_recursion_hash() {
681        let mut x1 = Tree::x();
682        for _ in 0..1_000_000 {
683            x1 += 1.0;
684        }
685        let mut x2 = Tree::x();
686        for _ in 0..1_000_000 {
687            x2 += 1.0;
688        }
689        let state = std::hash::RandomState::new();
690        assert_eq!(state.hash_one(&x1), state.hash_one(&x2));
691    }
692
693    #[test]
694    fn deep_recursion_import() {
695        let mut x = Tree::x();
696        for _ in 0..1_000_000 {
697            x += 1.0;
698        }
699        let mut ctx = Context::new();
700        ctx.import(&x);
701        // we should not panic here!
702    }
703
704    #[test]
705    fn tree_remap_multi() {
706        let mut ctx = Context::new();
707
708        let out = Tree::x() + Tree::y() + Tree::z();
709        let out =
710            out.remap_xyz(Tree::x() * 2.0, Tree::y() * 3.0, Tree::z() * 5.0);
711
712        let v_ = ctx.import(&out);
713        assert_eq!(ctx.eval_xyz(v_, 1.0, 1.0, 1.0).unwrap(), 10.0);
714        assert_eq!(ctx.eval_xyz(v_, 2.0, 1.0, 1.0).unwrap(), 12.0);
715        assert_eq!(ctx.eval_xyz(v_, 2.0, 2.0, 1.0).unwrap(), 15.0);
716        assert_eq!(ctx.eval_xyz(v_, 2.0, 2.0, 2.0).unwrap(), 20.0);
717
718        let out = out.remap_xyz(Tree::y(), Tree::z(), Tree::x());
719        let v_ = ctx.import(&out);
720        assert_eq!(ctx.eval_xyz(v_, 1.0, 1.0, 1.0).unwrap(), 10.0);
721        assert_eq!(ctx.eval_xyz(v_, 2.0, 1.0, 1.0).unwrap(), 15.0);
722        assert_eq!(ctx.eval_xyz(v_, 2.0, 2.0, 1.0).unwrap(), 17.0);
723        assert_eq!(ctx.eval_xyz(v_, 2.0, 2.0, 2.0).unwrap(), 20.0);
724    }
725
726    #[test]
727    fn tree_import_cache() {
728        let mut x = Tree::x();
729        for _ in 0..100_000 {
730            x += 1.0;
731        }
732        let mut ctx = Context::new();
733        let start = std::time::Instant::now();
734        ctx.import(&x);
735        let small = start.elapsed();
736
737        // Build a new tree with 4 copies of the original
738        let x = x.clone() * x.clone() * x.clone() * x;
739        let mut ctx = Context::new();
740        let start = std::time::Instant::now();
741        ctx.import(&x);
742        let large = start.elapsed();
743
744        assert!(
745            large.as_millis() < small.as_millis() * 2,
746            "tree import cache failed: {large:?} is much larger than {small:?}"
747        );
748    }
749
750    #[test]
751    fn tree_import_nocache() {
752        let mut x = Tree::x();
753        for _ in 0..100_000 {
754            x += 1.0;
755        }
756        let mut ctx = Context::new();
757        let start = std::time::Instant::now();
758        ctx.import(&x);
759        let small = start.elapsed();
760
761        // Build a new tree with 4 remapped versions of the original
762        let x = x.remap_xyz(Tree::y(), Tree::z(), Tree::x())
763            * x.remap_xyz(Tree::z(), Tree::x(), Tree::y())
764            * x.remap_xyz(Tree::y(), Tree::x(), Tree::z())
765            * x;
766        let mut ctx = Context::new();
767        let start = std::time::Instant::now();
768        ctx.import(&x);
769        let large = start.elapsed();
770
771        assert!(
772            large.as_millis() > small.as_millis() * 2,
773            "tree import cache failed:
774             {large:?} is not much larger than {small:?}"
775        );
776    }
777
778    #[test]
779    fn tree_from_int() {
780        let a = Tree::from(3);
781        let b = a * 5;
782
783        let mut ctx = Context::new();
784        let root = ctx.import(&b);
785        assert_eq!(ctx.get_const(root).unwrap(), 15.0);
786    }
787
788    #[test]
789    fn tree_deriv() {
790        // dx/dx = 1
791        let x = Tree::x();
792        let vx = x.var().unwrap();
793        let d = x.deriv(vx);
794        let TreeOp::Const(v) = *d else {
795            panic!("invalid deriv {d:?}")
796        };
797        assert_eq!(v, 1.0);
798
799        // dx/dv = 0
800        let d = x.deriv(Var::new());
801        let TreeOp::Const(v) = *d else {
802            panic!("invalid deriv {d:?}")
803        };
804        assert_eq!(v, 0.0);
805    }
806
807    #[test]
808    fn tree_pow() {
809        let a = Tree::from(3);
810        let b = a.pow(3);
811        let c = a.pow(-3);
812        let d = a.pow(0);
813
814        let mut ctx = Context::new();
815        let root = ctx.import(&b);
816        assert_eq!(ctx.get_const(root).unwrap(), 27.0);
817        ctx.clear();
818        let root = ctx.import(&c);
819        assert_eq!(ctx.get_const(root).unwrap(), (1f32 / 3.0).powi(3));
820        ctx.clear();
821        let root = ctx.import(&d);
822        assert_eq!(ctx.get_const(root).unwrap(), 1.0);
823    }
824
825    #[test]
826    fn tree_poke() {
827        #[derive(facet::Facet)]
828        struct Transform {
829            tree: Tree,
830            x: f32,
831        }
832
833        let builder = facet::Partial::alloc::<Transform>()
834            .unwrap()
835            .set_field("tree", Tree::x() + 2.0 * Tree::y())
836            .unwrap()
837            .set_field("x", 1.0f32)
838            .unwrap();
839        let t: Transform = builder.build().unwrap().materialize().unwrap();
840        assert_eq!(t.x, 1.0);
841        let mut ctx = Context::new();
842        let node = ctx.import(&t.tree);
843        assert_eq!(ctx.eval_xyz(node, 1.0, 2.0, 3.0).unwrap(), 5.0);
844    }
845
846    #[test]
847    fn tree_eq_simple() {
848        let a = Tree::from(1.0) + Tree::x();
849        let a_ = a.clone();
850        let b = Tree::from(1.0) + Tree::x();
851        assert_eq!(a_, a);
852        assert_eq!(a, b);
853
854        let c = Tree::from(1.0) + Tree::y();
855        assert_ne!(a, c);
856        assert_ne!(b, c);
857
858        let a = Tree::x().cos();
859        let a_ = Tree::x().cos();
860        let b = Tree::x().sin();
861        assert_eq!(a, a_);
862        assert_ne!(a, b);
863    }
864
865    #[test]
866    fn tree_hash_simple() {
867        let a = Tree::from(1.0) + Tree::x();
868        let a_ = a.clone();
869        let b = Tree::from(1.0) + Tree::x();
870        let state = std::hash::RandomState::new();
871
872        assert_eq!(state.hash_one(&a), state.hash_one(&b));
873        assert_eq!(state.hash_one(&a), state.hash_one(&a_));
874
875        let c = Tree::from(1.0) + Tree::y();
876        assert_ne!(state.hash_one(&a), state.hash_one(&c));
877
878        let d = Tree::from(1.0) * Tree::x();
879        assert_ne!(state.hash_one(&a), state.hash_one(&d));
880
881        let a = Tree::x().cos();
882        let a_ = Tree::x().cos();
883        let b = Tree::x().sin();
884        assert_eq!(state.hash_one(&a), state.hash_one(&a_));
885        assert_ne!(state.hash_one(&a), state.hash_one(&b));
886    }
887
888    #[test]
889    fn tree_eq_nan() {
890        let a = Tree::from(f32::NAN);
891        let a_ = a.clone();
892        let b = Tree::from(f32::NAN);
893
894        assert!(a.ptr_eq(&a_));
895        assert_eq!(a, a_);
896        assert!(!a.ptr_eq(&b));
897        assert_eq!(a, b);
898
899        let affine: nalgebra::Affine3<_> =
900            nalgebra::convert(nalgebra::Translation3::new(1.0, 2.0, f32::NAN));
901        let a = Tree::x().remap_affine(affine);
902        let b = Tree::x().remap_affine(affine);
903        assert_eq!(a, b);
904    }
905
906    #[test]
907    fn tree_hash_nan() {
908        let a = Tree::from(f32::NAN);
909        let a_ = a.clone();
910        let b = Tree::from(f32::NAN);
911        let c = Tree::from(1.0);
912        let state = std::hash::RandomState::new();
913
914        assert_eq!(state.hash_one(&a), state.hash_one(a_));
915        assert_eq!(state.hash_one(&a), state.hash_one(&b));
916        assert_ne!(state.hash_one(&a), state.hash_one(&c));
917
918        let affine: nalgebra::Affine3<_> =
919            nalgebra::convert(nalgebra::Translation3::new(1.0, 2.0, f32::NAN));
920        let a = Tree::x().remap_affine(affine);
921        let b = Tree::x().remap_affine(affine);
922        assert_eq!(state.hash_one(&a), state.hash_one(&b));
923
924        let affine: nalgebra::Affine3<_> =
925            nalgebra::convert(nalgebra::Translation3::new(1.0, 2.0, 3.0));
926        let c = Tree::x().remap_affine(affine);
927        assert_ne!(state.hash_one(&a), state.hash_one(&c));
928    }
929}