1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! Vtree rotation primitives.
//!
//! A rotation is the local move for searching vtree space: a consumer scoring
//! vtrees under a cost model this crate does not have rewrites one edge and
//! rescores, instead of rebuilding a tree from scratch. That caller is why
//! these are public — this crate's own pipeline selects a vtree and stops, so
//! it never rotates one.
//!
//! A rotation changes the SHAPE and nothing else: the same leaves carry the
//! same variables afterwards, so it moves within the space of vtrees over one
//! variable set. [`rotate_left`] and [`rotate_right`] are therefore the two
//! moves a local-minimisation loop is built from — apply one, rescore under
//! your own cost, keep or undo. Undoing is the mirror rotation at the same
//! index; [`RotationInfo`] names the five nodes involved, which is also what a
//! consumer holding per-node state needs in order to invalidate exactly the
//! entries the move invalidated rather than all of them.
//!
//! Both take the index of the internal node to rotate at, which must be a node
//! of the vtree passed alongside it. Both leave the vtree untouched and return
//! `None` when the move is structurally impossible — `v` is a leaf, or the
//! child that would be promoted is.
//!
//! A **left rotation** at internal node `v` promotes `v`'s right child `w`:
//!
//! ```text
//! Before: v_idx After: v_idx (now w_new)
//! / \ / \
//! A w_idx w_idx C
//! / \ / \
//! B C A B
//! ```
//!
//! A **right rotation** at internal node `v` is the inverse — it promotes `v`'s
//! left child `w`:
//!
//! ```text
//! Before: v_idx After: v_idx (now w_new)
//! / \ / \
//! w_idx C A w_idx
//! / \ / \
//! A B B C
//! ```
//!
//! Both operations are O(1) pointer surgery on `(v_idx, w_idx)`. Raw `VtreeIdx`
//! values for nodes never change after construction; the side `topo` list on
//! `Vtree` is updated locally via `Vtree::fixup_topo_after_rotate` (the
//! convenience wrappers below also do this) so subsequent traversals (LCA,
//! bottom-up iteration) remain correct.
//!
//! Returns `None` only when the rotation is structurally impossible (`v` or
//! `w` is a leaf). There is no longer a topological-order applicability check
//! because the topo update is decoupled from node identity.
//!
//! # Topo properties preserved by rotations
//!
//! `Vtree::topo` is required to satisfy *children-before-parents* (every
//! parent appears after both its children). The rotation primitives + topo
//! fixup additionally preserve a stronger property, which
//! `Vtree::fixup_topo_after_rotate` relies on:
//!
//! ## Root-last property
//!
//! For every vtree node `t`,
//!
//! ```text
//! topo_pos[t] == max( topo_pos[d] for d in {t} ∪ descendants(t) )
//! ```
//!
//! Each subtree's root sits at the latest topo position among its members.
//! This is *not* asserted after every operation, but it holds inductively
//! through any legal sequence of rebuilds + rotations:
//!
//! - **Base**: a full postorder rebuild of `topo` visits each subtree root
//! after all of its descendants. Property holds trivially.
//! - **Pointer-only rotation**: pointer surgery only edits the parent/child
//! links of `v` and `w`. The descendant *sets* of subtrees `A`, `B`, `C`
//! (and of any node not in `v`'s subtree) are unchanged, so their
//! max-position witness is unchanged. The property may be temporarily
//! broken for `v` and `w` themselves until the topo fixup runs.
//! - **Topo fixup**: relocates `w` past the misplaced subtree's segment as
//! a single block (`topo[w_pos..=m_end].rotate_left(1)`). It does not
//! reorder anything outside that slice, and within the slice it shifts
//! every non-`w` element left by exactly one position. So the
//! max-position witness of every subtree (including `w`'s new subtree
//! and the misplaced subtree) updates consistently with the shift, and
//! the property is restored for `w` and `v`.
//!
//! The early-return case in `fixup_topo_after_rotate` relies on this property —
//! `topo_pos[misplaced_root]` being the maximum over the entire misplaced
//! subtree — to decide in O(1) whether any reordering is needed.
//!
//! ## Subtree contiguity is *not* preserved
//!
//! After a sequence of rotations + fixups, a subtree's members may occupy
//! a non-contiguous range of positions in `topo` (an element from a sibling
//! subtree can sit "between" two members of the same subtree). This is
//! intentional: enforcing contiguity would require shifting unrelated
//! elements during fixups. No consumer of `topo` / `topo_pos` /
//! `internal_topo` / `leaf_topo` in this codebase indexes a subtree by
//! position range — they all walk parent pointers / child links or do rank
//! comparisons via `topo_pos`. Children-before-parents and root-last are
//! sufficient for every consumer.
//!
//! Correctness of the slice rotation does **not** depend on contiguity: the
//! shift preserves children-before-parents for every edge in the new tree
//! (each edge either has both endpoints inside the slice — both shift — or
//! both endpoints outside — neither shifts — or one endpoint inside and the
//! shift never inverts the integer ordering between them). See the
//! `fixup_topo_after_rotate` body for the per-case argument.
use ;
/// The five nodes one rotation touched: the two that swapped depth and the
/// three subtree roots that changed parent.
///
/// Everything outside this set kept its parent, its children and its subtree,
/// so a consumer caching a value per node — a score, a width, a compiled
/// fragment — recomputes only for these and their ancestors, instead of
/// discarding the whole table. The same five indices name the mirror rotation
/// that undoes the move.
///
/// Node indices are stable identities, so these stay valid across further
/// rotations. Field naming follows the **left-rotation** geometry; right
/// rotation stores the same fields but with the corresponding subtrees.
/// Left-rotate, pointer surgery only — does NOT update `Vtree::topo`. Returns
/// `None` if `v` or its right child is a leaf.
///
/// After this call, `topo`/`topo_pos`/`internal_topo`/`leaf_topo` are stale
/// relative to the new shape. Callers that need them must repair the order
/// themselves, through `Vtree::fixup_topo_after_rotate`. For a caller probing
/// many rotations and keeping one: a probe that measures and reverts reads no
/// topo, so repairing it on every probe is wasted work.
pub
/// Left-rotate the vtree at node `v`, promoting `v`'s right child `w`.
///
/// The leaf set is unchanged; only the shape is. [`rotate_right`] at the same
/// index undoes it.
///
/// `v` must be an index into `vtree`. Returns `None`, leaving `vtree`
/// untouched, if `v` or its right child is a leaf; always succeeds otherwise,
/// and the returned [`RotationInfo`] names the nodes the move touched. The
/// vtree is left fully consistent — a caller may read it, score it and rotate
/// it again without any repair step of its own.
/// Right-rotate, pointer surgery only — does NOT update `Vtree::topo`. Mirror
/// of `rotate_left_pointers`.
pub
/// Right-rotate the vtree at node `v`, promoting `v`'s left child `w`.
///
/// The mirror of [`rotate_left`], and what undoes one at the same index. The
/// leaf set is unchanged; only the shape is.
///
/// `v` must be an index into `vtree`. Returns `None`, leaving `vtree`
/// untouched, if `v` or its left child is a leaf; always succeeds otherwise,
/// and the returned [`RotationInfo`] names the nodes the move touched. The
/// vtree is left fully consistent — a caller may read it, score it and rotate
/// it again without any repair step of its own.