Guía de Migración del QubitConverter#

La clase QubitConverter ha quedado obsoleta como parte de la versión 0.6 de Qiskit Nature. En lugar de encapsular esta clase alrededor de una de las clases QubitMapper disponibles, esta última ahora se puede usar directamente. Para soportar esto, los mapeadores se han mejorado y ahora se entregan con una funcionalidad más avanzada lista para usar.

Para simplificar esta guía, nos centraremos en ejemplos sobre cómo reemplazar casos de uso específicos de QubitConverter. Para obtener más detalles sobre los mapeadores, te sugerimos que consultes el tutorial sobre mapeadores de qubits.

Configuración#

Para los ejemplos de esta guía, siempre usaremos el siguiente FermionicOp:

from qiskit_nature.second_q.drivers import PySCFDriver

driver = PySCFDriver(atom="H 0 0 0; H 0 0 0.735")
problem = driver.run()

hamiltonian = problem.hamiltonian.second_q_op()

for label, coeff in sorted(hamiltonian.items()):
    print(f"{coeff:+.8f} * '{label}'")
+0.33785508 * '+_0 +_0 -_0 -_0'
+0.09046560 * '+_0 +_0 -_1 -_1'
+0.09046560 * '+_0 +_1 -_0 -_1'
+0.33229087 * '+_0 +_1 -_1 -_0'
+0.33785508 * '+_0 +_2 -_2 -_0'
+0.09046560 * '+_0 +_2 -_3 -_1'
+0.09046560 * '+_0 +_3 -_2 -_1'
+0.33229087 * '+_0 +_3 -_3 -_0'
-1.25633907 * '+_0 -_0'
+0.33229087 * '+_1 +_0 -_0 -_1'
+0.09046560 * '+_1 +_0 -_1 -_0'
+0.09046560 * '+_1 +_1 -_0 -_0'
+0.34928686 * '+_1 +_1 -_1 -_1'
+0.33229087 * '+_1 +_2 -_2 -_1'
+0.09046560 * '+_1 +_2 -_3 -_0'
+0.09046560 * '+_1 +_3 -_2 -_0'
+0.34928686 * '+_1 +_3 -_3 -_1'
-0.47189601 * '+_1 -_1'
+0.33785508 * '+_2 +_0 -_0 -_2'
+0.09046560 * '+_2 +_0 -_1 -_3'
+0.09046560 * '+_2 +_1 -_0 -_3'
+0.33229087 * '+_2 +_1 -_1 -_2'
+0.33785508 * '+_2 +_2 -_2 -_2'
+0.09046560 * '+_2 +_2 -_3 -_3'
+0.09046560 * '+_2 +_3 -_2 -_3'
+0.33229087 * '+_2 +_3 -_3 -_2'
-1.25633907 * '+_2 -_2'
+0.33229087 * '+_3 +_0 -_0 -_3'
+0.09046560 * '+_3 +_0 -_1 -_2'
+0.09046560 * '+_3 +_1 -_0 -_2'
+0.34928686 * '+_3 +_1 -_1 -_3'
+0.33229087 * '+_3 +_2 -_2 -_3'
+0.09046560 * '+_3 +_2 -_3 -_2'
+0.09046560 * '+_3 +_3 -_2 -_2'
+0.34928686 * '+_3 +_3 -_3 -_3'
-0.47189601 * '+_3 -_3'

PauliSumOp vs. SparsePauliOp#

Una nota más: el módulo qiskit.opflow quedará obsoleto en el futuro. Para pasar de la clase PauliSumOp y su uso generalizado al módulo qiskit.quantum_info y su clase SparsePauliOp, ahora puedes establecer el valor de use_pauli_sum_op en False. Esto se convertirá en el valor predeterminado en una versión posterior.

Para asegurarnos de que podemos confiar consistentemente en el uso del SparsePauliOp en las siguientes partes de esta guía, estamos aplicando esta configuración aquí:

from qiskit_nature import settings

settings.use_pauli_sum_op = False

Como consecuencia de esta próxima obsoletización, Qiskit Nature ahora es totalmente compatible con el uso de instancias SparsePauliOp en todos los lugares que anteriormente permitían objetos PauliSumOp. Para aprovechar esto, no es necesario cambiar la configuración anterior. Por lo tanto, se recomienda que cambies a usar SparsePauliOp.

Para obtener información más detallada sobre la obsoletización de qiskit.opflow, consulta su guía de migración.

Casos más simples#

En los casos más simples, todo lo que hiciste fue pasar un objeto QubitMapper al QubitConverter. Por ejemplo, algo así:

from qiskit_nature.second_q.mappers import JordanWignerMapper, QubitConverter

