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).
506    pub fn buffer(&mut self, name: &str, a: &Array) {
507        self.leaf(name, a, LeafKind::Buffer, false);
508    }
509
510    /// A child tree named `name` (empty: transparent).
511    pub fn child<T: Leaves + ?Sized>(&mut self, name: &str, t: &T) {
512        let len = self.push(name);
513        self.visitor
514            .enter(&self.path, name, std::any::type_name::<T>());
515        t.visit(self);
516        self.visitor.exit(&self.path);
517        self.path.truncate(len);
518    }
519
520    /// The active variant of an enum (its index), part of the structure.
521    pub fn variant(&mut self, index: u32) {
522        self.visitor.variant(&self.path, index);
523    }
524
525    /// A mode flag (`true` in training mode), part of the structure.
526    pub fn mode(&mut self, training: bool) {
527        self.visitor.mode(&self.path, training);
528    }
529
530    /// A static field, hashed into the structure.
531    pub fn static_field(&mut self, name: &str, value: &dyn Static) {
532        let len = self.push(name);
533        self.visitor.static_field(&self.path, value);
534        self.path.truncate(len);
535    }
536}
537
538/// Receives what a [`VisitMut`] reports, with full paths.
539pub trait VisitorMut {
540    /// A leaf, mutably: its array, kind and, for a parameter, its frozen flag.
541    fn leaf(&mut self, path: &str, array: &mut Array, kind: LeafKind, frozen: Option<&mut bool>);
542    /// Whether the visitor reads paths (`true` by default); see [`Visitor::paths`].
543    fn paths(&self) -> bool {
544        true
545    }
546    /// A child tree at `path`, as `&mut dyn Any` (to find or replace submodules of a given
547    /// type); returns whether to visit inside it (the default).
548    fn child(&mut self, path: &str, child: &mut dyn Any) -> bool {
549        let _ = (path, child);
550        true
551    }
552    /// A mode flag (train/eval), mutably.
553    fn training(&mut self, path: &str, flag: &mut bool) {
554        let _ = (path, flag);
555    }
556}
557
558/// The mutable visitor that [`Tree::visit_mut`] reports to.
559pub struct VisitMut<'a> {
560    path: String,
561    paths: bool,
562    visitor: &'a mut dyn VisitorMut,
563}
564
565impl<'a> VisitMut<'a> {
566    /// A visit reporting to `visitor`, starting at the root (empty path).
567    pub fn new(visitor: &'a mut dyn VisitorMut) -> Self {
568        Self {
569            path: String::new(),
570            paths: visitor.paths(),
571            visitor,
572        }
573    }
574
575    fn push(&mut self, name: &str) -> usize {
576        if self.paths {
577            push(&mut self.path, name)
578        } else {
579            0
580        }
581    }
582
583    /// The current path.
584    pub fn path(&self) -> &str {
585        &self.path
586    }
587
588    /// A plain array leaf.
589    pub fn array(&mut self, name: &str, a: &mut Array) {
590        let len = self.push(name);
591        self.visitor.leaf(&self.path, a, LeafKind::Array, None);
592        self.path.truncate(len);
593    }
594
595    /// A parameter leaf.
596    pub fn param(&mut self, name: &str, p: &mut Param) {
597        let len = self.push(name);
598        self.visitor.leaf(
599            &self.path,
600            &mut p.value,
601            LeafKind::Param,
602            Some(&mut p.frozen),
603        );
604        self.path.truncate(len);
605    }
606
607    /// A buffer leaf.
608    pub fn buffer(&mut self, name: &str, a: &mut Array) {
609        let len = self.push(name);
610        self.visitor.leaf(&self.path, a, LeafKind::Buffer, None);
611        self.path.truncate(len);
612    }
613
614    /// A child tree named `name` (empty: transparent).
615    pub fn child<T: Tree>(&mut self, name: &str, t: &mut T) {
616        let len = self.push(name);
617        if self.visitor.child(&self.path, t) {
618            t.visit_mut(self);
619        }
620        self.path.truncate(len);
621    }
622
623    /// A mode flag (`true` in training mode).
624    pub fn training(&mut self, flag: &mut bool) {
625        self.visitor.training(&self.path, flag);
626    }
627}
628
629// ── The traits ──────────────────────────────────────────────────────────────
630
631/// Anything whose array leaves can be visited in a fixed order with their paths: owned
632/// trees and borrowed views (`&T`, `(&A, &B)`, `[T]`). Object-safe.
633#[diagnostic::on_unimplemented(
634    message = "`{Self}` is not a tree of arrays",
635    note = "derive `Tree` for it (`#[derive(Clone, Tree)]`), or use a tuple, `Vec`, map or `Option` of arrays"
636)]
637pub trait Leaves {
638    /// Reports every leaf (and child tree, enum variant, mode flag and static field) to
639    /// `v`, in order.
640    fn visit(&self, v: &mut Visit<'_>);
641}
642
643/// An owned tree of arrays: rebuilt with new leaves of the same structure, mutated in place.
644///
645/// Implemented for [`Array`], [`Param`], `()`, `Option<T>`, `Box<T>`, `Vec<T>`, `[T; N]`,
646/// tuples up to 8 elements, `BTreeMap<String, T>` and `HashMap<String, T>` (visited in
647/// sorted key order), and user types with `#[derive(Tree)]`.
648#[diagnostic::on_unimplemented(
649    message = "`{Self}` is not a tree of arrays",
650    note = "derive `Tree` for it (`#[derive(Clone, Tree)]`), or use a tuple, `Vec`, map or `Option` of arrays"
651)]
652pub trait Tree: Leaves + Clone + 'static {
653    /// Reports every leaf (and child tree and mode flag) to `v` mutably, in the order of
654    /// [`Leaves::visit`].
655    fn visit_mut(&mut self, v: &mut VisitMut<'_>);
656
657    /// Calls `f(path, leaf)` for every leaf, in order.
658    fn for_each(&self, mut f: impl FnMut(&str, &Array)) {
659        struct ForEach<'f>(&'f mut dyn FnMut(&str, &Array));
660        impl Visitor for ForEach<'_> {
661            fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
662                (self.0)(path, a);
663            }
664        }
665        self.visit(&mut Visit::new(&mut ForEach(&mut f)));
666    }
667
668    /// The tree with every leaf replaced by `f(path, leaf)` (same structure; parameters keep
669    /// their frozen flag).
670    fn map(&self, mut f: impl FnMut(&str, &Array) -> Array) -> Self {
671        struct Map<'f>(&'f mut dyn FnMut(&str, &Array) -> Array);
672        impl VisitorMut for Map<'_> {
673            fn leaf(&mut self, path: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
674                *a = (self.0)(path, a);
675            }
676        }
677        let mut out = self.clone();
678        out.visit_mut(&mut VisitMut::new(&mut Map(&mut f)));
679        out
680    }
681
682    /// The leaves, in order.
683    fn leaves(&self) -> Vec<Array> {
684        collect_leaves(self)
685    }
686
687    /// The leaves' paths, in order.
688    fn paths(&self) -> Vec<String> {
689        let mut out = Vec::new();
690        self.for_each(|p, _| out.push(p.to_owned()));
691        out
692    }
693
694    /// The leaves with their paths, in order (MLX's `tree_flatten`).
695    fn flatten(&self) -> Vec<(String, Array)> {
696        let mut out = Vec::new();
697        self.for_each(|p, a| out.push((p.to_owned(), a.clone())));
698        out
699    }
700
701    /// A tree of this structure (`self` is the template) with `leaves` in order: exactly
702    /// [`Tree::len`] of them, else an error naming both counts.
703    fn unflatten(&self, leaves: impl IntoIterator<Item = Array>) -> Result<Self> {
704        struct Assign<I>(I, usize, usize);
705        impl<I: Iterator<Item = Array>> VisitorMut for Assign<I> {
706            fn leaf(&mut self, _: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
707                self.2 += 1;
708                if let Some(next) = self.0.next() {
709                    *a = next;
710                    self.1 += 1;
711                }
712            }
713            fn paths(&self) -> bool {
714                false
715            }
716        }
717        let mut out = self.clone();
718        let mut assign = Assign(leaves.into_iter(), 0, 0);
719        out.visit_mut(&mut VisitMut::new(&mut assign));
720        let (given, expected) = (assign.1 + assign.0.count(), assign.2);
721        if given != expected {
722            return Err(Error::InvalidArgument(format!(
723                "unflatten: {given} leaves for a tree of {expected}"
724            )));
725        }
726        Ok(out)
727    }
728
729    /// Replaces leaves by path. [`LoadMode::Strict`] requires exactly the tree's paths with
730    /// matching shapes; [`LoadMode::Partial`] assigns the matching names and ignores
731    /// unknown ones (shapes must still match). Loaded dtypes are kept. On any problem the
732    /// error lists every one (missing, unknown and mismatched names, sorted) and the tree is
733    /// unchanged. Duplicate paths (in the tree or in `named`) are an error.
734    fn load_flat(
735        &mut self,
736        named: impl IntoIterator<Item = (String, Array)>,
737        mode: LoadMode,
738    ) -> Result<()> {
739        load_named(self, named.into_iter().collect(), mode)
740    }
741
742    /// The number of leaves.
743    fn len(&self) -> usize {
744        struct Count(usize);
745        impl Visitor for Count {
746            fn leaf(&mut self, _: &str, _: &Array, _: LeafKind, _: bool) {
747                self.0 += 1;
748            }
749            fn paths(&self) -> bool {
750                false
751            }
752        }
753        let mut c = Count(0);
754        self.visit(&mut Visit::new(&mut c));
755        c.0
756    }
757
758    /// Whether the tree has no leaves.
759    fn is_empty(&self) -> bool {
760        self.len() == 0
761    }
762
763    /// The tree's structure.
764    fn def(&self) -> TreeDef {
765        TreeDef::of(self)
766    }
767}
768
769/// The leaves of any [`Leaves`] value, in order.
770pub fn collect_leaves<T: Leaves + ?Sized>(tree: &T) -> Vec<Array> {
771    struct Collect(Vec<Array>);
772    impl Visitor for Collect {
773        fn leaf(&mut self, _: &str, a: &Array, _: LeafKind, _: bool) {
774            self.0.push(a.clone());
775        }
776        fn paths(&self) -> bool {
777            false
778        }
779    }
780    let mut c = Collect(Vec::new());
781    tree.visit(&mut Visit::new(&mut c));
782    c.0
783}
784
785/// How [`Tree::load_flat`] treats names it does not find.
786#[non_exhaustive]
787#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
788pub enum LoadMode {
789    /// Every path must be given, and nothing else.
790    #[default]
791    Strict,
792    /// Matching names are assigned; missing paths keep their values, unknown names are
793    /// ignored.
794    Partial,
795}
796
797fn load_named<T: Tree>(tree: &mut T, named: Vec<(String, Array)>, mode: LoadMode) -> Result<()> {
798    let mut given: HashMap<String, Array> = HashMap::with_capacity(named.len());
799    let mut problems: Vec<(u8, String)> = Vec::new();
800    for (name, a) in named {
801        if given.contains_key(&name) {
802            return Err(Error::InvalidArgument(format!(
803                "load_flat: {name:?} is given twice"
804            )));
805        }
806        given.insert(name, a);
807    }
808    let mut shapes: Vec<(String, Vec<i32>)> = Vec::new();
809    {
810        struct Shapes<'a>(&'a mut Vec<(String, Vec<i32>)>);
811        impl Visitor for Shapes<'_> {
812            fn leaf(&mut self, path: &str, a: &Array, _: LeafKind, _: bool) {
813                self.0.push((path.to_owned(), a.shape().to_vec()));
814            }
815        }
816        tree.visit(&mut Visit::new(&mut Shapes(&mut shapes)));
817    }
818    let mut seen = std::collections::HashSet::new();
819    for (path, shape) in &shapes {
820        if !seen.insert(path.as_str()) {
821            return Err(Error::InvalidArgument(format!(
822                "load_flat: the tree has the path {path:?} twice"
823            )));
824        }
825        match given.get(path) {
826            Some(a) if a.shape() != shape.as_slice() => problems.push((
827                2,
828                format!("shape: {path} {:?}, expected {shape:?}", a.shape()),
829            )),
830            Some(_) => {}
831            None if mode == LoadMode::Strict => problems.push((0, format!("missing: {path}"))),
832            None => {}
833        }
834    }
835    if mode == LoadMode::Strict {
836        for name in given.keys() {
837            if !seen.contains(name.as_str()) {
838                problems.push((1, format!("unknown: {name}")));
839            }
840        }
841    }
842    if !problems.is_empty() {
843        problems.sort();
844        let n = problems.len();
845        let mut msg = format!("load_flat: {n} problem{}", if n == 1 { "" } else { "s" });
846        for (_, p) in problems {
847            msg.push_str("\n  ");
848            msg.push_str(&p);
849        }
850        return Err(Error::InvalidArgument(msg));
851    }
852    struct Assign<'a>(&'a mut HashMap<String, Array>);
853    impl VisitorMut for Assign<'_> {
854        fn leaf(&mut self, path: &str, a: &mut Array, _: LeafKind, _: Option<&mut bool>) {
855            if let Some(v) = self.0.remove(path) {
856                *a = v;
857            }
858        }
859    }
860    tree.visit_mut(&mut VisitMut::new(&mut Assign(&mut given)));
861    Ok(())
862}
863
864// ── Structure ───────────────────────────────────────────────────────────────
865
866#[derive(Clone, PartialEq, Eq, Hash, Debug)]
867enum DefNode {
868    Leaf {
869        path: String,
870        kind: LeafKind,
871        trainable: bool,
872    },
873    Variant {
874        path: String,
875        index: u32,
876    },
877    Mode {
878        path: String,
879        training: bool,
880    },
881}
882
883/// The structure of a tree: its root type, the ordered `(path, kind, trainable)` of every
884/// leaf, the active enum variants, the mode flags and a hash of the static fields. Equal
885/// structures have equal `TreeDef`s (see the [module documentation](self)).
886#[derive(Clone, PartialEq, Eq, Hash, Debug)]
887pub struct TreeDef {
888    root: TypeId,
889    root_name: &'static str,
890    nodes: Vec<DefNode>,
891    statics: u64,
892}
893
894impl TreeDef {
895    /// The structure of `tree`.
896    pub fn of<T: Leaves + ?Sized + 'static>(tree: &T) -> Self {
897        struct Collect {
898            nodes: Vec<DefNode>,
899            hasher: std::hash::DefaultHasher,
900        }
901        impl Visitor for Collect {
902            fn leaf(&mut self, path: &str, _: &Array, kind: LeafKind, trainable: bool) {
903                self.nodes.push(DefNode::Leaf {
904                    path: path.to_owned(),
905                    kind,
906                    trainable,
907                });
908            }
909            fn variant(&mut self, path: &str, index: u32) {
910                self.nodes.push(DefNode::Variant {
911                    path: path.to_owned(),
912                    index,
913                });
914            }
915            fn mode(&mut self, path: &str, training: bool) {
916                self.nodes.push(DefNode::Mode {
917                    path: path.to_owned(),
918                    training,
919                });
920            }
921            fn static_field(&mut self, path: &str, value: &dyn Static) {
922                self.hasher.write(path.as_bytes());
923                self.hasher.write_u8(0xff);
924                value.hash_static(&mut self.hasher);
925            }
926        }
927        let mut c = Collect {
928            nodes: Vec::new(),
929            hasher: std::hash::DefaultHasher::new(),
930        };
931        tree.visit(&mut Visit::new(&mut c));
932        Self {
933            root: TypeId::of::<T>(),
934            root_name: std::any::type_name::<T>(),
935            nodes: c.nodes,
936            statics: c.hasher.finish(),
937        }
938    }
939
940    /// The name of the root type.
941    pub fn type_name(&self) -> &'static str {
942        self.root_name
943    }
944
945    /// The leaves: path, kind and whether trainable, in order.
946    pub fn leaves(&self) -> impl Iterator<Item = (&str, LeafKind, bool)> {
947        self.nodes.iter().filter_map(|n| match n {
948            DefNode::Leaf {
949                path,
950                kind,
951                trainable,
952            } => Some((path.as_str(), *kind, *trainable)),
953            _ => None,
954        })
955    }
956
957    /// The number of leaves.
958    pub fn len(&self) -> usize {
959        self.leaves().count()
960    }
961
962    /// Whether the tree has no leaves.
963    pub fn is_empty(&self) -> bool {
964        self.len() == 0
965    }
966
967    /// The first difference from `other`, described for error messages (`None` if equal).
968    pub fn difference(&self, other: &TreeDef) -> Option<String> {
969        if self == other {
970            return None;
971        }
972        if self.root != other.root {
973            return Some(format!("type {} vs {}", self.root_name, other.root_name));
974        }
975        let name = |n: &DefNode| match n {
976            DefNode::Leaf {
977                path,
978                kind,
979                trainable,
980            } => format!(
981                "leaf {path:?} ({kind:?}{})",
982                if *trainable { "" } else { ", not trainable" }
983            ),
984            DefNode::Variant { path, index } => format!("variant {index} at {path:?}"),
985            DefNode::Mode { path, training } => {
986                format!(
987                    "{} mode at {path:?}",
988                    if *training { "train" } else { "eval" }
989                )
990            }
991        };
992        for i in 0..self.nodes.len().max(other.nodes.len()) {
993            match (self.nodes.get(i), other.nodes.get(i)) {
994                (Some(a), Some(b)) if a == b => {}
995                (Some(a), Some(b)) => return Some(format!("{} vs {}", name(a), name(b))),
996                (Some(a), None) => return Some(format!("{} vs nothing", name(a))),
997                (None, Some(b)) => return Some(format!("nothing vs {}", name(b))),
998                (None, None) => {}
999            }
1000        }
1001        Some("different static fields (configuration or hyperparameters)".into())
1002    }
1003}
1004
1005// ── Zero gradients ──────────────────────────────────────────────────────────
1006
1007/// A zero of `like`'s dtype broadcast to its shape: a stride-0 view of one element, so
1008/// evaluating it allocates no full-size memory (gradients of frozen leaves).
1009pub(crate) fn broadcast_zero(like: &Array) -> Array {
1010    Array::from(0u8)
1011        .astype(like.dtype())
1012        .broadcast_to(like.shape())
1013}
1014
1015/// Whether a leaf of this dtype can be differentiated.
1016pub(crate) fn differentiable(dtype: DType) -> bool {
1017    matches!(dtype.category(), Category::Float | Category::Complex)
1018}
1019
1020// ── Impls ───────────────────────────────────────────────────────────────────
1021
1022impl Leaves for Array {
1023    fn visit(&self, v: &mut Visit<'_>) {
1024        v.array("", self);
1025    }
1026}
1027
1028impl Tree for Array {
1029    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1030        v.array("", self);
1031    }
1032}
1033
1034impl Leaves for Param {
1035    fn visit(&self, v: &mut Visit<'_>) {
1036        v.param("", self);
1037    }
1038}
1039
1040impl Tree for Param {
1041    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1042        v.param("", self);
1043    }
1044}
1045
1046impl Leaves for () {
1047    fn visit(&self, _: &mut Visit<'_>) {}
1048}
1049
1050impl Tree for () {
1051    fn visit_mut(&mut self, _: &mut VisitMut<'_>) {}
1052}
1053
1054impl<T: ?Sized> Leaves for PhantomData<T> {
1055    fn visit(&self, _: &mut Visit<'_>) {}
1056}
1057
1058impl<T: ?Sized + 'static> Tree for PhantomData<T> {
1059    fn visit_mut(&mut self, _: &mut VisitMut<'_>) {}
1060}
1061
1062impl<T: Leaves + ?Sized> Leaves for &T {
1063    fn visit(&self, v: &mut Visit<'_>) {
1064        (**self).visit(v);
1065    }
1066}
1067
1068impl<T: Leaves + ?Sized> Leaves for &mut T {
1069    fn visit(&self, v: &mut Visit<'_>) {
1070        (**self).visit(v);
1071    }
1072}
1073
1074impl<T: Leaves> Leaves for Option<T> {
1075    fn visit(&self, v: &mut Visit<'_>) {
1076        match self {
1077            None => v.variant(0),
1078            Some(x) => {
1079                v.variant(1);
1080                v.child("", x);
1081            }
1082        }
1083    }
1084}
1085
1086impl<T: Tree> Tree for Option<T> {
1087    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1088        if let Some(x) = self {
1089            v.child("", x);
1090        }
1091    }
1092}
1093
1094impl<T: Leaves> Leaves for Box<T> {
1095    fn visit(&self, v: &mut Visit<'_>) {
1096        v.child("", &**self);
1097    }
1098}
1099
1100impl<T: Tree> Tree for Box<T> {
1101    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1102        v.child("", &mut **self);
1103    }
1104}
1105
1106impl<T: Leaves> Leaves for [T] {
1107    fn visit(&self, v: &mut Visit<'_>) {
1108        for (i, x) in self.iter().enumerate() {
1109            v.child(&i.to_string(), x);
1110        }
1111    }
1112}
1113
1114impl<T: Leaves> Leaves for Vec<T> {
1115    fn visit(&self, v: &mut Visit<'_>) {
1116        self.as_slice().visit(v);
1117    }
1118}
1119
1120impl<T: Tree> Tree for Vec<T> {
1121    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1122        for (i, x) in self.iter_mut().enumerate() {
1123            v.child(&i.to_string(), x);
1124        }
1125    }
1126}
1127
1128impl<T: Leaves, const N: usize> Leaves for [T; N] {
1129    fn visit(&self, v: &mut Visit<'_>) {
1130        self.as_slice().visit(v);
1131    }
1132}
1133
1134impl<T: Tree, const N: usize> Tree for [T; N] {
1135    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1136        for (i, x) in self.iter_mut().enumerate() {
1137            v.child(&i.to_string(), x);
1138        }
1139    }
1140}
1141
1142impl<T: Leaves> Leaves for BTreeMap<String, T> {
1143    fn visit(&self, v: &mut Visit<'_>) {
1144        for (k, x) in self {
1145            v.child(k, x);
1146        }
1147    }
1148}
1149
1150impl<T: Tree> Tree for BTreeMap<String, T> {
1151    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1152        for (k, x) in self.iter_mut() {
1153            v.child(k, x);
1154        }
1155    }
1156}
1157
1158impl<T: Leaves, S: 'static> Leaves for HashMap<String, T, S> {
1159    fn visit(&self, v: &mut Visit<'_>) {
1160        let mut entries: Vec<(&String, &T)> = self.iter().collect();
1161        entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
1162        for (k, x) in entries {
1163            v.child(k, x);
1164        }
1165    }
1166}
1167
1168impl<T: Tree, S: std::hash::BuildHasher + Clone + 'static> Tree for HashMap<String, T, S> {
1169    fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1170        let mut entries: Vec<(&String, &mut T)> = self.iter_mut().collect();
1171        entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
1172        for (k, x) in entries {
1173            v.child(k, x);
1174        }
1175    }
1176}
1177
1178macro_rules! tuples {
1179    ($(($($t:ident $i:tt),+)),*) => {$(
1180        impl<$($t: Leaves),+> Leaves for ($($t,)+) {
1181            fn visit(&self, v: &mut Visit<'_>) {
1182                $(v.child(stringify!($i), &self.$i);)+
1183            }
1184        }
1185        impl<$($t: Tree),+> Tree for ($($t,)+) {
1186            fn visit_mut(&mut self, v: &mut VisitMut<'_>) {
1187                $(v.child(stringify!($i), &mut self.$i);)+
1188            }
1189        }
1190    )*};
1191}
1192
1193tuples!(
1194    (A 0),
1195    (A 0, B 1),
1196    (A 0, B 1, C 2),
1197    (A 0, B 1, C 2, D 3),
1198    (A 0, B 1, C 2, D 3, E 4),
1199    (A 0, B 1, C 2, D 3, E 4, F 5),
1200    (A 0, B 1, C 2, D 3, E 4, F 5, G 6),
1201    (A 0, B 1, C 2, D 3, E 4, F 5, G 6, H 7)
1202);
1203
1204/// What `#[derive(Tree)]`, `#[derive(Module)]` and `#[derive(Static)]` generate code
1205/// against (re-exported as `nuvec::__derive`). Not a public API.
1206#[doc(hidden)]
1207pub mod __derive {
1208    pub use std::any::TypeId;
1209    pub use std::clone::Clone;
1210    pub use std::hash::Hasher;
1211    pub use std::marker::PhantomData;
1212
1213    pub use super::{LeafKind, Leaves, Param, Static, Tree, Visit, VisitMut};
1214    pub use crate::Array;
1215
1216    /// Fails to compile unless `T` is [`Static`] (the field's span carries the error).
1217    pub const fn assert_static<T: Static + ?Sized>() {}
1218}