Hamiltonian Adaptive Ternary Tree#

HATT is an algorithm for the construction of Ternary-Tree encodings which are optimised for the Hamiltonian of interest.

This notebook shows how to reproduce the results of: Y. Liu et al., “HATT: Hamiltonian Adaptive Ternary Tree for Optimizing Fermion-to-Qubit Mapping,” 2025 IEEE International Symposium on High Performance Computer Architecture (HPCA), Las Vegas, NV, USA, 2025, pp. 143-157, doi: 10.1109/HPCA61900.2025.00022.

Preprocess Hamiltonian#

The first thing we need to do for HATT is to process our fermionic operator into a majorana operator.

There’s a function to do this in ferrmion.utils if you need it, and the details aren’t so important. We’ll start with the small example given in the original paper.

\[H_F = a^{\dagger}_0a_0 + 2 a^{\dagger}_1 a^{\dagger}_2 a_1 a_2\]
from ferrmion.core import fermionic_to_sparse_majorana
import numpy as np

n_modes = 3
ones = np.zeros((n_modes, n_modes))
twos = np.zeros((n_modes, n_modes, n_modes, n_modes))

ones[0, 0] = 1
twos[1, 2, 1, 2] = 2

majorana_ham = fermionic_to_sparse_majorana(signatures=["+-","++--"], coeffs=[ones,twos],constant_energy=0)
majorana_ham
{(0, 1): 0.5j, (4, 5): -0.5j, (2, 3): -0.5j, (2, 3, 4, 5): (0.5+0j)}

By rerodering the modes using \(\{\gamma_i,\gamma_j\}=2\delta_{i,j}\) you can simplify this down to the following:

majorana_ham = {(0, 1): 0.5j, (2, 3): -0.5j, (4, 5): -0.5j, (2, 3, 4, 5): 0.5}
n_modes = 3

Algorithm 3#

There are three algorithms presented, each iteratively adding some improvements. We’ll jump straight to algorithm 3, which caches some information about the tree as we build it.

from itertools import permutations
from typing import Iterable
from ferrmion.encode import TernaryTree
from ferrmion.encode.ternary_tree_node import TTNode


def _qubit_term_weight(term: Iterable, comb: tuple[int, int, int]) -> int:
    """Find the single-qubit Pauli-weight of majorana terms.

    If any pauli term is found an even number f times, we obtain I, weight = 0.
    If we find all three pauli terms, return I (with an imaginary ccoefficient), weight = 0
    If we find either one pauli or two then the weight = 1.

    Args:
        term (Iterable): Indices of term in our majorana-hamiltonian.
        comb (tuple[int, int, int]): Combination of indices to weigh (x,y,z).

    Returns:
        int: Weight of the term.
    """
    term_array = np.array([t for t in term])
    odd_parity_paulis = np.array(
        [np.count_nonzero(np.array(term_array - index)) % 2 for index in comb]
    )
    print(f"{term_array=}")
    print(f"{comb=}")
    print([np.array(term_array - index) for index in comb])
    non_commuting = np.sum(odd_parity_paulis) % 3
    weight = int(non_commuting != 0)
    print(f"{weight=}")
    return weight


def _reduce_hamiltonian(
    majorana_ham: dict[tuple[int, ...], float],
    parent_index: int,
    selection: tuple[int, int, int],
) -> dict[tuple[int, ...], float]:
    """Simplify the Hamiltonian.

    As we increase the qubit number, we iteratively remove majoranas
    which act trivially on the remaining qubits.
    We replace them with the index of their parent string
    as going forward they are identical to the parent string.

    Args:
        majorana_ham (dict[tuple[int,...],float]): Current Hamiltonian.
        parent_index (int): Qubit index of the parent node.
        selection (tuple[int, int, int]): Indices of the majoranas to be replaced.

    Returns:
        dict[tuple[int,...],float]: Reduced Hamiltonian.
    """
    new_ham = {}
    for term, coeff in majorana_ham.items():
        new_term = [i if i not in selection else parent_index for i in term]
        if len(set(new_term)) != 1:
            new_ham[tuple(new_term)] = coeff
    return new_ham

We’ll first set up for iteratively building the tree.

We need:

  • a set of leaves and nodes

  • a way to keep track of which ones have been used already

  • maps from each node to its furthest child and parent which can be found by taking only z branches

