Skip to content

Commit

Permalink
Update API to allow QubitMapper where QubitConverter is used. (qiskit…
Browse files Browse the repository at this point in the history
…-community#999)

* Failing tests but stable  QubitConverter and QubitMapper can now be interverted

* Modifications of the mappers map()

* Update API for QubitMapper

* Adds release note

* Small corrections after first feedback.

* Fix indentation:

* Change for map_all to map and map to map_single.

* Updated release note

* Updated release note 2

* Small updates before pushing

* small fix spell

* First review of docstrings

* Other docstrings

* Make black

* Fix mypy

* Fix comments

* fix

* Rolled back the type expansion of the map and convert_match methods, added new tests to cover more cases

* Implement the unwrapper in the _ListOrDict class

* Fix spelling

* Fix spell 2
  • Loading branch information
Anthony-Gandon authored Jan 12, 2023
1 parent bd60a1f commit 617f54a
Show file tree
Hide file tree
Showing 58 changed files with 1,099 additions and 491 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020, 2022.
# (C) Copyright IBM 2020, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -21,7 +21,7 @@
from qiskit.algorithms.eigensolvers import Eigensolver
from qiskit.opflow import PauliSumOp

from qiskit_nature.second_q.mappers import QubitConverter
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper
from qiskit_nature.second_q.operators import SparseLabelOp
from qiskit_nature.second_q.problems import BaseProblem
from qiskit_nature.second_q.problems import EigenstateResult
Expand All @@ -37,15 +37,14 @@ class ExcitedStatesEigensolver(ExcitedStatesSolver):

def __init__(
self,
qubit_converter: QubitConverter,
qubit_converter: QubitConverter | QubitMapper,
solver: Union[Eigensolver, EigensolverFactory],
) -> None:
"""
Args:
qubit_converter: The ``QubitConverter`` 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.
qubit_converter: The ``QubitConverter`` or ``QubitMapper`` to use for mapping and symmetry
reduction.
solver: Minimum Eigensolver or MESFactory object.
"""
self._qubit_converter = qubit_converter
Expand All @@ -72,19 +71,27 @@ def get_qubit_operators(

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

main_operator = self._qubit_converter.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)
if isinstance(self._qubit_converter, QubitConverter):
main_operator = self._qubit_converter.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)

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

