The QubitConverter Migration Guide#

The QubitConverter class has been deprecated as part of version 0.6 of Qiskit Nature. Instead of wrapping this class around one of the available QubitMapper classes, the latter can now instead be used directly. To support this, the mappers have been improved and now ship with more advanced functionality out of the box.

To keep this guide simple, we will focus on examples on how to replace specific use cases of the QubitConverter. For more details on the mappers we suggest you check out the tutorial on qubit mappers.

Setup#

For the examples in this guide, we will always be using the following 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#

One more note: the qiskit.opflow module is going to be deprecated in the future. In order to transition from the PauliSumOp class and its widespread use to the qiskit.quantum_info module and its SparsePauliOp class, you can now set the value of use_pauli_sum_op to False. This will become the default in a later release.

To ensure that we can consistently rely on using the SparsePauliOp in the following parts of this guide, we are applying this setting here:

from qiskit_nature import settings

settings.use_pauli_sum_op = False

As a consequence of this upcoming deprecation, Qiskit Nature now fully supports the use SparsePauliOp instances in all places which previously allowed PauliSumOp objects. In order to leverage this, it is not required to change the setting above. Thus, it is recommended that you switch to using SparsePauliOp.

For more in-depth information about the qiskit.opflow deprecation please refer to its migration guide.

Simplest cases#

In the simplest cases, all you did was pass a QubitMapper object into the QubitConverter. For example, somewhat like this:

from qiskit_nature.second_q.mappers import JordanWignerMapper, QubitConverter

mapper = JordanWignerMapper()
converter = QubitConverter(mapper)

All you need to do in order to update your code, is stop doing that and simply pass the mapper object from the example above into whichever place you were using it before.

If you were working directly with some SparseLabelOp like so:

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

You should now directly use the mapper again, but its method is called .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

This is likely what you were doing when you were working with any of the following mappers:

The ParityMapper#

However, when you were using the ParityMapper, you were able to use the two_qubit_reduction=True option of the QubitConverter. This ability, which has always been unique to the ParityMapper, is now directly built into said mapper. So if you were doing something along these lines:

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

The equivalent code now looks like the following:

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#

Finally, the QubitConverter class also supported further reduction of qubit resources by exploiting the Z2Symmetries implemented in the qiskit.opflow module. Although we did extend the deprecated class to also support the updated implementation in the qiskit.quantum_info module (which has the same name: Z2Symmetries), you should now use the new TaperedQubitMapper instead.

In the past, you would have enabled this like so:

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

which would then later use symmetry_sector_locator() to find the symmetry sector of the Hilbert space in which the solution of your problem lies. This was only supported by the ElectronicStructureProblem. Below is a quick example:

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

Now, all you need to do is the use the get_tapered_mapper() method and provide the original mapper which you would like to wrap:

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

If you were not using the automatic symmetry detection but instead provided a custom symmetry sector, you can construct your TaperedQubitMapper instance directly. Be sure to check out its documentation for more details.