# We need 2*M +1 leaves and M nodes.
nodes = {i: None for i in range(2 * n_modes + 1)}
for i in range(n_modes):
    nodes[2 * n_modes + 1 + i] = TTNode(qubit_label=i)

# Start with all the leaves unassigned
unassigned = {*range(2 * n_modes + 1)}

# We create two maps, of z_ancestors and z_descendants
ancestor_map = {i: i for i in nodes}
descendant_map = {i: i for i in nodes}

# We can also total up the pauli-weight as we go to save effort later
total_weight = 0

The next step is to iteratively update the tree, using a greedy method to explicitly check which combination of possible child nodes gives us the smallest Pauli-weight.

for i in range(n_modes):
    parent_index = 2 * n_modes + 1 + i
    parent = nodes[parent_index]

    min = np.inf
    selection = None
    for comb in permutations(unassigned, 2):
        small_y = None
        small_x = None
        x_index, z_index = comb
        small_x = descendant_map[x_index]

        # discard this combination
        if small_x == 2 * n_modes:
            continue

        if small_x % 2 == 0:
            small_y = small_x + 1
        else:
            small_y = small_x - 1
        # We can't use this index for y a
        # it has been used in the combination already
        # so we'd be replacing our x or z!
        if small_y in comb:
            # print(comb, small_y)
            # print("small y in comb")
            continue

        y_index = ancestor_map[small_y]

        if y_index in comb:
            # print(comb, y_index)
            # print("y index in comb")
            continue

        if small_x % 2 == 0:
            comb = np.array([x_index, y_index, z_index], dtype=np.uint)
        else:
            comb = np.array([y_index, x_index, z_index], dtype=np.uint)
        comb = [int(i) for i in comb]
        weight = np.sum(
            [_qubit_term_weight(term, comb) for term in majorana_ham.keys()]
        )
        print(f"This weight {weight=}\n")
        if weight < min:
            print(f"NEW MIN {weight=}")
            print(f"NEW COMB {comb=}\n")
            min = weight
            selection = comb

    total_weight += min
    print(f"{comb=}")
    print(f"{total_weight=}")
    # Now find the Y pair of the x-node
    for i, char in zip(selection, ["x", "y", "z"]):
        if i in unassigned:
            unassigned.remove(i)

        if isinstance(nodes.get(i, None), TTNode):
            parent.add_child(which_child=char, child_node=nodes.get(i))
        else:
            parent.leaf_majorana_indices[char] = i

    z_index = selection[2]
    z_desc = descendant_map[z_index]
    descendant_map[parent_index] = z_desc
    ancestor_map[z_index] = parent_index
    ancestor_map[z_desc] = parent_index

    unassigned.add(parent_index)

    # See the docstring for this function above
    print(f"Ham {majorana_ham=}")
    majorana_ham = _reduce_hamiltonian(majorana_ham, parent_index, selection)
    print(f"Reduced Ham {majorana_ham=}")
if len(unassigned) != 1:
    raise ValueError("Not all nodes assigned by HATT.")
print(unassigned, total_weight)
root = nodes[unassigned.pop()]
term_array=array([0, 1])
comb=[0, 1, 2]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 2]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[0, 1, 2]
[array([4, 5]), array([3, 4]), array([2, 3])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 2]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([0, 1, 2, 3])]
weight=1
This weight weight=np.int64(3)

NEW MIN weight=np.int64(3)
NEW COMB comb=[0, 1, 2]

term_array=array([0, 1])
comb=[0, 1, 3]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 3]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[0, 1, 3]
[array([4, 5]), array([3, 4]), array([1, 2])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 3]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-1,  0,  1,  2])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 4]
[array([0, 1]), array([-1,  0]), array([-4, -3])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 4]
[array([2, 3]), array([1, 2]), array([-2, -1])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 4]
[array([4, 5]), array([3, 4]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[0, 1, 4]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-2, -1,  0,  1])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 5]
[array([0, 1]), array([-1,  0]), array([-5, -4])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 5]
[array([2, 3]), array([1, 2]), array([-3, -2])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 5]
[array([4, 5]), array([3, 4]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[0, 1, 5]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-3, -2, -1,  0])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 6]
[array([0, 1]), array([-1,  0]), array([-6, -5])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 6]
[array([2, 3]), array([1, 2]), array([-4, -3])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 6]
[array([4, 5]), array([3, 4]), array([-2, -1])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 6]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-4, -3, -2, -1])]
weight=0
This weight weight=np.int64(1)