if aux_operators is not None:
for name_aux, aux_op in aux_operators.items():
if isinstance(aux_op, SparseLabelOp):
converted_aux_op = self._qubit_converter.convert_match(
aux_op, suppress_none=True
)
if isinstance(self._qubit_converter, QubitConverter):
converted_aux_op = self._qubit_converter.convert_match(
aux_op, suppress_none=True
)
else:
converted_aux_op = self._qubit_converter.map(aux_op)
else:
converted_aux_op = aux_op
if name_aux in aux_ops.keys():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020, 2022.
# (C) Copyright IBM 2020, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -56,7 +56,8 @@ def get_qubit_operators(
problem: BaseProblem,
aux_operators: Optional[dict[str, Union[SparseLabelOp, PauliSumOp]]] = None,
) -> Tuple[PauliSumOp, Optional[dict[str, PauliSumOp]]]:
"""Gets the operator and auxiliary operators, and transforms the provided auxiliary operators.
"""Gets the operator and auxiliary operators, and transforms the provided auxiliary operators
using a ``QubitConverter`` or ``QubitMapper``.
If the user-provided ``aux_operators`` contain a name which clashes with an internally
constructed auxiliary operator, then the corresponding internal operator will be overridden by
the user-provided operator.
Expand Down
92 changes: 66 additions & 26 deletions qiskit_nature/second_q/algorithms/excited_states_solvers/qeom.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from qiskit_nature.second_q.algorithms.excited_states_solvers.excited_states_solver import (
ExcitedStatesSolver,
)
from qiskit_nature.second_q.mappers import QubitConverter
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper
from qiskit_nature.second_q.operators import SparseLabelOp
from qiskit_nature.second_q.problems import (
BaseProblem,
Expand Down Expand Up @@ -158,7 +158,7 @@ def __init__(
self._untapered_qubit_op_main: QubitOperator | None = None

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

Expand Down Expand Up @@ -193,21 +193,35 @@ def get_qubit_operators(

# 1. Convert the main operator (hamiltonian) to QubitOperator and apply two qubit reduction
num_particles = getattr(problem, "num_particles", None)
main_op = self.qubit_converter.convert_only(
main_operator,
num_particles=num_particles,
)

if isinstance(self.qubit_converter, QubitConverter):
main_op = self.qubit_converter.convert_only(
main_operator,
num_particles=num_particles,
)
else:
main_op = self.qubit_converter.map(main_operator)

# aux_ops set to None if the solver does not support auxiliary operators.
aux_ops = None

if self.solver.supports_aux_operators():
self.qubit_converter.force_match(num_particles=num_particles)
aux_ops = self.qubit_converter.convert_match(aux_second_q_operators)
if isinstance(self.qubit_converter, QubitConverter):
self.qubit_converter.force_match(num_particles=num_particles)
aux_ops = self.qubit_converter.convert_match(aux_second_q_operators)

else:
aux_ops = self.qubit_converter.map(aux_second_q_operators)

cast(ListOrDictType[QubitOperator], aux_ops)
if aux_operators is not None:
for name, op in aux_operators.items():
if isinstance(op, (SparseLabelOp)):
converted_aux_op = self.qubit_converter.convert_match(op)
if isinstance(self.qubit_converter, QubitConverter):
converted_aux_op = self.qubit_converter.convert_match(op)
else:
converted_aux_op = self.qubit_converter.map(op)

else:
converted_aux_op = op
if name in aux_ops.keys():
Expand All @@ -224,12 +238,19 @@ def get_qubit_operators(

# 2. Find the z2symmetries, set them in the qubit_converter, and apply the first step of the
# tapering.
_, z2symmetries = self.qubit_converter.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)
if isinstance(self.qubit_converter, QubitConverter):
_, z2symmetries = self.qubit_converter.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)
else:
# TODO: Issue #974 sketches the construction of a Tapered Qubit Mapper which would implement
# the logic of the symmetries. Here, there should be a check for a Tapered Qubit Mapper and
# a similar logic that used above.
untap_main_op = main_op
untap_aux_ops = aux_ops

# 4. If a MinimumEigensolverFactory was provided, then an additional call to get_solver() is
# required.
Expand Down Expand Up @@ -264,7 +285,10 @@ def solve(

# 2. Run ground state calculation with fully tapered custom auxiliary operators
# Note that the solve() method includes the `second_q' auxiliary operators
tap_aux_operators = self.qubit_converter.symmetry_reduce_clifford(untap_aux_ops)
if isinstance(self.qubit_converter, QubitConverter):
tap_aux_operators = self.qubit_converter.symmetry_reduce_clifford(untap_aux_ops)
else:
tap_aux_operators = untap_aux_ops

groundstate_result = self._gsc.solve(problem, tap_aux_operators)
ground_state = groundstate_result.eigenstates[0]
Expand Down Expand Up @@ -383,9 +407,12 @@ def _build_one_sector(available_hopping_ops):
right_op_2 = available_hopping_ops.get(f"Edag_{n_u}")
to_be_computed_list.append((m_u, n_u, left_op_1, right_op_1, right_op_2))

try:
z2_symmetries = self._gsc.qubit_converter.z2symmetries
except AttributeError:
if isinstance(self.qubit_converter, QubitConverter):
try:
z2_symmetries = self.qubit_converter.z2symmetries
except AttributeError:
z2_symmetries = Z2Symmetries([], [], [])
else:
z2_symmetries = Z2Symmetries([], [], [])

if not z2_symmetries.is_empty():
Expand Down Expand Up @@ -583,11 +610,17 @@ def _prepare_expansion_basis(
size = int(len(list(excitation_indices.keys())) // 2)

# Small workaround to apply two_qubit_reduction to a list with convert_match()
z2_symmetries = self.qubit_converter.z2symmetries
self.qubit_converter.force_match(z2symmetries=Z2Symmetries([], [], []))
reduced_hopping_ops = self.qubit_converter.convert_match(hopping_operators)
self.qubit_converter.force_match(z2symmetries=z2_symmetries)
untap_hopping_ops = self.qubit_converter.convert_clifford(reduced_hopping_ops)
if isinstance(self.qubit_converter, QubitConverter):
num_particles = self.qubit_converter.num_particles

reduced_hopping_ops = {}
for hopping_name, hopping_op in hopping_operators.items():
reduced_hopping_ops[hopping_name] = self.qubit_converter._two_qubit_reduce(
hopping_op, num_particles
)
untap_hopping_ops = self.qubit_converter.convert_clifford(reduced_hopping_ops)
else:
untap_hopping_ops = hopping_operators

return untap_hopping_ops, type_of_commutativities, size

Expand Down Expand Up @@ -699,7 +732,10 @@ def _build_excitation_operators(
"""

untap_hopping_ops, _, size = expansion_basis_data
tap_hopping_ops = self.qubit_converter.symmetry_reduce_clifford(untap_hopping_ops)
if isinstance(self.qubit_converter, QubitConverter):
tap_hopping_ops = self.qubit_converter.symmetry_reduce_clifford(untap_hopping_ops)
else:
tap_hopping_ops = untap_hopping_ops

additionnal_measurements = estimate_observables(
self._estimator, reference_state[0], tap_hopping_ops, reference_state[1]
Expand Down Expand Up @@ -843,7 +879,11 @@ def _evaluate_observables_excited_states(
)

# 3. Measure observables
tap_op_aux_op_dict = self.qubit_converter.symmetry_reduce_clifford(op_aux_op_dict)
if isinstance(self.qubit_converter, QubitConverter):
tap_op_aux_op_dict = self.qubit_converter.symmetry_reduce_clifford(op_aux_op_dict)
else:
tap_op_aux_op_dict = op_aux_op_dict

aux_measurements = estimate_observables(
self._estimator, reference_state[0], tap_op_aux_op_dict, reference_state[1]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021, 2022.
# (C) Copyright IBM 2021, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -16,14 +16,14 @@

from typing import Callable, Dict, List, Tuple

from qiskit.opflow import PauliSumOp
from qiskit.opflow import PauliSumOp, Z2Symmetries
from qiskit.tools import parallel_map
from qiskit.utils import algorithm_globals

from qiskit_nature import QiskitNatureError
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
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper


def build_electronic_ops(
Expand All @@ -36,7 +36,7 @@ def build_electronic_ops(
[int, tuple[int, int]],
list[tuple[tuple[int, ...], tuple[int, ...]]],
],
qubit_converter: QubitConverter,
qubit_converter: QubitConverter | QubitMapper,
) -> Tuple[
Dict[str, PauliSumOp],
Dict[str, List[bool]],
Expand All @@ -54,9 +54,10 @@ 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`` 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.
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.
Returns:
A tuple containing the hopping operators, the types of commutativities and the excitation
Expand Down Expand Up @@ -100,7 +101,7 @@ def build_electronic_ops(
def _build_single_hopping_operator(
excitation: Tuple[Tuple[int, ...], Tuple[int, ...]],
num_spatial_orbitals: int,
qubit_converter: QubitConverter,
qubit_converter: QubitConverter | QubitMapper,
) -> Tuple[PauliSumOp, List[bool]]:
label = []
for occ in excitation[0]:
Expand All @@ -109,8 +110,13 @@ def _build_single_hopping_operator(
label.append(f"-_{unocc}")
fer_op = FermionicOp({" ".join(label): 1.0}, num_spin_orbitals=2 * num_spatial_orbitals)

qubit_op = qubit_converter.convert_only(fer_op, qubit_converter.num_particles)
z2_symmetries = qubit_converter.z2symmetries
if isinstance(qubit_converter, QubitConverter):
qubit_op = qubit_converter.convert_only(fer_op, num_particles=qubit_converter.num_particles)
z2_symmetries = qubit_converter.z2symmetries

else:
qubit_op = qubit_converter.map(fer_op)
z2_symmetries = Z2Symmetries([], [], [])

commutativities = []
if not z2_symmetries.is_empty():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from qiskit_nature.second_q.circuit.library import UVCC
from qiskit_nature.second_q.operators import VibrationalOp
from qiskit_nature.second_q.mappers import QubitConverter
from qiskit_nature.second_q.mappers import QubitConverter, QubitMapper


def build_vibrational_ops(
Expand All @@ -34,7 +34,7 @@ def build_vibrational_ops(
[int, tuple[int, int]],
list[tuple[tuple[int, ...], tuple[int, ...]]],
],
qubit_converter: QubitConverter,
qubit_converter: QubitConverter | QubitMapper,
) -> Tuple[
Dict[str, PauliSumOp],
Dict[str, List[bool]],
Expand All @@ -50,9 +50,9 @@ def build_vibrational_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_vibrational_excitations`.
qubit_converter: The ``QubitConverter`` 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.
qubit_converter: The ``QubitConverter`` or ``QubitMapper`` to use for mapping and symmetry
reduction. Note that the ``QubitConverter`` will use its stored Z2 symmetries as basis for
the commutativity information returned by this method.
Returns:
Dict of hopping operators, dict of commutativity types and dict of excitation indices.
"""
Expand Down Expand Up @@ -91,7 +91,7 @@ def build_vibrational_ops(
def _build_single_hopping_operator(
excitation: Tuple[Tuple[int, ...], Tuple[int, ...]],
num_modals: List[int],
qubit_converter: QubitConverter,
qubit_converter: QubitConverter | QubitMapper,
) -> PauliSumOp:
label = []
for occ in excitation[0]:
Expand All @@ -100,6 +100,11 @@ def _build_single_hopping_operator(
label.append(f"-_{VibrationalOp.build_dual_index(num_modals, unocc)}")

vibrational_op = VibrationalOp({" ".join(label): 1}, num_modals)
qubit_op: PauliSumOp = qubit_converter.convert_match(vibrational_op)

qubit_op: PauliSumOp
if isinstance(qubit_converter, QubitConverter):
qubit_op = qubit_converter.convert_match(vibrational_op)
else:
qubit_op = qubit_converter.map(vibrational_op)

return qubit_op
Loading

0 comments on commit 617f54a

Please sign in to comment.