Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Agrupar términos de operadores: usa la estructura del operador

La agrupación es una de las operaciones de qiskit_fermions.operators.terms, que reúne rutinas que particionan los términos de un operador según su estructura.

Como se describe en la guía de operadores, los operadores pueden almacenar un arreglo de grupos, una parte opcional de la estructura de datos dispersa que asocia cada término con un índice de grupo. Los términos que comparten el mismo índice de grupo forman un grupo. Esto te permite aprovechar sistemáticamente la estructura en el procesamiento posterior.

Al agrupar términos relacionados (ya sea por propiedades físicas, relaciones algebraicas o simetrías específicas del problema), puedes obtener varios beneficios:

  • Síntesis de circuitos optimizada: los términos agrupados transportan información específica del problema a lo largo de la pila, lo que facilita la optimización de los circuitos sintetizados. Esto reduce la profundidad del circuito y el número de puertas.

  • Preservación de la estructura física: los grupos codifican estructura significativa del problema original (por ejemplo, patrones de interacción o simetrías) que puede usarse para descomposiciones con mayor sentido físico.

  • Selección de conjuntos de flujo: la agrupación puede codificar de forma natural conjuntos de flujo y otras estructuras de teoría de grafos relevantes para la simulación cuántica, como se muestra en [1].

  • Mayor estabilidad algorítmica: como se demuestra en la rutina SqDRIFT, algunos algoritmos pueden beneficiarse de una mayor estabilidad o rendimiento gracias a la preservación de propiedades físicas.

  • Factores de la fórmula de producto: una fórmula de producto fermiónica divide un operador en sus grupos, de modo que la agrupación determina tanto el costo como el error de Trotter de una evolución temporal. Consulta Sintetizar una puerta de evolución en el espacio fermiónico.

Uso​

La forma más sencilla de definir una agrupación de operadores es establecer el atributo groups del operador de destino. A continuación se muestra un ejemplo de agrupación de términos según los conjuntos de flujo de línea, como se muestra en la Figura 1c de [1].

Sugerencia

El módulo qiskit_fermions.operators.terms.grouping ofrece métodos de conveniencia para identificar automáticamente la estructura de un operador y asignar los índices de grupo en consecuencia.

>>> 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 ... with 1 Axes>

A simple directed graph on which to define a MajoranaOperator.

Se trata de una red sencilla de dos por tres de modos de Majorana conectados por aristas dirigidas. Cada arista representa un término del operador que actúa sobre los dos modos conectados. El objetivo es agrupar estos términos según las etiquetas de sus aristas (mostradas en el grafo). Los términos conectados por aristas con la misma etiqueta se colocan en el mismo grupo. El siguiente código muestra cómo hacerlo:

>>> 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))]

Analizar una agrupación​

Los grupos no prescriben ningún significado propio: el arreglo de grupos registra qué términos van juntos, nunca por qué. Ninguna rutina de esta biblioteca informa si una agrupación es «correcta», porque no existe una única noción de corrección con la que comparar. Sin embargo, los consumidores posteriores sí asumen propiedades de una agrupación, y las funciones de esta sección te permiten comprobar de antemano tal suposición en lugar de pagar por ella en cada llamada.

La convención más común es que cada grupo sea hermítico por separado. Eso es lo que hace posibles las fórmulas de producto aleatorizadas fermiónicas: un ap†aqa^\dagger_p a_q aislado con p≠qp \neq q no es hermítico, por lo que su evolución temporal no es unitaria y no puede muestrearse por sí solo. Emparejarlo con aq†apa^\dagger_q a_p produce un grupo que sí puede serlo.

>>> from qiskit_fermions.operators import FermionOperator
>>> from qiskit_fermions.operators.terms.grouping import (
... group_coeff_means,
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>>
>>> # a conjugate pair, plus one unpaired term. The operator is built from the sparse
>>> # arrays rather than from a dictionary, because only the former fixes the term order
>>> # that the group indices below are paired with.
>>> pair_and_single = FermionOperator(
... [1.0, 1.0, 1.0],
... [True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3],
... [0, 2, 4, 6],
... )
>>> pair_and_single.groups = [0, 0, 1]
>>>
>>> groups_are_hermitian(pair_and_single) # group 1 is unpaired
[True, False]
>>> group_coeff_means(pair_and_single)
[1.0, 1.0]

Se exige un índice de grupo por término​

El arreglo de grupos contiene un índice por término, y asignar uno de longitud distinta lanza un error. Nada posterior vuelve a comprobar esta invariante, por lo que se hace cumplir donde los índices entran en el operador: un arreglo demasiado corto descartaría en silencio los términos finales, y uno demasiado largo inventaría grupos que ningún término contiene.

>>> pair_and_single.groups = [0, 0] # one index short, on a three-term operator
Traceback (most recent call last):
ValueError: expected one group index per term, but got 2 for 3 terms

Borrar los índices de grupo asignando None siempre está permitido.

Las dos comprobaciones son independientes​

groups_have_uniform_coeffs() informa de si los coeficientes de un grupo coinciden, que es la suposición que hace group_coeff_means() cuando promedia magnitudes. No es una forma más débil de la comprobación de hermiticidad: ninguna de las dos implica la otra, en ninguna dirección.

>>> from qiskit_fermions.operators.terms.grouping import (
... groups_are_hermitian,
... groups_have_uniform_coeffs,
... )
>>> # uniform coefficients, but not Hermitian: the terms are not each other's adjoint
>>> uniform = FermionOperator(
... [1.0, 1.0],
... [True, False, True, False],
... [0, 1, 2, 3],
... [0, 2, 4],
... )
>>> uniform.groups = [0, 0]
>>> groups_have_uniform_coeffs(uniform), groups_are_hermitian(uniform)
([True], [False])
>>> # Hermitian, but with mixed magnitudes: two conjugate pairs at different scales
>>> mixed = FermionOperator(
... [1.0, 1.0, 5.0, 5.0],
... [True, False, True, False, True, False, True, False],
... [0, 1, 1, 0, 2, 3, 3, 2],
... [0, 2, 4, 6, 8],
... )
>>> mixed.groups = [0, 0, 0, 0]
>>> groups_are_hermitian(mixed), groups_have_uniform_coeffs(mixed)
([True], [False])

Por defecto se comparan las magnitudes, porque eso es lo que promedia group_coeff_means(). Un par conjugado hermítico con coeficientes complejos tiene magnitudes iguales pero coeficientes distintos, por lo que satisface solo la forma por defecto.

>>> conjugate = FermionOperator(
... [1.0j, -1.0j],
... [True, False, True, False],
... [0, 1, 1, 0],
... [0, 2, 4],
... )
>>> conjugate.groups = [0, 0]
>>> groups_have_uniform_coeffs(conjugate)
[True]
>>> groups_have_uniform_coeffs(conjugate, abs=False)
[False]
Nota

groups_are_hermitian() hereda la garantía unilateral de is_hermitian(): una entrada True siempre es fiable, mientras que una entrada False solo es fiable para los tipos de operador cuya forma normal es una auténtica forma canónica. Consulta el tipo de operador concreto para saber cuál es el caso.

[1] (1,2)

A. Gandon et al., Stabilizer-based quantum simulation of fermion dynamics with local qubit encodings, arXiv:2512.11418v2.