Trees and Fenwick Trees
Why trees?
The Z-chain in Jordan-Wigner grows linearly because it uses a linear data structure. The key insight behind better encodings: use a tree to share parity information, cutting depth to $O(\log n)$.
Fenwick Trees (Binary Indexed Trees)
The Bravyi-Kitaev encoding is built on a Fenwick tree. FockMap provides a purely functional, immutable implementation:
let occupations = [| 1; 0; 1; 1; 0; 1; 0; 1 |]
let tree = FenwickTree.ofArray (^^^) 0 occupations
// Prefix query: XOR of elements 0..3
FenwickTree.prefixQuery tree 3
// Point query: just element 5
FenwickTree.pointQuery tree 5
// Immutable update (returns a new tree):
let tree' = FenwickTree.update tree 2 0
The Fenwick structure defines the BK index sets:
FenwickTree.updateSet 8 3 // U(3) β which qubits to update
FenwickTree.paritySet 3 // P(3) β parity contributors
FenwickTree.occupationSet 3 // Occ(3) β occupation encoding
FenwickTree.remainderSet 3 // R(3) = P(3) \ Occ(3)
Encoding trees: choose your shape
FockMap generalises beyond Fenwick trees to arbitrary tree shapes:
let linear = linearTree 8 // Chain β recovers Jordan-Wigner
let binary = balancedBinaryTree 8 // Balanced binary β O(logβ n)
let ternary = balancedTernaryTree 8 // Balanced ternary β O(logβ n)
let vlasov = vlasovTree 8 // Complete ternary (Vlasov) β O(logβ n)
Walk any tree:
let tree = balancedBinaryTree 8
treeAncestors tree 5 // path from node 5 toward root
treeDescendants tree 1 // all descendants
treeChildren tree 1 // direct children only
Two frameworks for tree-based encoding
Framework 1 β Index sets (Fenwick-compatible trees only):
let scheme = treeEncodingScheme (balancedBinaryTree 8)
encodeOperator scheme Raise 2u 8u
Framework 2 β Path-based ternary tree (any ternary tree):
Constructs Pauli strings directly from root-to-leg paths using X/Y/Z link labels. This is the approach from Jiang et al. and the Bonsai paper:
let tree = balancedTernaryTree 8
let links = computeLinks tree // assign X/Y/Z labels
let legs = allLegs links // enumerate all legs
let pairs = pairLegs tree links // pair legs per mode
// Full encoding in one call:
let result = encodeWithTernaryTree tree Raise 2u 8u
The Vlasov tree: a different ternary shape
The balanced ternary tree (balancedTernaryTree) uses midpoint-split
indexing: the root is in the middle, and children are spread across
three roughly equal partitions. The Vlasov tree (vlasovTree) uses
level-order (breadth-first) indexing instead: node 0 is the root,
and children of node $j$ are $3j+1, 3j+2, 3j+3$.
This is based on Vlasovβs Clifford-algebraic construction (arXiv:1904.09912), where Clifford algebra generators are defined recursively on a complete ternary tree.
Both achieve $O(\log_3 n)$ weight, but they distribute weight differently across modes:
let n = 8u
printfn "%-5s %-8s %-8s" "Mode" "TerTree" "Vlasov"
for j in 0u .. n-1u do
let weight encode =
let terms = (encode Raise j n).DistributeCoefficient
terms.SummandTerms
|> Array.map (fun t ->
t.Signature |> Seq.filter (fun c -> c <> 'I') |> Seq.length)
|> Array.max
printfn "%-5d %-8d %-8d" j (weight ternaryTreeTerms) (weight vlasovTreeTerms)
Output:
Mode TerTree Vlasov
0 3 3
1 3 3
2 3 3
3 3 2 β Vlasov wins here
4 2 3 β Balanced ternary wins here
5 3 3
6 3 3
7 3 3
The tree shape matters: different qubit assignments put different modes at different depths, so the βbestβ tree depends on which modes appear most frequently in your Hamiltonian.
Comparing tree shapes on Hβ
Both ternary tree shapes produce valid Hβ Hamiltonians with identical eigenspectra but different Pauli structure:
let hamEncoders = [
("Jordan-Wigner", jordanWignerTerms)
("Bravyi-Kitaev", bravyiKitaevTerms)
("Balanced Ternary", ternaryTreeTerms)
("Vlasov Tree", vlasovTreeTerms)
]
for (name, encoder) in hamEncoders do
let ham = (computeHamiltonianWith encoder lookup 4u).DistributeCoefficient
let terms = ham.SummandTerms |> Array.filter (fun t -> Complex.Abs t.Coefficient > 1e-10)
let weights = terms |> Array.map (fun t ->
t.Signature |> Seq.filter (fun c -> c <> 'I') |> Seq.length)
printfn "%-20s Terms: %d MaxWt: %d AvgWt: %.2f"
name terms.Length (Array.max weights) (Array.averageBy float weights)
Jordan-Wigner Terms: 7 MaxWt: 2 AvgWt: 1.14
Bravyi-Kitaev Terms: 7 MaxWt: 3 AvgWt: 1.71
Balanced Ternary Terms: 7 MaxWt: 3 AvgWt: 1.43
Vlasov Tree Terms: 7 MaxWt: 3 AvgWt: 1.43
For Hβ (4 qubits), both tree encodings match. The differences grow with system size β at $n = 64$, different tree shapes can differ by 30β40% in total CNOT cost, making tree selection a first-order optimization concern.
Try it yourself: See Lab 10: Vlasov Tree for the full comparison script with tree structure printouts, per-mode weight tables, and Hβ Hamiltonian analysis.
Next: Building a Real Hamiltonian β the complete end-to-end pipeline