Grouping Operator Terms

The operator representations provided by the operators module provide a groups attribute which can optionally be used to specify group indices for each term.

What this means concretely, is that all the terms of an operator can be associated with an integer value and terms with identical indices are considered to be part of the same group of terms. Grouping information provided like this can be exploited in different processing steps, some examples of which include:

  • optimizing the time evolution of an operator with grouped terms

    Hint

    A tutorial for this will be provided soon!

  • more physically meaningful circuits generated by the SqDRIFT routine

  • the selection of flow sets [1]

Usage

The simplest way of defining an operator grouping is by setting the groups attribute of the target operator. Below we give a simple example for grouping terms according to the line flow sets as shown in Fig. 1c of [1].

>>> import rustworkx as rx
>>> graph = rx.PyDiGraph()
>>> _ = graph.add_nodes_from(range(6))
>>> edges = [(0, 1), (1, 2), (3, 0), (1, 4), (5, 2), (5, 4), (4, 3)]
>>> groups = [0, 0, 1, 2, 1, 3, 3]
>>> edges_with_group_as_payload = [(i, j, g) for (i, j), g in zip(edges, groups)]
>>> _ = graph.add_edges_from(edges_with_group_as_payload)
>>> from rustworkx.visualization import mpl_draw
>>> mpl_draw(
...     graph,
...     pos={i: (i % 3, i // 3) for i in range(6)},
...     edge_labels=str,
...     with_labels=True,
... )
<Figure size 640x480 with 1 Axes>

(png, hires.png, pdf)

A simple directed graph on which we define our MajoranaOperator.

Here, we have a simple 2 by 3 lattice of Majorana modes which are connected with directional information. We want to group the corresponding operator terms according to their edge labels. The code below shows have that can be done:

>>> from qiskit_fermions.operators import MajoranaOperator
>>> op = MajoranaOperator(
...     [1.0] * 7,
...     [node for edge in edges for node in edge],
...     [0, 2, 4, 6, 8, 10, 12, 14],
... )
>>> op.groups = groups
>>> groups = op.split_out_groups()
>>> for i, g in enumerate(groups):
...     print(f"{i}: {list(sorted(g.iter_terms()))}")
0: [([0, 1], (1+0j)), ([1, 2], (1+0j))]
1: [([3, 0], (1+0j)), ([5, 2], (1+0j))]
2: [([1, 4], (1+0j))]
3: [([4, 3], (1+0j)), ([5, 4], (1+0j))]
#include <qiskit_fermions.h>

uint64_t num_terms = 7;
uint64_t num_modes = 14;
uint32_t modes[14] = {0, 1, 1, 2, 3, 0, 1, 4, 5, 2, 5, 4, 4, 3};
QkComplex67 coeffs[7] = {{1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0},
                         {1.0, 0.0}, {1.0, 0.0}, {1.0, 0.0}};
uint32_t boundaries[8] = {0, 2, 4, 6, 8, 10, 12, 14};
QfMajoranaOperator *op = qf_maj_op_new(num_terms, num_modes, coeffs, modes, boundaries);

uint32_t groups[7] = {0, 0, 1, 2, 1, 3, 3};
qf_maj_op_set_groups(op, groups, num_terms);

QfMajoranaOperator *group_ops[4];
qf_maj_op_split_out_groups(op, group_ops);