.. _grouping_explanation: Group operator terms: use the operator structure ================================================ Grouping is one of the operations under :mod:`qiskit_fermions.operators.terms`, which collects routines that partition the terms of an operator based on their structure. As described in the :ref:`operators guide `, operators can store a **groups array**, an optional part of the sparse data structure that associates each term with a group index. Terms sharing the same group index form a group. This lets you use systematically use of the structure in downstream processing. By grouping related terms (whether by physical properties, algebraic relationships, or problem-specific symmetries), you can unlock several benefits: - **Optimized circuit synthesis**: Grouped terms carry problem-specific information through the stack, enabling simpler optimization of the synthesized circuits. This results in reduced circuit depth and gate count. - **Physical structure preservation**: Groups encode meaningful structure from the original problem (for example, interaction patterns or symmetries) that can be used for more physically meaningful decompositions. - **Flow set selection**: Grouping can naturally encode flow sets and other graph-theoretic structures relevant to quantum simulation, as shown in [1]_. - **Improved algorithmic stability**: As demonstrated in the :ref:`SqDRIFT routine `, some algorithms can benefit from improved stability or performance due to the preservation of physical properties. Usage ----- The simplest way to define an operator grouping is by setting the target operator's ``groups`` attribute. Below is an example for grouping terms according to the *line flow sets* as shown in Figure 1c of [1]_. .. hint:: The :mod:`qiskit_fermions.operators.terms.grouping` module provides convenience methods for automatically identifying structure in an operator and assigning group indices accordingly. .. plot:: :alt: A simple directed graph on which to define a MajoranaOperator. :context: close-figs :include-source: >>> 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, ... )
This is a simple two-by-three lattice of Majorana modes connected by directed edges. Each edge represents a term in the operator acting on the two connected modes. The goal is to group these terms according to their edge labels (shown on the graph). Terms connected by edges with the same label are placed in the same group. The following code shows how that can be done: .. tab-set-code:: .. code-block:: python >>> from qiskit_fermions.operators import MajoranaOperator >>> >>> # Construct operator directly using sparse arrays >>> # 7 terms, each with coefficient 1.0, modes from edge endpoints >>> op = MajoranaOperator( ... coeffs=[1.0] * 7, ... modes=[node for edge in edges for node in edge], ... boundaries=[0, 2, 4, 6, 8, 10, 12, 14], ... ) >>> >>> # Assign group indices to terms (groups is part of the sparse data structure) >>> op.groups = groups >>> >>> # Partition operator by groups >>> grouped_ops = op.split_out_groups() >>> >>> # Inspect each group >>> for i, g in enumerate(grouped_ops): ... print(f"Group {i}: {list(sorted(g.iter_terms()))}") Group 0: [([0, 1], (1+0j)), ([1, 2], (1+0j))] Group 1: [([3, 0], (1+0j)), ([5, 2], (1+0j))] Group 2: [([1, 4], (1+0j))] Group 3: [([4, 3], (1+0j)), ([5, 4], (1+0j))] .. code-block:: c #include 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, NULL, 0, group_ops); .. [1] A. Gandon et al., Stabilizer-based quantum simulation of fermion dynamics with local qubit encodings, `arXiv:2512.11418v2 `_.