Tree Data Structure Redesign¶
Problem¶
The current nested-tuple tree representation requires full recursive traversal
for every structural query (weight, arity, leaves, min_leaf, subtree_degree).
Since these queries are called repeatedly in performance-critical paths—especially
the bar/cobar differentials _d2_on_basis—this causes O(v²) or worse overhead
per differential computation.
Current Representation¶
Trees are encoded as nested Python tuples:
Leaf:
intin{1, ..., n}Internal vertex:
(decoration, child_1, ..., child_k)wheredecorationis an operad/cooperad basis key (a tuple) and eachchild_jis a leaf or subtree.
Example: ((1, 2), ((1,), 1, 2), 3) — arity 3, weight 2.
Bottlenecks¶
subtree_degreerecomputation in_d2_on_basis: For each internal edge, the degrees of all sibling subtrees are recomputed from scratch. This is O(v² × lookup) per tree.min_leafrecomputation in shuffle normalization: Called O(k) times per vertex duringto_shuffle_tree_bar/cobar, each O(n) traversal.leaves()recomputation: O(n) set union called inafter_cobar_deg,tree_arity,validate_tree, and others.weight()recomputation: O(v) recursive count called in enumeration.Full recursive copy on every structural operation:
relabel_leaves,graft,contract_edge,expand_vertexall rebuild the entire tree.
New Design: RootedTree Class¶
Core Idea¶
Replace nested tuples with an immutable RootedTree class that eagerly
computes and caches structural properties at construction time. Since every
tree operation already rebuilds the tree from scratch anyway, the per-node
overhead of caching is amortized over the many subsequent queries.
Invariants¶
Leaves remain as plain
intvalues (no wrapping).RootedTreeis immutable and hashable — works as SageMath@cached_methodarguments andCombinatorialFreeModulebasis keys.Identity comparison (
is) continues to work for DFS-extracted vertices.
Cached Properties (computed at construction, O(1) access)¶
Property |
Type |
Old cost |
New cost |
|---|---|---|---|
|
|
O(v) |
O(1) |
|
|
O(n) |
O(1) |
|
|
O(n) |
O(1) |
|
|
O(n) |
O(1) |
|
|
O(1) |
O(1) |
|
|
O(1) |
O(1) |
|
|
O(1) |
O(1) |
Construction Cost¶
Each RootedTree(decoration, children) call computes cached properties by
merging child properties in O(k) time (where k = number of children).
Since every tree modification already takes O(tree_size) for the recursive
rebuild, this adds at most a constant factor per node during construction.
Class API¶
class RootedTree:
__slots__ = (
'_decoration', '_children', '_weight', '_tree_arity',
'_leaves', '_min_leaf', '_arity', '_hash',
)
def __init__(self, decoration: tuple, *children):
# children: tuple of (RootedTree | int)
# Eagerly compute: weight, leaves, min_leaf, tree_arity
Comparison & Hashing¶
__hash__: Based on(decoration, children)tuple — structurally equivalent trees hash identically.__eq__: Recursive structural comparison (with short-circuit on hash mismatch).__lt__: Lexicographic comparison for SageMath basis ordering.
Migration Strategy¶
Add
RootedTreeclass totrees.py.Update all functions in
trees.pyto accept and returnRootedTree.Add
RootedTree.from_tuple()for backward compatibility.Update bar/cobar constructions, tree_module, and morphisms.
Update tests.
Operations on RootedTree¶
All existing operations are preserved with the same semantics:
relabel_leaves(tree, mapping)→ returns newRootedTreegraft(tree_top, i, tree_bot)→ returns newRootedTreecontract_edge(tree, parent, pos, dec)→ returns newRootedTreeexpand_vertex(tree, target, pos, ...)→ returns newRootedTreesplit_at_vertex(tree, target)→ returns(RootedTree, int, RootedTree)vertices_dfs(tree)→ returns list ofRootedTreereferences (identity-safe)
Future Optimizations (Not in This PR)¶
Degree caching: Store context-specific degrees (bar, cobar, free) via a
degree_cachedict on each node. This would makesubtree_degreeO(1).Persistent/structural sharing: Use path-copying so that
contract_edgeonly rebuilds nodes on the root-to-target path, sharing unchanged subtrees. This is already partially achieved sinceRootedTreechildren are references.Flat array representation: For very large trees, store nodes in a flat DFS-ordered array with parent/child indices for cache-friendly traversal.
Status Update (2026-04): Planar Coinvariant Enumeration¶
In parallel with tree-level optimization, profiling showed a separate dominant
cost in cofree coalgebra basis enumeration: explicit S_n orbit-sum
construction for invariants. The current implementation now enumerates planar
representatives directly and canonicalizes boundary output back to planar keys.
Mathematical Setup¶
For invariants of the form
X ⊗_{S_n} Y with X = X_pl ⊗ k[S_n],
we use the canonical identification
(X_pl ⊗ k[S_n]) ⊗_{S_n} Y ≅ X_pl ⊗ Y
to avoid explicit orbit sums in basis enumeration.
Boundary Strategies Compared¶
Two methods were tested.
Orbit-sum method (reference):
Build orbit-sum representative.
Apply boundary.
Renormalize to planar representatives.
Planar-first method:
Keep only planar representative in the basis.
Apply boundary there.
Canonicalize non-planar output terms to planar keys.
Result¶
The two methods are not coefficient-identical in general when the module tuple
m has repeated entries. The difference is a stabilizer factor |Stab(m)|.
Over
QQ, this appears as multiplicative factors (for example factor2in binary symmetry cases).Over positive characteristic, this factor can vanish (for example
2 = 0inGF(2)), so matrix entries can differ by zero/nonzero behavior.
Despite this, the two complexes are chain-isomorphic under the coinvariant
identification above. The planar-first implementation satisfies d^2 = 0 and
computes the same homology.
Implementation Notes¶
basis_iter/basis_weight_iterenumerate planar termsterm((c_pl, m))directly (no orbit-sum construction during enumeration).Boundary matrix assembly canonicalizes non-planar terms via a full canonicalization routine that returns all planar contributions.
This fixes a subtle lossy behavior from single-term canonicalization when planarization has multiple terms (for example with Lie-type factors).
Validation Summary¶
d^2 = 0checked at weights 2-4 over bothQQandGF(2).Regression tests added for planar basis counting/canonical keys and boundary canonicalization behavior.
Observed speedup on heavy layers around
2.8x(example: 226s to 80s), with full test suite passing.