mapper = JordanWignerMapper()
converter = QubitConverter(mapper)

Todo lo que necesitas hacer para actualizar tu código es dejar de hacerlo y simplemente pasar el objeto mapper del ejemplo anterior al lugar donde lo estabas usando antes.

Si estuvieras trabajando directamente con algún SparseLabelOp así:

qubit_op = converter.convert(hamiltonian)

for pauli, coeff in sorted(qubit_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-0.81054798 * IIII
+0.17218393 * IIIZ
-0.22575349 * IIZI
+0.12091263 * IIZZ
+0.17218393 * IZII
+0.16892754 * IZIZ
+0.16614543 * IZZI
+0.04523280 * XXXX
+0.04523280 * XXYY
+0.04523280 * YYXX
+0.04523280 * YYYY
-0.22575349 * ZIII
+0.16614543 * ZIIZ
+0.17464343 * ZIZI
+0.12091263 * ZZII

Ahora deberías usar directamente el mapper de nuevo, pero su método se llama .map:

qubit_op = mapper.map(hamiltonian)

for pauli, coeff in sorted(qubit_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-0.81054798 * IIII
+0.17218393 * IIIZ
-0.22575349 * IIZI
+0.12091263 * IIZZ
+0.17218393 * IZII
+0.16892754 * IZIZ
+0.16614543 * IZZI
+0.04523280 * XXXX
+0.04523280 * XXYY
+0.04523280 * YYXX
+0.04523280 * YYYY
-0.22575349 * ZIII
+0.16614543 * ZIIZ
+0.17464343 * ZIZI
+0.12091263 * ZZII

Esto es probablemente lo que estabas haciendo cuando trabajabas con cualquiera de los siguientes mapeadores:

El ParityMapper#

Sin embargo, cuando estabas usando el ParityMapper, podías usar la opción two_qubit_reduction=True del QubitConverter. Esta capacidad, que siempre ha sido exclusiva del ParityMapper, ahora está integrada directamente en dicho mapeador. Entonces, si estuvieras haciendo algo en este sentido:

from qiskit_nature.second_q.mappers import ParityMapper

converter = QubitConverter(ParityMapper(), two_qubit_reduction=True)

reduced_op = converter.convert(hamiltonian, num_particles=problem.num_particles)

for pauli, coeff in sorted(reduced_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-1.05237325 * II
+0.39793742 * IZ
+0.18093120 * XX
-0.39793742 * ZI
-0.01128010 * ZZ

El código equivalente ahora tiene el siguiente aspecto:

mapper = ParityMapper(num_particles=problem.num_particles)

reduced_op = mapper.map(hamiltonian)

for pauli, coeff in sorted(reduced_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-1.05237325 * II
+0.39793742 * IZ
+0.18093120 * XX
-0.39793742 * ZI
-0.01128010 * ZZ

Z2Symmetries#

Finalmente, la clase QubitConverter también soportó una mayor reducción de los recursos de qubit al explotar las Z2Symmetries implementada en el módulo qiskit.opflow. Aunque extendimos la clase obsoleta para soportar también la implementación actualizada en el módulo qiskit.quantum_info (que tiene el mismo nombre: Z2Symmetries), deberías ahora usar el nuevo TaperedQubitMapper en su lugar.

En el pasado, habrías habilitado esto así:

mapper = JordanWignerMapper()
converter = QubitConverter(mapper, z2symmetry_reduction="auto")

lo que luego usaría symmetry_sector_locator() para encontrar el sector de simetría del espacio de Hilbert en el que se encuentra la solución de tu problema. Esto solo fue soportado por el ElectronicStructureProblem. A continuación se muestra un ejemplo rápido:

tapered_op = converter.convert(
    hamiltonian,
    num_particles=problem.num_particles,
    sector_locator=problem.symmetry_sector_locator,
)

for pauli, coeff in sorted(tapered_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-1.04109314 * I
+0.18093120 * X
-0.79587485 * Z

Ahora, todo lo que necesitas hacer es usar el método get_tapered_mapper() y proporcionar el mapeador original que te gustaría encapsular:

tapered_mapper = problem.get_tapered_mapper(mapper)

tapered_op = tapered_mapper.map(hamiltonian)

for pauli, coeff in sorted(tapered_op.label_iter()):
    print(f"{coeff.real:+.8f} * {pauli}")
-1.04109314 * I
+0.18093120 * X
-0.79587485 * Z

Si no estabas utilizando la detección automática de simetría, sino que proporcionabas un sector de simetría personalizado, puedes construir tu instancia de TaperedQubitMapper directamente. Asegúrate de consultar su documentación para obtener más detalles.