NEW MIN weight=np.int64(1)
NEW COMB comb=[0, 1, 6]

term_array=array([0, 1])
comb=[0, 1, 2]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 2]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[0, 1, 2]
[array([4, 5]), array([3, 4]), array([2, 3])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 2]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([0, 1, 2, 3])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 3]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 3]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[0, 1, 3]
[array([4, 5]), array([3, 4]), array([1, 2])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 3]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-1,  0,  1,  2])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 4]
[array([0, 1]), array([-1,  0]), array([-4, -3])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 4]
[array([2, 3]), array([1, 2]), array([-2, -1])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 4]
[array([4, 5]), array([3, 4]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[0, 1, 4]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-2, -1,  0,  1])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 5]
[array([0, 1]), array([-1,  0]), array([-5, -4])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 5]
[array([2, 3]), array([1, 2]), array([-3, -2])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 5]
[array([4, 5]), array([3, 4]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[0, 1, 5]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-3, -2, -1,  0])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[0, 1, 6]
[array([0, 1]), array([-1,  0]), array([-6, -5])]
weight=1
term_array=array([2, 3])
comb=[0, 1, 6]
[array([2, 3]), array([1, 2]), array([-4, -3])]
weight=0
term_array=array([4, 5])
comb=[0, 1, 6]
[array([4, 5]), array([3, 4]), array([-2, -1])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[0, 1, 6]
[array([2, 3, 4, 5]), array([1, 2, 3, 4]), array([-4, -3, -2, -1])]
weight=0
This weight weight=np.int64(1)

term_array=array([0, 1])
comb=[2, 3, 0]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([2, 3])
comb=[2, 3, 0]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 0]
[array([2, 3]), array([1, 2]), array([4, 5])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 0]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([2, 3, 4, 5])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[2, 3, 1]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([2, 3])
comb=[2, 3, 1]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 1]
[array([2, 3]), array([1, 2]), array([3, 4])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 1]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([1, 2, 3, 4])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[2, 3, 4]
[array([-2, -1]), array([-3, -2]), array([-4, -3])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 4]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 4]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 4]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-2, -1,  0,  1])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[2, 3, 5]
[array([-2, -1]), array([-3, -2]), array([-5, -4])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 5]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 5]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 5]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-3, -2, -1,  0])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[2, 3, 6]
[array([-2, -1]), array([-3, -2]), array([-6, -5])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 6]
[array([0, 1]), array([-1,  0]), array([-4, -3])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 6]
[array([2, 3]), array([1, 2]), array([-2, -1])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 6]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-4, -3, -2, -1])]
weight=1
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[2, 3, 0]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([2, 3])
comb=[2, 3, 0]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 0]
[array([2, 3]), array([1, 2]), array([4, 5])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 0]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([2, 3, 4, 5])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[2, 3, 1]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([2, 3])
comb=[2, 3, 1]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 1]
[array([2, 3]), array([1, 2]), array([3, 4])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 1]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([1, 2, 3, 4])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[2, 3, 4]
[array([-2, -1]), array([-3, -2]), array([-4, -3])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 4]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 4]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 4]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-2, -1,  0,  1])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[2, 3, 5]
[array([-2, -1]), array([-3, -2]), array([-5, -4])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 5]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 5]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 5]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-3, -2, -1,  0])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[2, 3, 6]
[array([-2, -1]), array([-3, -2]), array([-6, -5])]
weight=0
term_array=array([2, 3])
comb=[2, 3, 6]
[array([0, 1]), array([-1,  0]), array([-4, -3])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 6]
[array([2, 3]), array([1, 2]), array([-2, -1])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 6]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-4, -3, -2, -1])]
weight=1
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 0]
[array([-4, -3]), array([-5, -4]), array([0, 1])]
weight=1
term_array=array([2, 3])
comb=[4, 5, 0]
[array([-2, -1]), array([-3, -2]), array([2, 3])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 0]
[array([0, 1]), array([-1,  0]), array([4, 5])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 0]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([2, 3, 4, 5])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[4, 5, 1]
[array([-4, -3]), array([-5, -4]), array([-1,  0])]
weight=1
term_array=array([2, 3])
comb=[4, 5, 1]
[array([-2, -1]), array([-3, -2]), array([1, 2])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 1]
[array([0, 1]), array([-1,  0]), array([3, 4])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 1]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([1, 2, 3, 4])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[4, 5, 2]
[array([-4, -3]), array([-5, -4]), array([-2, -1])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 2]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 2]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 2]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([0, 1, 2, 3])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 3]
[array([-4, -3]), array([-5, -4]), array([-3, -2])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 3]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 3]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 3]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-1,  0,  1,  2])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 6]
[array([-4, -3]), array([-5, -4]), array([-6, -5])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 6]
[array([-2, -1]), array([-3, -2]), array([-4, -3])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 6]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 6]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-4, -3, -2, -1])]
weight=1
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 0]
[array([-4, -3]), array([-5, -4]), array([0, 1])]
weight=1
term_array=array([2, 3])
comb=[4, 5, 0]
[array([-2, -1]), array([-3, -2]), array([2, 3])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 0]
[array([0, 1]), array([-1,  0]), array([4, 5])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 0]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([2, 3, 4, 5])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[4, 5, 1]
[array([-4, -3]), array([-5, -4]), array([-1,  0])]
weight=1
term_array=array([2, 3])
comb=[4, 5, 1]
[array([-2, -1]), array([-3, -2]), array([1, 2])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 1]
[array([0, 1]), array([-1,  0]), array([3, 4])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 1]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([1, 2, 3, 4])]
weight=1
This weight weight=np.int64(3)

