Skip to main content

nuvec_core/
tree.rs

1//! Trees of arrays: structured values (tuples, vectors, maps, options, user structs and
2//! enums) whose array *leaves* have paths and a fixed order, as MLX's `tree_flatten`.
3//!
4//! - [`Leaves`] is the read-only, object-safe half: anything whose leaves can be visited,
5//!   including borrowed views such as `&T`, `(&A, &B)` and `[T]`. [`crate::eval`] takes it,
6//!   so `eval(&(&loss, &grads))` evaluates both without cloning.
7//! - [`Tree`] is an owned tree that can be mutated in place and rebuilt with new leaves of
8//!   the same structure ([`Tree::unflatten`]); transforms differentiate, batch and compile
9//!   trees.
10//!
11//! **Paths and order** (MLX's `tree_flatten` names): struct fields in declaration order,
12//! sequences and tuples by index (`0`, `1`, …), maps by sorted key, `Option`, `Box` and
13//! newtype enum variants transparent, joined with `.`; a root array has the empty path.
14//!
15//! **Leaf kinds** ([`LeafKind`]): a plain [`Array`] (always differentiable), a [`Param`]
16//! (a module parameter, trainable unless frozen) or a buffer (an array field marked
17//! `#[tree(buffer)]`: saved and loaded, never trained).
18//!
19//! **Structure** ([`TreeDef`]): the root type, the ordered `(path, kind, trainable)` of every
20//! leaf, the active enum variants, the mode flags (train/eval) and a hash of the *static*
21//! fields (`#[tree(skip)]` fields of a [`Static`] type, such as a layer's `eps`). Two trees
22//! with equal `TreeDef`s have the same structure; a compiled function retraces when the
23//! structure changes.
24//!
25//! User types implement the traits with `#[derive(Tree)]` (`nuvec::Tree`; code that uses
26//! `nuvec_core` directly names it with `#[tree(crate = "nuvec_core")]`):
27//!
28//! ```
29//! use nuvec_core::tree::Tree;
30//! use nuvec_core::{Array, array};
31//!
32//! #[derive(Clone, nuvec_macros::Tree)]
33//! #[tree(crate = "nuvec_core")]
34//! struct Batch {
35//!     x: Array,
36//!     y: Array,
37//!     #[tree(skip)]
38//!     len: usize, // not a leaf: hashed into the structure
39//! }
40//!
41//! let b = Batch { x: array![1.0, 2.0], y: array![0.0], len: 2 };
42//! assert_eq!(b.paths(), ["x", "y"]);
43//! let doubled = b.map(|_, a| a.multiply(2.0));
44//! assert_eq!(doubled.x.to_vec::<f32>().unwrap(), [2.0, 4.0]);
45//! assert_eq!(doubled.len, 2);
46//! # let _ = b.len;
47//! ```
48//!
49//! Static fields and retracing: everything in a `TreeDef` is part of a compiled
50//! function's cache key, so switching train/eval, freezing, a different enum variant or a
51//! changed hashed field retraces once. A `#[tree(skip, static_unchecked)]` field is not
52//! hashed: its value is baked into compiled traces.
53
54use std::any::{Any, TypeId};
55use std::collections::{BTreeMap, HashMap};
56use std::hash::{Hash, Hasher};
57use std::marker::PhantomData;
58use std::sync::Arc;
59use std::time::Duration;
60
61use crate::scalar::{ScalarOperand, ScalarOrArray};
62use crate::{Array, Category, DType, Device, Error, Result};
63
64/// What a leaf is (see the [module documentation](self)).
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum LeafKind {
67    /// A plain array: always differentiable.
68    Array,
69    /// A module parameter: trainable unless frozen.
70    Param,
71    /// A buffer: saved and loaded, never trainable.
72    Buffer,
73}
74
75/// A module parameter: an array and a frozen flag (trainable unless frozen).
76///
77/// It reads as an [`Array`] (`Deref`), so methods, `&Param` arguments where `&Array` is
78/// expected, operators (`&x * &p`, `&p + 1.0`) and free functions (`ops::add(&p, &x)`)
79/// work as on the array. Gradients, [`Tree::map`] and clones keep the flag.
80#[derive(Debug, Clone, Default)]
81pub struct Param {
82    value: Array,
83    frozen: bool,
84}
85
86impl Param {
87    /// A trainable parameter.
88    pub fn new(value: Array) -> Self {
89        Self {
90            value,
91            frozen: false,
92        }
93    }
94
95    /// A parameter frozen at construction (a LoRA base, say).
96    pub fn frozen(value: Array) -> Self {
97        Self {
98            value,
99            frozen: true,
100        }
101    }
102
103    /// Whether the parameter is frozen (not trained).
104    pub fn is_frozen(&self) -> bool {
105        self.frozen
106    }
107
108    /// Freezes (`true`) or unfreezes (`false`) the parameter.
109    pub fn set_frozen(&mut self, frozen: bool) {
110        self.frozen = frozen;
111    }
112
113    /// The array.
114    pub fn value(&self) -> &Array {
115        &self.value
116    }
117
118    /// The array, mutably (the frozen flag is kept).
119    pub fn value_mut(&mut self) -> &mut Array {
120        &mut self.value
121    }
122
123    /// The array, consuming the parameter.
124    pub fn into_inner(self) -> Array {
125        self.value
126    }
127}
128
129impl std::ops::Deref for Param {
130    type Target = Array;
131    fn deref(&self) -> &Array {
132        &self.value
133    }
134}
135
136impl AsRef<Array> for Param {
137    fn as_ref(&self) -> &Array {
138        &self.value
139    }
140}
141
142impl From<Array> for Param {
143    fn from(value: Array) -> Self {
144        Self::new(value)
145    }
146}
147
148impl From<Param> for Array {
149    fn from(p: Param) -> Self {
150        p.value
151    }
152}
153
154impl From<&Param> for ScalarOrArray {
155    fn from(p: &Param) -> Self {
156        Self::Array(p.value.clone())
157    }
158}
159
160impl From<Param> for ScalarOrArray {
161    fn from(p: Param) -> Self {
162        Self::Array(p.value)
163    }
164}
165
166/// A parameter where an optional array is expected (`fast::rms_norm(x, &p, eps)`).
167impl<'a> From<&'a Param> for Option<&'a Array> {
168    fn from(p: &'a Param) -> Self {
169        Some(&p.value)
170    }
171}
172
173impl std::fmt::Display for Param {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        std::fmt::Display::fmt(&self.value, f)
176    }
177}
178
179/// Operators with a [`Param`] on either side, delegating to the arrays' (no blanket over
180/// `AsRef<Array>`, which would overlap the weak-scalar operators).
181macro_rules! param_operators {
182    ($($trait:ident $method:ident),*) => {$(
183        impl std::ops::$trait<&Param> for &Array {
184            type Output = Array;
185            #[track_caller]
186            fn $method(self, rhs: &Param) -> Array {
187                std::ops::$trait::$method(self, &rhs.value)
188            }
189        }
190        impl std::ops::$trait<Param> for &Array {
191            type Output = Array;
192            #[track_caller]
193            fn $method(self, rhs: Param) -> Array {
194                std::ops::$trait::$method(self, &rhs.value)
195            }
196        }
197        impl std::ops::$trait<&Param> for Array {
198            type Output = Array;
199            #[track_caller]
200            fn $method(self, rhs: &Param) -> Array {
201                std::ops::$trait::$method(&self, &rhs.value)
202            }
203        }
204        impl std::ops::$trait<Param> for Array {
205            type Output = Array;
206            #[track_caller]
207            fn $method(self, rhs: Param) -> Array {
208                std::ops::$trait::$method(&self, &rhs.value)
209            }
210        }
211        impl std::ops::$trait<&Array> for &Param {
212            type Output = Array;
213            #[track_caller]
214            fn $method(self, rhs: &Array) -> Array {
215                std::ops::$trait::$method(&self.value, rhs)
216            }
217        }
218        impl std::ops::$trait<Array> for &Param {
219            type Output = Array;
220            #[track_caller]
221            fn $method(self, rhs: Array) -> Array {
222                std::ops::$trait::$method(&self.value, &rhs)
223            }
224        }
225        impl std::ops::$trait<&Array> for Param {
226            type Output = Array;
227            #[track_caller]
228            fn $method(self, rhs: &Array) -> Array {
229                std::ops::$trait::$method(&self.value, rhs)
230            }
231        }
232        impl std::ops::$trait<Array> for Param {
233            type Output = Array;
234            #[track_caller]
235            fn $method(self, rhs: Array) -> Array {
236                std::ops::$trait::$method(&self.value, &rhs)
237            }
238        }
239        impl std::ops::$trait<&Param> for &Param {
240            type Output = Array;
241            #[track_caller]
242            fn $method(self, rhs: &Param) -> Array {
243                std::ops::$trait::$method(&self.value, &rhs.value)
244            }
245        }
246        impl std::ops::$trait<Param> for Param {
247            type Output = Array;
248            #[track_caller]
249            fn $method(self, rhs: Param) -> Array {
250                std::ops::$trait::$method(&self.value, &rhs.value)
251            }
252        }
253        impl<S: ScalarOperand> std::ops::$trait<S> for &Param {
254            type Output = Array;
255            #[track_caller]
256            fn $method(self, rhs: S) -> Array {
257                std::ops::$trait::$method(&self.value, rhs)
258            }
259        }
260        impl<S: ScalarOperand> std::ops::$trait<S> for Param {
261            type Output = Array;
262            #[track_caller]
263            fn $method(self, rhs: S) -> Array {
264                std::ops::$trait::$method(&self.value, rhs)
265            }
266        }
267    )*};
268}
269
270param_operators!(Add add, Sub sub, Mul mul, Div div);
271
272impl std::ops::Neg for &Param {
273    type Output = Array;
274    #[track_caller]
275    fn neg(self) -> Array {
276        -&self.value
277    }
278}
279
280// ── Static fields ───────────────────────────────────────────────────────────
281
282/// A value hashed into a [`TreeDef`]: the type of a `#[tree(skip)]` field (a
283/// configuration, a hyperparameter). A different value is a different structure, so a
284/// compiled function retraces instead of replaying a stale constant.
285///
286/// Implemented for integers, `bool`, `char`, `()`, strings, `f32`/`f64` (by bit pattern:
287/// `-0.0` differs from `0.0`), [`DType`], [`Device`], `Duration` and `Option`, `Vec`,
288/// `Box`, `Arc`, arrays and tuples (up to 8) of them; `#[derive(Static)]` implements it for
289/// user types. (Not a blanket over `Hash`: that would rule out the float impls.)
290#[diagnostic::on_unimplemented(
291    message = "`{Self}` is not `Static`, so a `#[tree(skip)]` field of this type cannot be hashed into the tree's structure",
292    note = "derive `Static` for it (`#[derive(Static)]`), or mark the field `#[tree(skip, static_unchecked)]` (its value is then baked into compiled traces)"
293)]
294pub trait Static: 'static {
295    /// Feeds the value to `h`.
296    fn hash_static(&self, h: &mut dyn Hasher);
297}
298
299macro_rules! static_by_hash {
300    ($($t:ty),*) => {$(
301        impl Static for $t {
302            fn hash_static(&self, mut h: &mut dyn Hasher) {
303                Hash::hash(self, &mut h);
304            }
305        }
306    )*};
307}
308
309static_by_hash!(
310    u8,
311    u16,
312    u32,
313    u64,
314    u128,
315    usize,
316    i8,
317    i16,
318    i32,
319    i64,
320    i128,
321    isize,
322    bool,
323    char,
324    (),
325    String,
326    &'static str,
327    DType,
328    Device,
329    Duration
330);
331
332impl Static for f32 {
333    fn hash_static(&self, h: &mut dyn Hasher) {
334        h.write_u32(self.to_bits());
335    }
336}
337
338impl Static for f64 {
339    fn hash_static(&self, h: &mut dyn Hasher) {
340        h.write_u64(self.to_bits());
341    }
342}
343
344impl<T: Static> Static for Option<T> {
345    fn hash_static(&self, h: &mut dyn Hasher) {
346        match self {
347            None => h.write_u8(0),
348            Some(v) => {
349                h.write_u8(1);
350                v.hash_static(h);
351            }
352        }
353    }
354}
355
356impl<T: Static> Static for Vec<T> {
357    fn hash_static(&self, h: &mut dyn Hasher) {
358        h.write_usize(self.len());
359        for v in self {
360            v.hash_static(h);
361        }
362    }
363}
364
365impl<T: Static, const N: usize> Static for [T; N] {
366    fn hash_static(&self, h: &mut dyn Hasher) {
367        for v in self {
368            v.hash_static(h);
369        }
370    }
371}
372
373impl<T: Static + ?Sized> Static for Box<T> {
374    fn hash_static(&self, h: &mut dyn Hasher) {
375        (**self).hash_static(h);
376    }
377}
378
379impl<T: Static + ?Sized> Static for Arc<T> {
380    fn hash_static(&self, h: &mut dyn Hasher) {
381        (**self).hash_static(h);
382    }
383}
384
385impl<T: 'static> Static for PhantomData<T> {
386    fn hash_static(&self, _: &mut dyn Hasher) {}
387}
388
389macro_rules! static_tuples {
390    ($(($($t:ident $i:tt),+)),*) => {$(
391        impl<$($t: Static),+> Static for ($($t,)+) {
392            fn hash_static(&self, h: &mut dyn Hasher) {
393                $(self.$i.hash_static(h);)+
394            }
395        }
396    )*};
397}
398
399static_tuples!(
400    (A 0),
401    (A 0, B 1),
402    (A 0, B 1, C 2),
403    (A 0, B 1, C 2, D 3),
404    (A 0, B 1, C 2, D 3, E 4),
405    (A 0, B 1, C 2, D 3, E 4, F 5),
406    (A 0, B 1, C 2, D 3, E 4, F 5, G 6),
407    (A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7)
408);
409
410// ── Visitors ────────────────────────────────────────────────────────────────
411
412/// Receives what a [`Visit`] reports, with full paths. Every method but [`Visitor::leaf`]
413/// has an empty default.
414pub trait Visitor {
415    /// A leaf: its array, kind and whether it is trainable.
416    fn leaf(&mut self, path: &str, array: &Array, kind: LeafKind, trainable: bool);
417    /// Whether the visitor reads paths (`true` by default); without them, every path it
418    /// receives is empty and the visit builds none (faster).
419    fn paths(&self) -> bool {
420        true
421    }
422    /// A child tree of type `type_name` starts at `path` (`name` is its own segment; empty
423    /// for transparent wrappers such as `Option`).
424    fn enter(&mut self, path: &str, name: &str, type_name: &'static str) {
425        let _ = (path, name, type_name);
426    }
427    /// The child tree entered last at `path` ends.
428    fn exit(&mut self, path: &str) {
429        let _ = path;
430    }
431    /// An enum's active variant (or `Option`'s: 0 for `None`, 1 for `Some`).
432    fn variant(&mut self, path: &str, index: u32) {
433        let _ = (path, index);
434    }
435    /// A mode flag (train/eval).
436    fn mode(&mut self, path: &str, training: bool) {
437        let _ = (path, training);
438    }
439    /// A static field (a hashed `#[tree(skip)]` field).
440    fn static_field(&mut self, path: &str, value: &dyn Static) {
441        let _ = (path, value);
442    }
443}
444
445/// Appends `name` to `path` (`.`-joined; an empty name is transparent) and returns the
446/// previous length.
447fn push(path: &mut String, name: &str) -> usize {
448    let len = path.len();
449    if !name.is_empty() {
450        if !path.is_empty() {
451            path.push('.');
452        }
453        path.push_str(name);
454    }
455    len
456}
457
458/// The read-only visitor that [`Leaves::visit`] reports to: it builds paths and dispatches
459/// leaves, child trees, enum variants, mode flags and static fields to a [`Visitor`].
460pub struct Visit<'a> {
461    path: String,
462    paths: bool,
463    visitor: &'a mut dyn Visitor,
464}
465
466impl<'a> Visit<'a> {
467    /// A visit reporting to `visitor`, starting at the root (empty path).
468    pub fn new(visitor: &'a mut dyn Visitor) -> Self {
469        Self {
470            path: String::new(),
471            paths: visitor.paths(),
472            visitor,
473        }
474    }
475
476    fn push(&mut self, name: &str) -> usize {
477        if self.paths {
478            push(&mut self.path, name)
479        } else {
480            0
481        }
482    }
483
484    /// The current path.
485    pub fn path(&self) -> &str {
486        &self.path
487    }
488
489    fn leaf(&mut self, name: &str, a: &Array, kind: LeafKind, trainable: bool) {
490        let len = self.push(name);
491        self.visitor.leaf(&self.path, a, kind, trainable);
492        self.path.truncate(len);
493    }
494
495    /// A plain array leaf (always differentiable).
496    pub fn array(&mut self, name: &str, a: &Array) {
497        self.leaf(name, a, LeafKind::Array, true);
498    }
499
500    /// A parameter leaf (trainable unless frozen).
501    pub fn param(&mut self, name: &str, p: &Param) {
502        self.leaf(name, &p.value, LeafKind::Param, !p.frozen);
503    }
504
505    /// A buffer leaf (never trainable): an [`Array`], or an `Option<Array>` that is a leaf
506    /// when present.
507    pub fn buffer(&mut self, name: &str, a: &(impl BufferLeaf + ?Sized)) {
508        if let Some(a) = a.buffer_array() {
509            self.leaf(name, a, LeafKind::Buffer, false);
510        }
511    }
512
513    /// A child tree named `name` (empty: transparent).
514    pub fn child<T: Leaves + ?Sized>(&mut self, name: &str, t: &T) {
515        let len = self.push(name);
516        self.visitor
517            .enter(&self.path, name, std::any::type_name::<T>());
518        t.visit(self);
519        self.visitor.exit(&self.path);
520        self.path.truncate(len);
521    }
522
523    /// The active variant of an enum (its index), part of the structure.
524    pub fn variant(&mut self, index: u32) {
525        self.visitor.variant(&self.path, index);
526    }
527
528    /// A mode flag (`true` in training mode), part of the structure.
529    pub fn mode(&mut self, training: bool) {
530        self.visitor.mode(&self.path, training);
531    }
532
533    /// A static field, hashed into the structure.
534    pub fn static_field(&mut self, name: &str, value: &dyn Static) {
535        let len = self.push(name);
536        self.visitor.static_field(&self.path, value);
537        self.path.truncate(len);
538    }
539}
540
541/// What a `#[tree(buffer)]` field may hold: an [`Array`], or an `Option<Array>` (a leaf when
542/// present, as MLX's optional buffers such as affine-only quantization biases).
543pub trait BufferLeaf {
544    /// The array, if present.
545    fn buffer_array(&self) -> Option<&Array>;
546    /// The array, mutably, if present.
547    fn buffer_array_mut(&mut self) -> Option<&mut Array>;
548}
549
550impl BufferLeaf for Array {
551    fn buffer_array(&self) -> Option<&Array> {
552        Some(self)
553    }
554
555    fn buffer_array_mut(&mut self) -> Option<&mut Array> {
556        Some(self)
557    }
558}
559
560impl BufferLeaf for Option<Array> {
561    fn buffer_array(&self) -> Option<&Array> {
562        self.as_ref()
563    }
564
565    fn buffer_array_mut(&mut self) -> Option<&mut Array> {
566        self.as_mut()
567    }
568}
569
570/// Receives what a [`VisitMut`] reports, with full paths.
571pub trait VisitorMut {
572    /// A leaf, mutably: its array, kind and, for a parameter, its frozen flag.
573    fn leaf(&mut self, path: &str, array: &mut Array, kind: LeafKind, frozen: Option<&mut bool>);
574    /// Whether the visitor reads paths (`true` by default); see [`Visitor::paths`].
575    fn paths(&self) -> bool {
576        true
577    }
578    /// A child tree at `path`, as `&mut dyn Any` (to find or replace submodules of a given
579    /// type); returns whether to visit inside it (the default).
580    fn child(&mut self, path: &str, child: &mut dyn Any) -> bool {
581        let _ = (path, child);
582        true
583    }
584    /// A mode flag (train/eval), mutably.
585    fn training(&mut self, path: &str, flag: &mut bool) {
586        let _ = (path, flag);
587    }
588}
589
590/// The mutable visitor that [`Tree::visit_mut`] reports to.
591pub struct VisitMut<'a> {
592    path: String,
593    paths: bool,
594    visitor: &'a mut dyn VisitorMut,
595}
596
597impl<'a> VisitMut<'a> {
598    /// A visit reporting to `visitor`, starting at the root (empty path).
599    pub fn new(visitor: &'a mut dyn VisitorMut) -> Self {
600        Self {
601            path: String::new(),
602            paths: visitor.paths(),
603            visitor,
604        }
605    }
606
607    fn push(&mut self, name: &str) -> usize {
608        if self.paths {
609            push(&mut self.path, name)
610        } else {
611            0
612        }
613    }
614
615    /// The current path.
616    pub fn path(&self) -> &str {
617        &self.path
618    }
619
620    /// A plain array leaf.
621    pub fn array(&mut self, name: &str, a: &mut Array) {
622        let len = self.push(name);
623        self.visitor.leaf(&self.path, a, LeafKind::Array, None);
624        self.path.truncate(len);
625    }
626
627    /// A parameter leaf.
628    pub fn param(&mut self, name: &str, p: &mut Param) {
629        let len = self.push(name);
630        self.visitor.leaf(
631            &self.path,
632            &mut p.value,
633            LeafKind::Param,
634            Some(&mut p.frozen),
635        );
636        self.path.truncate(len);
637    }
638
639    /// A buffer leaf: an [`Array`], or an `Option<Array>` that is a leaf when present.
640    pub fn buffer(&mut self, name: &str, a: &mut (impl BufferLeaf + ?Sized)) {
641        let Some(a) = a.buffer_array_mut() else {
642            return;
643        };
644        let len = self.push(name);
645        self.visitor.leaf(&self.path, a, LeafKind::Buffer, None);
646        self.path.truncate(len);
647    }
648
649    /// A child tree named `name` (empty: transparent).
650    pub fn child<T: Tree>(&mut self, name: &str, t: &mut T) {
651        let len = self.push(name);
652        if self.visitor.child(&self.path, t) {
653            t.visit_mut(self);
654        }
655        self.path.truncate(len);
656    }
657
658    /// A mode flag (`true` in training mode).
659    pub fn training(&mut self, flag: &mut bool) {
660        self.visitor.training(&self.path, flag);
661    }
662}
663
664// ── The traits ──────────────────────────────────────────────────────────────
665
666/// Anything whose array leaves can be visited in a fixed order with their paths: owned
667/// trees and borrowed views (`&T`, `(&A, &B)`, `[T]`). Object-safe.
668#[diagnostic::on_unimplemented(
669    message = "`{Self}` is not a tree of arrays",
670    note = "derive `Tree` for it (`#[derive(Clone, Tree)]`), or use a tuple, `Vec`, map or `Option` of arrays"
671)]
672pub trait Leaves {
673    /// Reports every leaf (and child tree, enum variant, mode flag and static field) to
674    /// `v`, in order.
675    fn visit(&self, v: &mut Visit<'_>);
676}
677
678/// An owned tree of arrays: rebuilt with new leaves of the same structure, mutated in place.
679///
680/// Implemented for [`Array`], [`Param`], `()`, `Option<T>`, `Box<T>`, `Vec<T>`, `[T; N]`,
681/// tuples up to 8 elements, `BTreeMap<String, T>` and `HashMap<String, T>` (visited in
682/// sorted key order), and user types with `#[derive(Tree)]`.
683#[diagnostic::on_unimplemented(
684    message = "`{Self}` is not a tree of arrays",
685    note = "derive `Tree` for it (`#[derive(Clone, Tree)]`), or use a tuple, `Vec`, map or `Option` of arrays"
686)]
687pub trait Tree: Leaves + Clone + 'static {
688    /// Reports every leaf (and child tree and mode flag) to `v` mutably, in the order of
689    /// [`Leaves::visit`].
690    fn visit_mut(&mut self, v: &mut VisitMut<'_>);
691
692    /// Calls `f(path, leaf)` for every leaf, in order.
693    fn for_each(&self, mut f: impl FnMut(&str, &Array)) {
694        struct ForEach<'f>(&'f mut dyn FnMut(&str, &Array));
695        impl Visitor for ForEach<'_> {
696            fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
697                (self.0)(path, a);
698            }
699        }
700        self.visit(&mut Visit::new(&mut ForEach(&mut f)));
701    }
702
703    /// The tree with every leaf replaced by `f(path, leaf)` (same structure; parameters keep
704    /// their frozen flag).
705    fn map(&self, mut f: impl FnMut(&str, &Array) -> Array) -> Self {
706        struct Map<'f>(&'f mut dyn FnMut(&str, &Array) -> Array);
707        impl VisitorMut for Map<'_> {
708            fn leaf(&mut self, path: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
709                *a = (self.0)(path, a);
710            }
711        }
712        let mut out = self.clone();
713        out.visit_mut(&mut VisitMut::new(&mut Map(&mut f)));
714        out
715    }
716
717    /// The leaves, in order.
718    fn leaves(&self) -> Vec<Array> {
719        collect_leaves(self)
720    }
721
722    /// The leaves' paths, in order.
723    fn paths(&self) -> Vec<String> {
724        let mut out = Vec::new();
725        self.for_each(|p, _| out.push(p.to_owned()));
726        out
727    }
728
729    /// The leaves with their paths, in order (MLX's `tree_flatten`).
730    fn flatten(&self) -> Vec<(String, Array)> {
731        let mut out = Vec::new();
732        self.for_each(|p, a| out.push((p.to_owned(), a.clone())));
733        out
734    }
735
736    /// A tree of this structure (`self` is the template) with `leaves` in order: exactly
737    /// [`Tree::len`] of them, else an error naming both counts.
738    fn unflatten(&self, leaves: impl IntoIterator<Item = Array>) -> Result<Self> {
739        struct Assign<I>(I, usize, usize);
740        impl<I: Iterator<Item = Array>> VisitorMut for Assign<I> {
741            fn leaf(&mut self, _: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
742                self.2 += 1;
743                if let Some(next) = self.0.next() {
744                    *a = next;
745                    self.1 += 1;
746                }
747            }
748            fn paths(&self) -> bool {
749                false
750            }
751        }
752        let mut out = self.clone();
753        let mut assign = Assign(leaves.into_iter(), 0, 0);
754        out.visit_mut(&mut VisitMut::new(&mut assign));
755        let (given, expected) = (assign.1 + assign.0.count(), assign.2);
756        if given != expected {
757            return Err(Error::InvalidArgument(format!(
758                "unflatten: {given} leaves for a tree of {expected}"
759            )));
760        }
761        Ok(out)
762    }
763
764    /// Replaces leaves by path. [`LoadMode::Strict`] requires exactly the tree's paths with
765    /// matching shapes; [`LoadMode::Partial`] assigns the matching names and ignores
766    /// unknown ones (shapes must still match). Loaded dtypes are kept. On any problem the
767    /// error lists every one (missing, unknown and mismatched names, sorted) and the tree is
768    /// unchanged. Duplicate paths (in the tree or in `named`) are an error.
769    fn load_flat(
770        &mut self,
771        named: impl IntoIterator<Item = (String, Array)>,
772        mode: LoadMode,
773    ) -> Result<()> {
774        load_named(self, named.into_iter().collect(), mode)
775    }
776
777    /// The number of leaves.
778    fn len(&self) -> usize {
779        struct Count(usize);
780        impl Visitor for Count {
781            fn leaf(&mut self, _: &str, _: &Array, _: LeafKind, _: bool) {
782                self.0 += 1;
783            }
784            fn paths(&self) -> bool {
785                false
786            }
787        }
788        let mut c = Count(0);
789        self.visit(&mut Visit::new(&mut c));
790        c.0
791    }
792
793    /// Whether the tree has no leaves.
794    fn is_empty(&self) -> bool {
795        self.len() == 0
796    }
797
798    /// The tree's structure.
799    fn def(&self) -> TreeDef {
800        TreeDef::of(self)
801    }
802}
803
804/// The leaves of any [`Leaves`] value, in order.
805pub fn collect_leaves<T: Leaves + ?Sized>(tree: &T) -> Vec<Array> {
806    struct Collect(Vec<Array>);
807    impl Visitor for Collect {
808        fn leaf(&mut self, _: &str, a: &Array, _: LeafKind, _: bool) {
809            self.0.push(a.clone());
810        }
811        fn paths(&self) -> bool {
812            false
813        }
814    }
815    let mut c = Collect(Vec::new());
816    tree.visit(&mut Visit::new(&mut c));
817    c.0
818}
819
820/// How [`Tree::load_flat`] treats names it does not find.
821#[non_exhaustive]
822#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
823pub enum LoadMode {
824    /// Every path must be given, and nothing else.
825    #[default]
826    Strict,
827    /// Matching names are assigned; missing paths keep their values, unknown names are
828    /// ignored.
829    Partial,
830}
831
832fn load_named<T: Tree>(tree: &mut T, named: Vec<(String, Array)>, mode: LoadMode) -> Result<()> {
833    let mut given: HashMap<String, Array> = HashMap::with_capacity(named.len());
834    let mut problems: Vec<(u8, String)> = Vec::new();
835    for (name, a) in named {
836        if given.contains_key(&name) {
837            return Err(Error::InvalidArgument(format!(
838                "load_flat: {name:?} is given twice"
839            )));
840        }
841        given.insert(name, a);
842    }
843    let mut shapes: Vec<(String, Vec<i32>)> = Vec::new();
844    {
845        struct Shapes<'a>(&'a mut Vec<(String, Vec<i32>)>);
846        impl Visitor for Shapes<'_> {
847            fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
848                self.0.push((path.to_owned(), a.shape().to_vec()));
849            }
850        }
851        tree.visit(&mut Visit::new(&mut Shapes(&mut shapes)));
852    }
853    let mut seen = std::collections::HashSet::new();
854    for (path, shape) in &shapes {
855        if !seen.insert(path.as_str()) {
856            return Err(Error::InvalidArgument(format!(
857                "load_flat: the tree has the path {path:?} twice"
858            )));
859        }
860        match given.get(path) {
861            Some(a) if a.shape() != shape.as_slice() => problems.push((
862                2,
863                format!("shape: {path} {:?}, expected {shape:?}", a.shape()),
864            )),
865            Some(_) => {}
866            None if mode == LoadMode::Strict => problems.push((0, format!("missing: {path}"))),
867            None => {}
868        }
869    }
870    if mode == LoadMode::Strict {
871        for name in given.keys() {
872            if !seen.contains(name.as_str()) {
873                problems.push((1, format!("unknown: {name}")));
874            }
875        }
876    }
877    if !problems.is_empty() {
878        problems.sort();
879        let n = problems.len();
880        let mut msg = format!("load_flat: {n} problem{}", if n == 1 { "" } else { "s" });
881        for (_, p) in problems {
882            msg.push_str("\n  ");
883            msg.push_str(&p);
884        }
885        return Err(Error::InvalidArgument(msg));
886    }
887    struct Assign<'a>(&'a mut HashMap<String, Array>);
888    impl VisitorMut for Assign<'_> {
889        fn leaf(&mut self, path: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
890            if let Some(v) = self.0.remove(path) {
891                *a = v;
892            }
893        }
894    }
895    tree.visit_mut(&mut VisitMut::new(&mut Assign(&mut given)));
896    Ok(())
897}
898
899// ── Structure ───────────────────────────────────────────────────────────────
900
901#[derive(Clone, PartialEq, Eq, Hash, Debug)]
902enum DefNode {
903    Leaf {
904        path: String,
905        kind: LeafKind,
906        trainable: bool,
907    },
908    Variant {
909        path: String,
910        index: u32,
911    },
912    Mode {
913        path: String,
914        training: bool,
915    },
916}
917
918/// The structure of a tree: its root type, the ordered `(path, kind, trainable)` of every
919/// leaf, the active enum variants, the mode flags and a hash of the static fields. Equal
920/// structures have equal `TreeDef`s (see the [module documentation](self)).
921#[derive(Clone, PartialEq, Eq, Hash, Debug)]
922pub struct TreeDef {
923    root: TypeId,
924    root_name: &'static str,
925    nodes: Vec<DefNode>,
926    statics: u64,
927}
928
929impl TreeDef {
930    /// The structure of `tree`.
931    pub fn of<T: Leaves + ?Sized + 'static>(tree: &T) -> Self {
932        struct Collect {
933            nodes: Vec<DefNode>,
934            hasher: std::hash::DefaultHasher,
935        }
936        impl Visitor for Collect {
937            fn leaf(&mut self, path: &str, _: &Array, kind: LeafKind, trainable: bool) {
938                self.nodes.push(DefNode::Leaf {
939                    path: path.to_owned(),
940                    kind,
941                    trainable,
942                });
943            }
944            fn variant(&mut self, path: &str, index: u32) {
945                self.nodes.push(DefNode::Variant {
946                    path: path.to_owned(),
947                    index,
948                });
949            }
950            fn mode(&mut self, path: &str, training: bool) {
951                self.nodes.push(DefNode::Mode {
952                    path: path.to_owned(),
953                    training,
954                });
955            }
956            fn static_field(&mut self, path: &str, value: &dyn Static) {
957                self.hasher.write(path.as_bytes());
958                self.hasher.write_u8(0xff);
959                value.hash_static(&mut self.hasher);
960            }
961        }
962        let mut c = Collect {
963            nodes: Vec::new(),
964            hasher: std::hash::DefaultHasher::new(),
965        };
966        tree.visit(&mut Visit::new(&mut c));
967        Self {
968            root: TypeId::of::<T>(),
969            root_name: std::any::type_name::<T>(),
970            nodes: c.nodes,
971            statics: c.hasher.finish(),
972        }
973    }
974
975    /// The name of the root type.
976    pub fn type_name(&self) -> &'static str {
977        self.root_name
978    }
979
980    /// The leaves: path, kind and whether trainable, in order.
981    pub fn leaves(&self) -> impl Iterator<Item = (&str, LeafKind, bool)> {
982        self.nodes.iter().filter_map(|n| match n {
983            DefNode::Leaf {
984                path,
985                kind,
986                trainable,
987            } => Some((path.as_str(), *kind, *trainable)),
988            _ => None,
989        })
990    }
991
992    /// The number of leaves.
993    pub fn len(&self) -> usize {
994        self.leaves().count()
995    }
996
997    /// Whether the tree has no leaves.
998    pub fn is_empty(&self) -> bool {
999        self.len() == 0
1000    }
1001
1002    /// The first difference from `other`, described for error messages (`None` if equal).
1003    pub fn difference(&self, other: &TreeDef) -> Option<String> {
1004        if self == other {
1005            return None;
1006        }
1007        if self.root != other.root {
1008            return Some(format!("type {} vs {}", self.root_name, other.root_name));
1009        }
1010        let name = |n: &DefNode| match n {
1011            DefNode::Leaf {
1012                path,
1013                kind,
1014                trainable,
1015            } => format!(
1016                "leaf {path:?} ({kind:?}{})",
1017                if *trainable { "" } else { ", not trainable" }
1018            ),
1019            DefNode::Variant { path, index } => format!("variant {index} at {path:?}"),
1020            DefNode::Mode { path, training } => {
1021                format!(
1022                    "{} mode at {path:?}",
1023                    if *training { "train" } else { "eval" }
1024                )
1025            }
1026        };
1027        for i in 0..self.nodes.len().max(other.nodes.len()) {
1028            match (self.nodes.get(i), other.nodes.get(i)) {
1029                (Some(a), Some(b)) if a == b => {}
1030                (Some(a), Some(b)) => return Some(format!("{} vs {}", name(a), name(b))),
1031                (Some(a), None) => return Some(format!("{} vs nothing", name(a))),
1032                (None, Some(b)) => return Some(format!("nothing vs {}", name(b))),
1033                (None, None) => {}
1034            }
1035        }
1036        Some("different static fields (configuration or hyperparameters)".into())
1037    }
1038}
1039
1040// ── Zero gradients ──────────────────────────────────────────────────────────
1041
1042/// A zero of `like`'s dtype broadcast to its shape: a stride-0 view of one element, so
1043/// evaluating it allocates no full-size memory (gradients of frozen leaves).
1044pub(crate) fn broadcast_zero(like: &Array) -> Array {
1045    Array::from(0u8)
1046        .astype(like.dtype())
1047        .broadcast_to(like.shape())
1048}
1049
1050/// Whether a leaf of this dtype can be differentiated.
1051pub(crate) fn differentiable(dtype: DType) -> bool {
1052    matches!(dtype.category(), Category::Float | Category::Complex)
1053}
1054
1055// ── Impls ───────────────────────────────────────────────────────────────────
1056
1057impl Leaves for Array {
1058    fn visit(&self, v: &mut Visit<'_>) {
1059        v.array("", self);
1060    }
1061}
1062
1063impl Tree for Array {
1064    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1065        v.array("", self);
1066    }
1067}
1068
1069impl Leaves for Param {
1070    fn visit(&self, v: &mut Visit<'_>) {
1071        v.param("", self);
1072    }
1073}
1074
1075impl Tree for Param {
1076    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1077        v.param("", self);
1078    }
1079}
1080
1081impl Leaves for () {
1082    fn visit(&self, _: &mut Visit<'_>) {}
1083}
1084
1085impl Tree for () {
1086    fn visit_mut(&mut self, _: &mut VisitMut<'_>) {}
1087}
1088
1089impl<T: ?Sized> Leaves for PhantomData<T> {
1090    fn visit(&self, _: &mut Visit<'_>) {}
1091}
1092
1093impl<T: ?Sized + 'static> Tree for PhantomData<T> {
1094    fn visit_mut(&mut self, _: &mut VisitMut<'_>) {}
1095}
1096
1097impl<T: Leaves + ?Sized> Leaves for &T {
1098    fn visit(&self, v: &mut Visit<'_>) {
1099        (**self).visit(v);
1100    }
1101}
1102
1103impl<T: Leaves + ?Sized> Leaves for &mut T {
1104    fn visit(&self, v: &mut Visit<'_>) {
1105        (**self).visit(v);
1106    }
1107}
1108
1109impl<T: Leaves> Leaves for Option<T> {
1110    fn visit(&self, v: &mut Visit<'_>) {
1111        match self {
1112            None => v.variant(0),
1113            Some(x) => {
1114                v.variant(1);
1115                v.child("", x);
1116            }
1117        }
1118    }
1119}
1120
1121impl<T: Tree> Tree for Option<T> {
1122    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1123        if let Some(x) = self {
1124            v.child("", x);
1125        }
1126    }
1127}
1128
1129impl<T: Leaves> Leaves for Box<T> {
1130    fn visit(&self, v: &mut Visit<'_>) {
1131        v.child("", &**self);
1132    }
1133}
1134
1135impl<T: Tree> Tree for Box<T> {
1136    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1137        v.child("", &mut **self);
1138    }
1139}
1140
1141impl<T: Leaves> Leaves for [T] {
1142    fn visit(&self, v: &mut Visit<'_>) {
1143        for (i, x) in self.iter().enumerate() {
1144            v.child(&i.to_string(), x);
1145        }
1146    }
1147}
1148
1149impl<T: Leaves> Leaves for Vec<T> {
1150    fn visit(&self, v: &mut Visit<'_>) {
1151        self.as_slice().visit(v);
1152    }
1153}
1154
1155impl<T: Tree> Tree for Vec<T> {
1156    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1157        for (i, x) in self.iter_mut().enumerate() {
1158            v.child(&i.to_string(), x);
1159        }
1160    }
1161}
1162
1163impl<T: Leaves, const N: usize> Leaves for [T; N] {
1164    fn visit(&self, v: &mut Visit<'_>) {
1165        self.as_slice().visit(v);
1166    }
1167}
1168
1169impl<T: Tree, const N: usize> Tree for [T; N] {
1170    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1171        for (i, x) in self.iter_mut().enumerate() {
1172            v.child(&i.to_string(), x);
1173        }
1174    }
1175}
1176
1177impl<T: Leaves> Leaves for BTreeMap<String, T> {
1178    fn visit(&self, v: &mut Visit<'_>) {
1179        for (k, x) in self {
1180            v.child(k, x);
1181        }
1182    }
1183}
1184
1185impl<T: Tree> Tree for BTreeMap<String, T> {
1186    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1187        for (k, x) in self.iter_mut() {
1188            v.child(k, x);
1189        }
1190    }
1191}
1192
1193impl<T: Leaves, S: 'static> Leaves for HashMap<String, T, S> {
1194    fn visit(&self, v: &mut Visit<'_>) {
1195        let mut entries: Vec<(&String, &T)> = self.iter().collect();
1196        entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
1197        for (k, x) in entries {
1198            v.child(k, x);
1199        }
1200    }
1201}
1202
1203impl<T: Tree, S: std::hash::BuildHasher + Clone + 'static> Tree for HashMap<String, T, S> {
1204    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1205        let mut entries: Vec<(&String, &mut T)> = self.iter_mut().collect();
1206        entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
1207        for (k, x) in entries {
1208            v.child(k, x);
1209        }
1210    }
1211}
1212
1213macro_rules! tuples {
1214    ($(($($t:ident $i:tt),+)),*) => {$(
1215        impl<$($t: Leaves),+> Leaves for ($($t,)+) {
1216            fn visit(&self, v: &mut Visit<'_>) {
1217                $(v.child(stringify!($i), &self.$i);)+
1218            }
1219        }
1220        impl<$($t: Tree),+> Tree for ($($t,)+) {
1221            fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1222                $(v.child(stringify!($i), &mut self.$i);)+
1223            }
1224        }
1225    )*};
1226}
1227
1228tuples!(
1229    (A 0),
1230    (A 0, B 1),
1231    (A 0, B 1, C 2),
1232    (A 0, B 1, C 2, D 3),
1233    (A 0, B 1, C 2, D 3, E 4),
1234    (A 0, B 1, C 2, D 3, E 4, F 5),
1235    (A 0, B 1, C 2, D 3, E 4, F 5, G 6),
1236    (A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7)
1237);
1238
1239/// What `#[derive(Tree)]`, `#[derive(Module)]` and `#[derive(Static)]` generate code
1240/// against (re-exported as `nuvec::__derive`). Not a public API.
1241#[doc(hidden)]
1242pub mod __derive {
1243    pub use std::any::TypeId;
1244    pub use std::clone::Clone;
1245    pub use std::hash::Hasher;
1246    pub use std::marker::PhantomData;
1247
1248    pub use super::{LeafKind, Leaves, Param, Static, Tree, Visit, VisitMut};
1249    pub use crate::Array;
1250
1251    /// Fails to compile unless `T` is [`Static`] (the field's span carries the error).
1252    pub const fn assert_static<T: Static + ?Sized>() {}
1253}