Skip to content

Commit

Permalink
QubitConverter deprecation (#1073)
Browse files Browse the repository at this point in the history
* Deprecate the QubitConverter class

* Some deprecations around the TaperedQubitMapper workflow

* Implement the _ListOrDict.wrap utility

* fix: do not drop deprecated arguments which have no replacement

* Deprecate all public QubitConverter naming in favor of QubitMapper

* Add release note

* Fix edge cases around settings.use_pauli_sum_op

Closes #1076

* Add HF bitstring test for ParityMapper with builtin 2-qubit reduction

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
mrossinek and mergify[bot] committed Feb 27, 2023
1 parent 376b444 commit 53a3e98
Show file tree
Hide file tree
Showing 59 changed files with 1,356 additions and 592 deletions.
1 change: 1 addition & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ quantized
quantumcircuit
quartic
qubit
qubitmapper
qubits
qutrit
radians
Expand Down
2 changes: 0 additions & 2 deletions qiskit_nature/deprecation.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,6 @@ def _rename_kwargs(version, qualname, func_name, kwargs, kwarg_map, additional_m

if new_arg:
kwargs[new_arg] = kwargs.pop(old_arg)
else:
kwargs.pop(old_arg)


def deprecate_arguments(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from qiskit_nature.second_q.operators import SparseLabelOp
from qiskit_nature.second_q.problems import BaseProblem
from qiskit_nature.second_q.problems import EigenstateResult
from qiskit_nature.deprecation import warn_deprecated_type
from qiskit_nature.deprecation import deprecate_arguments, warn_deprecated_type

from .excited_states_solver import ExcitedStatesSolver
from .eigensolver_factories import EigensolverFactory
Expand All @@ -35,19 +35,32 @@
class ExcitedStatesEigensolver(ExcitedStatesSolver):
"""The calculation of excited states via an Eigensolver algorithm."""

@deprecate_arguments(
"0.6.0",
{"qubit_converter": "qubit_mapper"},
additional_msg=(
". Additionally, the QubitConverter type in the qubit_mapper argument is deprecated "
"and support for it will be removed together with the qubit_converter argument."
),
)
def __init__(
self,
qubit_converter: QubitConverter | QubitMapper,
qubit_mapper: QubitConverter | QubitMapper,
solver: Eigensolver | EigensolverFactory,
*,
qubit_converter: QubitConverter | QubitMapper | None = None,
) -> None:
# pylint: disable=unused-argument
"""
Args:
qubit_converter: The ``QubitConverter`` or ``QubitMapper`` to use for mapping and symmetry
reduction.
qubit_mapper: The ``QubitMapper`` or ``QubitConverter`` (use of the latter is
deprecated) to use for mapping.
solver: Minimum Eigensolver or MESFactory object.
qubit_converter: DEPRECATED The ``QubitConverter`` or ``QubitMapper`` to use for mapping
and symmetry reduction.
"""
self._qubit_converter = qubit_converter
self._qubit_mapper = qubit_mapper
self._solver = solver

@property
Expand All @@ -71,17 +84,17 @@ def get_qubit_operators(

num_particles = getattr(problem, "num_particles", None)

if isinstance(self._qubit_converter, QubitConverter):
main_operator = self._qubit_converter.convert(
if isinstance(self._qubit_mapper, QubitConverter):
main_operator = self._qubit_mapper.convert(
main_second_q_op,
num_particles=num_particles,
sector_locator=problem.symmetry_sector_locator,
)
aux_ops = self._qubit_converter.convert_match(aux_second_q_ops)
aux_ops = self._qubit_mapper.convert_match(aux_second_q_ops)

else:
main_operator = self._qubit_converter.map(main_second_q_op)
aux_ops = self._qubit_converter.map(aux_second_q_ops)
main_operator = self._qubit_mapper.map(main_second_q_op)
aux_ops = self._qubit_mapper.map(aux_second_q_ops)

if aux_operators is not None:
for name_aux, aux_op in aux_operators.items():
Expand All @@ -93,12 +106,12 @@ def get_qubit_operators(
new_type="SparsePauliOp",
)
if isinstance(aux_op, SparseLabelOp):
if isinstance(self._qubit_converter, QubitConverter):
converted_aux_op = self._qubit_converter.convert_match(
if isinstance(self._qubit_mapper, QubitConverter):
converted_aux_op = self._qubit_mapper.convert_match(
aux_op, suppress_none=True
)
else:
converted_aux_op = self._qubit_converter.map(aux_op)
converted_aux_op = self._qubit_mapper.map(aux_op)
else:
converted_aux_op = aux_op

Expand Down
86 changes: 50 additions & 36 deletions qiskit_nature/second_q/algorithms/excited_states_solvers/qeom.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
EigenstateResult,
ElectronicStructureResult,
)
from qiskit_nature.deprecation import warn_deprecated_type
from qiskit_nature.deprecation import deprecate_property, warn_deprecated_type

from .qeom_electronic_ops_builder import build_electronic_ops
from .qeom_vibrational_ops_builder import build_vibrational_ops
Expand Down Expand Up @@ -236,9 +236,15 @@ def __init__(
self.tol = tol

@property
@deprecate_property("0.6.0", new_name="qubit_mapper")
def qubit_converter(self) -> QubitConverter | QubitMapper:
"""Returns the qubit_converter object defined in the ground state solver."""
return self._gsc.qubit_converter
"""DEPRECATED Returns the qubit_converter object defined in the ground state solver."""
return self._gsc.qubit_mapper

@property
def qubit_mapper(self) -> QubitConverter | QubitMapper:
"""Returns the qubit_mapper object defined in the ground state solver."""
return self._gsc.qubit_mapper

@property
def solver(self) -> MinimumEigensolver | MinimumEigensolverFactory:
Expand All @@ -248,21 +254,21 @@ def solver(self) -> MinimumEigensolver | MinimumEigensolverFactory:
def _map_operators(
self, operators: SparseLabelOp | ListOrDictType[SparseLabelOp]
) -> PauliSumOp | ListOrDictType[PauliSumOp]:
if isinstance(self.qubit_converter, QubitConverter):
mapped_ops = self.qubit_converter.convert_match(operators)
elif isinstance(self.qubit_converter, TaperedQubitMapper):
mapped_ops = self.qubit_converter.map_clifford(operators)
if isinstance(self.qubit_mapper, QubitConverter):
mapped_ops = self.qubit_mapper.convert_match(operators)
elif isinstance(self.qubit_mapper, TaperedQubitMapper):
mapped_ops = self.qubit_mapper.map_clifford(operators)
else:
mapped_ops = self.qubit_converter.map(operators)
mapped_ops = self.qubit_mapper.map(operators)
return mapped_ops

def _taper_operators(
self, operators: PauliSumOp | ListOrDictType[PauliSumOp]
) -> PauliSumOp | ListOrDictType[PauliSumOp]:
if isinstance(self.qubit_converter, QubitConverter):
tapered_ops = self.qubit_converter.symmetry_reduce_clifford(operators)
elif isinstance(self.qubit_converter, TaperedQubitMapper):
tapered_ops = self.qubit_converter.taper_clifford(operators, suppress_none=True)
if isinstance(self.qubit_mapper, QubitConverter):
tapered_ops = self.qubit_mapper.symmetry_reduce_clifford(operators)
elif isinstance(self.qubit_mapper, TaperedQubitMapper):
tapered_ops = self.qubit_mapper.taper_clifford(operators, suppress_none=True)
else:
tapered_ops = operators
return tapered_ops
Expand Down Expand Up @@ -293,13 +299,13 @@ def get_qubit_operators(
num_particles = getattr(problem, "num_particles", None)

# 1. Convert the main operator (hamiltonian) to a Qubit Operator and apply two qubit reduction
if isinstance(self.qubit_converter, QubitConverter):
self.qubit_converter.force_match(num_particles=num_particles)
main_op = self.qubit_converter.convert_only(main_operator, num_particles=num_particles)
elif isinstance(self.qubit_converter, TaperedQubitMapper):
main_op = self.qubit_converter.map_clifford(main_operator)
if isinstance(self.qubit_mapper, QubitConverter):
self.qubit_mapper.force_match(num_particles=num_particles)
main_op = self.qubit_mapper.convert_only(main_operator, num_particles=num_particles)
elif isinstance(self.qubit_mapper, TaperedQubitMapper):
main_op = self.qubit_mapper.map_clifford(main_operator)
else:
main_op = self.qubit_converter.map(main_operator)
main_op = self.qubit_mapper.map(main_operator)

# 3. Convert the auxiliary operators.
# aux_ops set to None if the solver does not support auxiliary operators.
Expand Down Expand Up @@ -333,23 +339,23 @@ def get_qubit_operators(
# The custom op overrides the default op if the key is already taken.
aux_ops[name] = converted_aux_op

# 4. Find the z2symmetries, set them in the qubit_converter, and apply the first step of the
# tapering.
if isinstance(self.qubit_converter, QubitConverter):
_, z2symmetries = self.qubit_converter.find_taper_op(
if isinstance(self.qubit_mapper, QubitConverter):
# 4. Find the z2symmetries, set them in the QubitConverter, and apply the first step of
# the tapering.
_, z2symmetries = self.qubit_mapper.find_taper_op(
main_op, problem.symmetry_sector_locator
)
self.qubit_converter.force_match(z2symmetries=z2symmetries)
untap_main_op = self.qubit_converter.convert_clifford(main_op)
untap_aux_ops = self.qubit_converter.convert_clifford(aux_ops)
self.qubit_mapper.force_match(z2symmetries=z2symmetries)
untap_main_op = self.qubit_mapper.convert_clifford(main_op)
untap_aux_ops = self.qubit_mapper.convert_clifford(aux_ops)
else:
untap_main_op = main_op
untap_aux_ops = aux_ops

# 5. If a MinimumEigensolverFactory was provided, then an additional call to get_solver() is
# required.
if isinstance(self.solver, MinimumEigensolverFactory):
self._gsc._solver = self.solver.get_solver(problem, self.qubit_converter) # type: ignore
self._gsc._solver = self.solver.get_solver(problem, self.qubit_mapper) # type: ignore

return untap_main_op, untap_aux_ops

Expand Down Expand Up @@ -380,8 +386,13 @@ def solve(
untap_aux_ops_sumop, # Auxiliary observables
) = self.get_qubit_operators(problem, aux_operators)

untap_main_op = untap_main_op_sumop.primitive
untap_aux_ops = {key: op.primitive for key, op in untap_aux_ops_sumop.items()}
untap_main_op = untap_main_op_sumop
if isinstance(untap_main_op, PauliSumOp):
untap_main_op = untap_main_op.primitive
untap_aux_ops = {
key: op.primitive if isinstance(op, PauliSumOp) else op
for key, op in untap_aux_ops_sumop.items()
}

# 2. Run ground state calculation with fully tapered custom auxiliary operators
# Note that the solve() method includes the `second_q' auxiliary operators
Expand Down Expand Up @@ -459,13 +470,13 @@ def _build_hopping_ops(
problem.num_spatial_orbitals,
(problem.num_alpha, problem.num_beta),
self.excitations,
self.qubit_converter,
self.qubit_mapper,
)
elif isinstance(problem, VibrationalStructureProblem):
return build_vibrational_ops(
problem.num_modals,
self.excitations,
self.qubit_converter,
self.qubit_mapper,
)
else:
raise NotImplementedError(
Expand Down Expand Up @@ -504,12 +515,12 @@ def _build_one_sector(available_hopping_ops):
to_be_computed_list.append((m_u, n_u, left_op_1, right_op_1, right_op_2))

if (
isinstance(self.qubit_converter, (QubitConverter, TaperedQubitMapper))
and not self.qubit_converter.z2symmetries.is_empty()
isinstance(self.qubit_mapper, (QubitConverter, TaperedQubitMapper))
and not self.qubit_mapper.z2symmetries.is_empty()
):

combinations = itertools.product(
[1, -1], repeat=len(self.qubit_converter.z2symmetries.symmetries)
[1, -1], repeat=len(self.qubit_mapper.z2symmetries.symmetries)
)
for targeted_tapering_values in combinations:
logger.info(
Expand Down Expand Up @@ -699,12 +710,15 @@ def _prepare_expansion_basis(
hopping_operators, type_of_commutativities, excitation_indices = data
size = int(len(list(excitation_indices.keys())) // 2)

if isinstance(self.qubit_converter, QubitConverter):
untap_hopping_ops = self.qubit_converter.convert_clifford(hopping_operators)
if isinstance(self.qubit_mapper, QubitConverter):
untap_hopping_ops = self.qubit_mapper.convert_clifford(hopping_operators)
else:
untap_hopping_ops = hopping_operators

untap_hopping_ops_sparse = {key: op.primitive for key, op in untap_hopping_ops.items()}
untap_hopping_ops_sparse = {
key: op.primitive if isinstance(op, PauliSumOp) else op
for key, op in untap_hopping_ops.items()
}

return untap_hopping_ops_sparse, type_of_commutativities, size

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@
from qiskit_nature.second_q.circuit.library import UCC
from qiskit_nature.second_q.operators import FermionicOp
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper, TaperedQubitMapper
from qiskit_nature.deprecation import deprecate_arguments


@deprecate_arguments(
"0.6.0",
{"qubit_converter": "qubit_mapper"},
additional_msg=(
". Additionally, the QubitConverter type in the qubit_mapper argument is deprecated "
"and support for it will be removed together with the qubit_converter argument."
),
)
def build_electronic_ops(
num_spatial_orbitals: int,
num_particles: tuple[int, int],
Expand All @@ -37,12 +46,15 @@ def build_electronic_ops(
[int, tuple[int, int]],
list[tuple[tuple[int, ...], tuple[int, ...]]],
],
qubit_converter: QubitConverter | QubitMapper,
qubit_mapper: QubitConverter | QubitMapper,
*,
qubit_converter: QubitConverter | QubitMapper | None = None,
) -> tuple[
dict[str, PauliSumOp | SparsePauliOp],
dict[str, list[bool]],
dict[str, tuple[tuple[int, ...], tuple[int, ...]]],
]:
# pylint: disable=unused-argument
"""Builds the product of raising and lowering operators (basic excitation operators)
Args:
Expand All @@ -55,10 +67,12 @@ def build_electronic_ops(
- and finally a callable which can be used to specify a custom list of excitations.
For more details on how to write such a function refer to the default method,
:meth:`generate_fermionic_excitations`.
qubit_converter: The ``QubitConverter`` or ``QubitMapper`` to use for mapping and symmetry
reduction. The Z2 symmetries stored in this instance are the basis for the commutativity
information returned by this method. These symmetries are set to `None` when a
``QubitMapper`` is used.
qubit_mapper: The ``QubitMapper`` or ``QubitConverter`` (use of the latter is deprecated) to
use for mapping.
qubit_converter: DEPRECATED The ``QubitConverter`` or ``QubitMapper`` to use for mapping and
symmetry reduction. The Z2 symmetries stored in this instance are the basis for the
commutativity information returned by this method. These symmetries are set to ``None``
when a ``QubitMapper`` is used.
Returns:
A tuple containing the hopping operators, the types of commutativities and the excitation
Expand All @@ -67,7 +81,7 @@ def build_electronic_ops(

num_alpha, num_beta = num_particles

ansatz = UCC(num_spatial_orbitals, (num_alpha, num_beta), excitations, qubit_converter)
ansatz = UCC(num_spatial_orbitals, (num_alpha, num_beta), excitations, qubit_mapper)
excitations_list = ansatz._get_excitation_list()
size = len(excitations_list)

Expand All @@ -88,7 +102,7 @@ def build_electronic_ops(
result = parallel_map(
_build_single_hopping_operator,
to_be_executed_list,
task_args=(num_spatial_orbitals, qubit_converter),
task_args=(num_spatial_orbitals, qubit_mapper),
num_processes=algorithm_globals.num_processes,
)

Expand All @@ -102,7 +116,7 @@ def build_electronic_ops(
def _build_single_hopping_operator(
excitation: tuple[tuple[int, ...], tuple[int, ...]],
num_spatial_orbitals: int,
qubit_converter: QubitConverter | QubitMapper,
qubit_mapper: QubitConverter | QubitMapper,
) -> tuple[PauliSumOp | SparsePauliOp, list[bool]]:
label = []
for occ in excitation[0]:
Expand All @@ -111,16 +125,16 @@ def _build_single_hopping_operator(
label.append(f"-_{unocc}")
fer_op = FermionicOp({" ".join(label): 1.0}, num_spin_orbitals=2 * num_spatial_orbitals)

if isinstance(qubit_converter, QubitConverter):
qubit_op = qubit_converter.convert_only(fer_op, num_particles=qubit_converter.num_particles)
symmetries_for_commutativity = qubit_converter.z2symmetries.symmetries
elif isinstance(qubit_converter, TaperedQubitMapper):
qubit_op = qubit_converter.map_clifford(fer_op)
if isinstance(qubit_mapper, QubitConverter):
qubit_op = qubit_mapper.convert_only(fer_op, num_particles=qubit_mapper.num_particles)
symmetries_for_commutativity = qubit_mapper.z2symmetries.symmetries
elif isinstance(qubit_mapper, TaperedQubitMapper):
qubit_op = qubit_mapper.map_clifford(fer_op)
# Because the clifford conversion was already done, the commutativity information are based
# on the single qubit pauli objects.
symmetries_for_commutativity = qubit_converter.z2symmetries.sq_paulis
symmetries_for_commutativity = qubit_mapper.z2symmetries.sq_paulis
else:
qubit_op = qubit_converter.map(fer_op)
qubit_op = qubit_mapper.map(fer_op)
symmetries_for_commutativity = []

commutativities = []
Expand Down
Loading

0 comments on commit 53a3e98

Please sign in to comment.