term_array=array([0, 1])
comb=[4, 5, 2]
[array([-4, -3]), array([-5, -4]), array([-2, -1])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 2]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 2]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 2]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([0, 1, 2, 3])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 3]
[array([-4, -3]), array([-5, -4]), array([-3, -2])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 3]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 3]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 3]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-1,  0,  1,  2])]
weight=0
This weight weight=np.int64(2)

term_array=array([0, 1])
comb=[4, 5, 6]
[array([-4, -3]), array([-5, -4]), array([-6, -5])]
weight=0
term_array=array([2, 3])
comb=[4, 5, 6]
[array([-2, -1]), array([-3, -2]), array([-4, -3])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 6]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 6]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-4, -3, -2, -1])]
weight=1
This weight weight=np.int64(2)

comb=(6, 5)
total_weight=np.int64(1)
Ham majorana_ham={(0, 1): 0.5j, (2, 3): (-0-0.5j), (4, 5): (-0-0.5j), (2, 3, 4, 5): 0.5}
Reduced Ham majorana_ham={(2, 3): (-0-0.5j), (4, 5): (-0-0.5j), (2, 3, 4, 5): 0.5}
term_array=array([2, 3])
comb=[2, 3, 4]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 4]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 4]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-2, -1,  0,  1])]
weight=0
This weight weight=np.int64(2)

NEW MIN weight=np.int64(2)
NEW COMB comb=[2, 3, 4]

term_array=array([2, 3])
comb=[2, 3, 5]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 5]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 5]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-3, -2, -1,  0])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[2, 3, 7]
[array([0, 1]), array([-1,  0]), array([-5, -4])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 7]
[array([2, 3]), array([1, 2]), array([-3, -2])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 7]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-5, -4, -3, -2])]
weight=1
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[2, 3, 4]
[array([0, 1]), array([-1,  0]), array([-2, -1])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 4]
[array([2, 3]), array([1, 2]), array([0, 1])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 4]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-2, -1,  0,  1])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[2, 3, 5]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 5]
[array([2, 3]), array([1, 2]), array([-1,  0])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[2, 3, 5]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-3, -2, -1,  0])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[2, 3, 7]
[array([0, 1]), array([-1,  0]), array([-5, -4])]
weight=1
term_array=array([4, 5])
comb=[2, 3, 7]
[array([2, 3]), array([1, 2]), array([-3, -2])]
weight=0
term_array=array([2, 3, 4, 5])
comb=[2, 3, 7]
[array([0, 1, 2, 3]), array([-1,  0,  1,  2]), array([-5, -4, -3, -2])]
weight=1
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 2]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 2]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 2]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([0, 1, 2, 3])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 3]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 3]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 3]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-1,  0,  1,  2])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 7]
[array([-2, -1]), array([-3, -2]), array([-5, -4])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 7]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 7]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-5, -4, -3, -2])]
weight=1
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 2]
[array([-2, -1]), array([-3, -2]), array([0, 1])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 2]
[array([0, 1]), array([-1,  0]), array([2, 3])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 2]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([0, 1, 2, 3])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 3]
[array([-2, -1]), array([-3, -2]), array([-1,  0])]
weight=1
term_array=array([4, 5])
comb=[4, 5, 3]
[array([0, 1]), array([-1,  0]), array([1, 2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 3]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-1,  0,  1,  2])]
weight=0
This weight weight=np.int64(2)

