1717
1818
1919class _TreeDef :
20- """A JAX-compatible treedef that records pytree structure for unflattening."""
20+ """
21+ A JAX-compatible treedef that records pytree structure for unflattening.
22+ """
2123
2224 __slots__ = ("typ" , "sorted_keys" , "children" , "num_leaves" , "aux_data" )
2325
@@ -29,15 +31,33 @@ def __init__(
2931 num_leaves : int ,
3032 aux_data : Any = None ,
3133 ) -> None :
34+ """
35+ Store the structure of one pytree node.
36+
37+ :param typ: Python type of this node (a container type, ``NoneType``
38+ for an empty pytree, or :class:`_LeafSentinel` for a leaf)
39+ :type typ: type
40+ :param sorted_keys: keys of a dict-like node in flatten order, else ``None``
41+ :type sorted_keys: Optional[Tuple[str, ...]]
42+ :param children: the :class:`_TreeDef` of each child node
43+ :type children: Tuple["_TreeDef", ...]
44+ :param num_leaves: total number of leaves contained in this subtree
45+ :type num_leaves: int
46+ :param aux_data: per-type payload such as a ``defaultdict`` factory,
47+ defaults to ``None``
48+ :type aux_data: Any
49+ """
3250 self .typ = typ
33- self .sorted_keys = sorted_keys # non-None only for dict like
51+ self .sorted_keys = sorted_keys
3452 self .children = children
3553 self .num_leaves = num_leaves
36- self .aux_data = aux_data # only used by defaultdict (factory)
54+ self .aux_data = aux_data
3755
3856
3957class _LeafSentinel :
40- """Sentinel type so that ``_TreeDef`` can represent a leaf node."""
58+ """
59+ Sentinel type so that :class:`_TreeDef` can represent a leaf node.
60+ """
4161
4262 pass
4363
@@ -47,17 +67,32 @@ class _LeafSentinel:
4767
4868
4969def _is_leaf (obj : Any ) -> bool :
50- """Check whether *obj* is a leaf managed by the pure-pytree machinery.
51-
52- Everything that is not a recognised pytree container (dict, list, tuple,
53- namedtuple, or ``None``) is a leaf. This deliberately matches JAX
54- semantics for the types supported here.
70+ """
71+ Check whether *obj* is a leaf managed by the pure-pytree machinery.
72+
73+ Uses **exact** type matching, mirroring JAX ``tree_util``: only the
74+ built-in container types (``dict``, ``OrderedDict``, ``defaultdict``,
75+ ``list``, ``tuple``) and ``namedtuple`` classes are traversed. Arbitrary
76+ subclasses (e.g. ``class MyDict(dict)``) are **leaves** — in JAX a
77+ subclass must be explicitly registered as a pytree node to be traversed,
78+ so without registration the whole instance is a single leaf.
79+
80+ :param obj: any Python object to classify
81+ :type obj: Any
82+ :return: ``False`` for recognised containers and ``None``, ``True`` otherwise
83+ :rtype: bool
5584 """
5685 if obj is None :
5786 return False
58- if isinstance (obj , dict ):
87+ t = type (obj )
88+ if t is dict or t is OrderedDict or t is defaultdict :
5989 return False
60- if isinstance (obj , (list , tuple )):
90+ if t is list or t is tuple :
91+ return False
92+ # ``namedtuple`` (and its subclasses) are tuple subclasses carrying
93+ # ``_fields``; keep them as containers while plain tuple subclasses
94+ # such as ``class MyTuple(tuple)`` remain leaves.
95+ if isinstance (obj , tuple ) and hasattr (obj , "_fields" ):
6196 return False
6297 return True
6398
@@ -67,12 +102,18 @@ def _pure_tree_flatten(pytree: Any) -> Tuple[List[Any], _TreeDef]:
67102 Pure Python tree flattening with JAX-compatible semantics.
68103
69104 Key behaviors:
105+
70106 * ``None`` is an empty pytree (0 leaves), not a leaf.
71107 * ``dict`` and ``defaultdict`` keys are iterated in **sorted** order
72108 for deterministic leaf ordering. ``defaultdict`` preserves the
73109 ``default_factory`` for round-trip.
74110 * ``OrderedDict`` keys are iterated in **insertion** order.
75111 * ``namedtuple`` is tracked as a separate type from plain ``tuple``.
112+
113+ :param pytree: the Python structure to flatten
114+ :type pytree: Any
115+ :return: the flat list of leaves and the :class:`_TreeDef` describing the structure
116+ :rtype: Tuple[List[Any], _TreeDef]
76117 """
77118 if pytree is None :
78119 return [], _TreeDef (_NONE_TYPE , None , (), 0 )
@@ -88,7 +129,9 @@ def _pure_tree_flatten(pytree: Any) -> Tuple[List[Any], _TreeDef]:
88129 else :
89130 # dict and defaultdict: sorted keys for determinism
90131 keys = tuple (sorted (pytree .keys ()))
91- aux_data = pytree .default_factory if actual_type is defaultdict else None
132+ aux_data = (
133+ pytree .default_factory if isinstance (pytree , defaultdict ) else None
134+ )
92135 children = []
93136 for k in keys :
94137 child_leaves , child_td = _pure_tree_flatten (pytree [k ])
@@ -107,7 +150,16 @@ def _pure_tree_flatten(pytree: Any) -> Tuple[List[Any], _TreeDef]:
107150
108151
109152def _unflatten_from_iter (treedef : _TreeDef , leaf_iter : Iterator [Any ]) -> Any :
110- """Recursively rebuild one node, consuming leaves from *leaf_iter*."""
153+ """
154+ Recursively rebuild one node, consuming leaves from *leaf_iter*.
155+
156+ :param treedef: the structure to rebuild
157+ :type treedef: _TreeDef
158+ :param leaf_iter: iterator over leaves, consumed left to right
159+ :type leaf_iter: Iterator[Any]
160+ :return: the rebuilt pytree node
161+ :rtype: Any
162+ """
111163 if treedef .typ is _LEAF :
112164 return next (leaf_iter )
113165 if issubclass (treedef .typ , dict ):
@@ -136,12 +188,21 @@ def _unflatten_from_iter(treedef: _TreeDef, leaf_iter: Iterator[Any]) -> Any:
136188
137189
138190def _pure_tree_unflatten (treedef : _TreeDef , leaves : List [Any ]) -> Any :
139- """Rebuild a pytree from *treedef* and *leaves*.
191+ """
192+ Rebuild a pytree from *treedef* and *leaves*.
140193
141194 Uses the :class:`_TreeDef` produced by :func:`_pure_tree_flatten`.
142195 The input *leaves* list is **not** mutated; a :class:`ValueError` is
143196 raised when the number of leaves does not match the structure
144197 recorded in *treedef* (either too few or too many).
198+
199+ :param treedef: the structure describing the target pytree
200+ :type treedef: _TreeDef
201+ :param leaves: the flat leaves to repack; not mutated
202+ :type leaves: List[Any]
203+ :return: the rebuilt pytree
204+ :rtype: Any
205+ :raises ValueError: if the number of leaves does not match *treedef*
145206 """
146207 leaf_iter = iter (leaves )
147208 try :
@@ -162,7 +223,16 @@ def _pure_tree_unflatten(treedef: _TreeDef, leaves: List[Any]) -> Any:
162223
163224
164225def _treedefs_compatible (treedef_a : _TreeDef , treedef_b : _TreeDef ) -> bool :
165- """Check whether two treedefs have compatible structure for tree_map."""
226+ """
227+ Check whether two treedefs have compatible structure for ``tree_map``.
228+
229+ :param treedef_a: the first treedef to compare
230+ :type treedef_a: _TreeDef
231+ :param treedef_b: the second treedef to compare
232+ :type treedef_b: _TreeDef
233+ :return: ``True`` if both treedefs share type, key order, and nesting
234+ :rtype: bool
235+ """
166236 if treedef_a .typ is not treedef_b .typ :
167237 return False
168238 if treedef_a .typ is _LEAF :
@@ -191,6 +261,14 @@ def _pure_tree_map(f: Callable[..., Any], *pytrees: Any) -> Any:
191261 Applies *f* element-wise to corresponding leaves. All non-leaf
192262 arguments must share the **same** pytree structure (type, key order,
193263 and nesting); a :class:`ValueError` is raised on mismatch.
264+
265+ :param f: function applied to each tuple of corresponding leaves
266+ :type f: Callable[..., Any]
267+ :param pytrees: one or more pytrees with matching structure
268+ :type pytrees: Any
269+ :return: a pytree shaped like the inputs with ``f`` applied to the leaves
270+ :rtype: Any
271+ :raises ValueError: if the non-leaf arguments have mismatched structure
194272 """
195273 if not pytrees :
196274 raise TypeError ("_pure_tree_map requires at least one pytree argument" )
@@ -199,7 +277,6 @@ def _pure_tree_map(f: Callable[..., Any], *pytrees: Any) -> Any:
199277 if all (_is_leaf (p ) for p in pytrees ):
200278 return f (* pytrees )
201279
202- # Flatten, validate structure, and collect leaf lists in one pass
203280 flat_results = []
204281 first_treedef = None
205282 all_leaves : List [List [Any ]] = []
@@ -216,9 +293,9 @@ def _pure_tree_map(f: Callable[..., Any], *pytrees: Any) -> Any:
216293
217294 assert first_treedef is not None
218295
219- # Apply f to corresponding leaves
220296 for i in range (first_treedef .num_leaves ):
221- flat_results .append (f (* (ls [i ] for ls in all_leaves )))
297+ corresponding_leaves = [leaves [i ] for leaves in all_leaves ]
298+ flat_results .append (f (* corresponding_leaves ))
222299
223300 return _pure_tree_unflatten (first_treedef , flat_results )
224301
0 commit comments