term_array=array([2, 3])
comb=[4, 5, 7]
[array([-2, -1]), array([-3, -2]), array([-5, -4])]
weight=0
term_array=array([4, 5])
comb=[4, 5, 7]
[array([0, 1]), array([-1,  0]), array([-3, -2])]
weight=1
term_array=array([2, 3, 4, 5])
comb=[4, 5, 7]
[array([-2, -1,  0,  1]), array([-3, -2, -1,  0]), array([-5, -4, -3, -2])]
weight=1
This weight weight=np.int64(2)

comb=(7, 5)
total_weight=np.int64(3)
Ham majorana_ham={(2, 3): (-0-0.5j), (4, 5): (-0-0.5j), (2, 3, 4, 5): 0.5}
Reduced Ham majorana_ham={(8, 5): (-0-0.5j), (8, 8, 8, 5): 0.5}
term_array=array([8, 5])
comb=[8, 5, 7]
[array([ 0, -3]), array([3, 0]), array([ 1, -2])]
weight=1
term_array=array([8, 8, 8, 5])
comb=[8, 5, 7]
[array([ 0,  0,  0, -3]), array([3, 3, 3, 0]), array([ 1,  1,  1, -2])]
weight=1
This weight weight=np.int64(2)

NEW MIN weight=np.int64(2)
NEW COMB comb=[8, 5, 7]

term_array=array([8, 5])
comb=[8, 5, 7]
[array([ 0, -3]), array([3, 0]), array([ 1, -2])]
weight=1
term_array=array([8, 8, 8, 5])
comb=[8, 5, 7]
[array([ 0,  0,  0, -3]), array([3, 3, 3, 0]), array([ 1,  1,  1, -2])]
weight=1
This weight weight=np.int64(2)

comb=[8, 5, 7]
total_weight=np.int64(5)
Ham majorana_ham={(8, 5): (-0-0.5j), (8, 8, 8, 5): 0.5}
Reduced Ham majorana_ham={}
{9} 5

Finally, we can build the tree from our root node, which must be the only node without a parent.

tree = TernaryTree(n_modes=n_modes, root_node=root)
tree.enumeration_scheme = tree.default_enumeration_scheme()
tree.pauli_weight = total_weight
tree.as_dict()
{'x': {'x': 2, 'y': 3, 'z': 4}, 'y': 5, 'z': {'x': 0, 'y': 1, 'z': 6}}

Inbuilt function#

ferrmion has an inbuilt function which works in just the same way.

import json
from ferrmion.encode import TernaryTree, MaxNTO
from pathlib import Path
from ferrmion.hamiltonians import molecular_hamiltonian

folder = Path.cwd().joinpath(Path("../../../python/tests/"))
with open(folder.joinpath("./data/h2o_sto-3g.json"), "rb") as file:
    data = json.load(file)
ones = np.array(data["ones"])
twos = np.array(data["twos"])

fham = molecular_hamiltonian(ones, twos, 0.0)
mham = fermionic_to_sparse_majorana(signatures=["+-","++--"], coeffs=[ones,twos],constant_energy=0)
from ferrmion.visualise import draw_tt
from ferrmion.optimize.hatt import hamiltonian_adaptive_ternary_tree

hatt = hamiltonian_adaptive_ternary_tree(fham, fham.n_modes)
print(hatt.pauli_weight)
hatt.as_dict()

draw_tt(hatt, enumeration_scheme=hatt.default_enumeration_scheme())
4668
../_images/1a90c60bd99bd16517fe466885b236c2ebdd3bb19a21223454b6c90b5dd06b81.png
hatt.flatpack()
[(13, (5, 6, 12)),
 (5, (34, 35, 18)),
 (6, (26, 27, 19)),
 (12, (11, 39, 2)),
 (11, (10, 8, 38)),
 (2, (14, 15, 1)),
 (10, (3, 9, 7)),
 (8, (22, 23, 37)),
 (1, (32, 33, 0)),
 (3, (16, 17, 24)),
 (9, (40, 41, 4)),
 (7, (20, 21, 36)),
 (0, (30, 31, None)),
 (4, (28, 29, 25))]