Skip to content

Commit

Permalink
Dict-based aux_operators (qiskit-community#406)
Browse files Browse the repository at this point in the history
* [WIP] naive migration to dict-based aux_operators

* [WIP] extract ListOrDict logic into class

* Revert ListOrDict integration

We need to properly deprecate the old function signature of
`second_q_ops` before we can fully switch to dict-based aux operators.
For this, I introduce a new keyword argument `return_list` which
defaults to the old way of list-based aux operators.

In the following commits I will add DeprecationWarnings announcing a
change of this default as well as unittests to assert the correct
behavior of the dict-based aux operators.

* Add basic unittest for dict-based aux ops

* Refactor

* Extend aux_operators public extension to support dict too

* Fix lint

* Revert some unnecessary changes

* Update docstrings

* Fix spell

* Remove unused import

* Fix lint

* Reuse ListOrDict-type alias from Terra

* Remove BaseProblem.main_property_name setter

This property should only ever be set during construction of a certain
problem type. Removing the setter ensures this scenario.

* Improve commutation debug message

* Extract new aux_operators interface into global setting

* Run black

* Add DeprecationWarning for list-based aux_operators

* Log warning instead of raising it

* Raise error upon aux_operator name clash

* Evaluate aux_operators at ground state during QEOM

Co-authored-by: Manoel Marques <[email protected]>
  • Loading branch information
mrossinek and manoelmarques authored Dec 1, 2021
1 parent 03bd69f commit ab1447e
Show file tree
Hide file tree
Showing 27 changed files with 686 additions and 187 deletions.
1 change: 1 addition & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ arxiv
asparagine
aspartic
atol
attr
autosummary
avogadro
äquivalenzverbot
Expand Down
10 changes: 9 additions & 1 deletion qiskit_nature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@
"""

from .version import __version__
from qiskit.algorithms.minimum_eigen_solvers.minimum_eigen_solver import (
ListOrDict as ListOrDictType,
)

from .exceptions import QiskitNatureError, UnsupportMethodError
from .settings import settings
from .version import __version__


__all__ = [
"__version__",
"ListOrDictType",
"QiskitNatureError",
"UnsupportMethodError",
"settings",
]
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@

"""The calculation of excited states via an Eigensolver algorithm"""

from typing import List, Union, Optional
from typing import Union, Optional

from qiskit.algorithms import Eigensolver
from qiskit.opflow import PauliSumOp

from qiskit_nature import ListOrDictType, QiskitNatureError
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.converters.second_quantization.utils import ListOrDict
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.problems.second_quantization import BaseProblem
from qiskit_nature.results import EigenstateResult
Expand Down Expand Up @@ -58,7 +60,7 @@ def solver(self, solver: Union[Eigensolver, EigensolverFactory]) -> None:
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
) -> EigenstateResult:
"""Compute Ground and Excited States properties.
Expand All @@ -67,8 +69,12 @@ def solve(
aux_operators: Additional auxiliary operators to evaluate.
Raises:
NotImplementedError: If an operator in ``aux_operators`` is not of type
``FermionicOperator``.
ValueError: if the grouped property object returned by the driver does not contain a
main property as requested by the problem being solved (`problem.main_property_name`)
QiskitNatureError: if the user-provided `aux_operators` contain a name which clashes
with an internally constructed auxiliary operator. Note: the names used for the
internal auxiliary operators correspond to the `Property.name` attributes which
generated the respective operators.
Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand All @@ -79,19 +85,46 @@ def solve(
# by the user but also additional ones from the transformation
second_q_ops = problem.second_q_ops()

aux_second_q_ops: ListOrDictType[SecondQuantizedOp]
if isinstance(second_q_ops, list):
main_second_q_op = second_q_ops[0]
aux_second_q_ops = second_q_ops[1:]
elif isinstance(second_q_ops, dict):
name = problem.main_property_name
main_second_q_op = second_q_ops.pop(name, None)
if main_second_q_op is None:
raise ValueError(
f"The main `SecondQuantizedOp` associated with the {name} property cannot be "
"`None`."
)
aux_second_q_ops = second_q_ops

main_operator = self._qubit_converter.convert(
second_q_ops[0],
main_second_q_op,
num_particles=problem.num_particles,
sector_locator=problem.symmetry_sector_locator,
)
aux_ops = self._qubit_converter.convert_match(second_q_ops[1:])
aux_ops = self._qubit_converter.convert_match(aux_second_q_ops)

if aux_operators is not None:
for aux_op in aux_operators:
wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict(
aux_operators
)
for name, aux_op in iter(wrapped_aux_operators):
if isinstance(aux_op, SecondQuantizedOp):
aux_ops.append(self._qubit_converter.convert_match(aux_op, True))
converted_aux_op = self._qubit_converter.convert_match(aux_op, True)
else:
aux_ops.append(aux_op)
converted_aux_op = aux_op
if isinstance(aux_ops, list):
aux_ops.append(converted_aux_op)
elif isinstance(aux_ops, dict):
if name in aux_ops.keys():
raise QiskitNatureError(
f"The key '{name}' is already taken by an internally constructed "
"auxliliary operator! Please use a different name for your custom "
"operator."
)
aux_ops[name] = converted_aux_op

if isinstance(self._solver, EigensolverFactory):
# this must be called after transformation.transform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
""" The excited states calculation interface """

from abc import ABC, abstractmethod
from typing import List, Optional, Union
from typing import Optional, Union

from qiskit.opflow import PauliSumOp

from qiskit_nature import ListOrDictType
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.problems.second_quantization import BaseProblem
from qiskit_nature.results import EigenstateResult
Expand All @@ -29,7 +30,7 @@ class ExcitedStatesSolver(ABC):
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
) -> EigenstateResult:
r"""Compute the excited states energies of the molecule that was supplied via the driver.
Expand Down
14 changes: 10 additions & 4 deletions qiskit_nature/algorithms/excited_states_solvers/qeom.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
PauliSumOp,
)

from qiskit_nature import ListOrDictType
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.problems.second_quantization import BaseProblem
from qiskit_nature.results import EigenstateResult
Expand Down Expand Up @@ -79,7 +80,7 @@ def excitations(self, excitations: Union[str, List[List[int]]]) -> None:
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[SecondQuantizedOp]] = None,
aux_operators: Optional[ListOrDictType[SecondQuantizedOp]] = None,
) -> EigenstateResult:
"""Run the excited-states calculation.
Expand All @@ -102,13 +103,18 @@ def solve(
)

# 1. Run ground state calculation
groundstate_result = self._gsc.solve(problem)
groundstate_result = self._gsc.solve(problem, aux_operators)

# 2. Prepare the excitation operators
second_q_ops = problem.second_q_ops()
if isinstance(second_q_ops, list):
main_second_q_op = second_q_ops[0]
elif isinstance(second_q_ops, dict):
main_second_q_op = second_q_ops.pop(problem.main_property_name)

self._untapered_qubit_op_main = self._gsc.qubit_converter.convert_only(
problem.second_q_ops()[0], problem.num_particles
main_second_q_op, problem.num_particles
)

matrix_operators_dict, size = self._prepare_matrix_operators(problem)

# 3. Evaluate eom operators
Expand Down
47 changes: 41 additions & 6 deletions qiskit_nature/algorithms/ground_state_solvers/adapt_vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@
from qiskit.circuit import QuantumCircuit
from qiskit.opflow import OperatorBase, PauliSumOp
from qiskit.utils.validation import validate_min
from qiskit_nature import ListOrDictType
from qiskit_nature.exceptions import QiskitNatureError
from qiskit_nature.circuit.library import UCC
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.converters.second_quantization.utils import ListOrDict
from qiskit_nature.problems.second_quantization import BaseProblem
from qiskit_nature.results import ElectronicStructureResult

Expand Down Expand Up @@ -136,7 +138,7 @@ def _check_cyclicity(indices: List[int]) -> bool:
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
) -> "AdaptVQEResult":
"""Computes the ground state.
Expand All @@ -147,6 +149,12 @@ def solve(
Raises:
QiskitNatureError: if a solver other than VQE or a ansatz other than UCCSD is provided
or if the algorithm finishes due to an unforeseen reason.
ValueError: if the grouped property object returned by the driver does not contain a
main property as requested by the problem being solved (`problem.main_property_name`)
QiskitNatureError: if the user-provided `aux_operators` contain a name which clashes
with an internally constructed auxiliary operator. Note: the names used for the
internal auxiliary operators correspond to the `Property.name` attributes which
generated the respective operators.
Returns:
An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime
Expand All @@ -155,19 +163,46 @@ def solve(
"""
second_q_ops = problem.second_q_ops()

aux_second_q_ops: ListOrDictType[SecondQuantizedOp]
if isinstance(second_q_ops, list):
main_second_q_op = second_q_ops[0]
aux_second_q_ops = second_q_ops[1:]
elif isinstance(second_q_ops, dict):
name = problem.main_property_name
main_second_q_op = second_q_ops.pop(name, None)
if main_second_q_op is None:
raise ValueError(
f"The main `SecondQuantizedOp` associated with the {name} property cannot be "
"`None`."
)
aux_second_q_ops = second_q_ops

self._main_operator = self._qubit_converter.convert(
second_q_ops[0],
main_second_q_op,
num_particles=problem.num_particles,
sector_locator=problem.symmetry_sector_locator,
)
aux_ops = self._qubit_converter.convert_match(second_q_ops[1:])
aux_ops = self._qubit_converter.convert_match(aux_second_q_ops)

if aux_operators is not None:
for aux_op in aux_operators:
wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict(
aux_operators
)
for name, aux_op in iter(wrapped_aux_operators):
if isinstance(aux_op, SecondQuantizedOp):
aux_ops.append(self._qubit_converter.convert_match(aux_op, True))
converted_aux_op = self._qubit_converter.convert_match(aux_op, True)
else:
aux_ops.append(aux_op)
converted_aux_op = aux_op
if isinstance(aux_ops, list):
aux_ops.append(converted_aux_op)
elif isinstance(aux_ops, dict):
if name in aux_ops.keys():
raise QiskitNatureError(
f"The key '{name}' is already taken by an internally constructed "
"auxliliary operator! Please use a different name for your custom "
"operator."
)
aux_ops[name] = converted_aux_op

if isinstance(self._solver, MinimumEigensolverFactory):
vqe = self._solver.get_solver(problem, self._qubit_converter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
from qiskit.algorithms import MinimumEigensolver
from qiskit.opflow import OperatorBase, PauliSumOp, StateFn, CircuitSampler

from qiskit_nature import ListOrDictType, QiskitNatureError
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.converters.second_quantization.utils import ListOrDict
from qiskit_nature.problems.second_quantization import BaseProblem
from qiskit_nature.results import EigenstateResult
from .ground_state_solver import GroundStateSolver
Expand Down Expand Up @@ -65,7 +67,7 @@ def returns_groundstate(self) -> bool:
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
) -> EigenstateResult:
"""Compute Ground State properties.
Expand All @@ -74,8 +76,12 @@ def solve(
aux_operators: Additional auxiliary operators to evaluate.
Raises:
NotImplementedError: If an operator in ``aux_operators`` is not of type
``FermionicOperator``.
ValueError: if the grouped property object returned by the driver does not contain a
main property as requested by the problem being solved (`problem.main_property_name`)
QiskitNatureError: if the user-provided `aux_operators` contain a name which clashes
with an internally constructed auxiliary operator. Note: the names used for the
internal auxiliary operators correspond to the `Property.name` attributes which
generated the respective operators.
Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand All @@ -86,19 +92,46 @@ def solve(
# user but also additional ones from the transformation
second_q_ops = problem.second_q_ops()

aux_second_q_ops: ListOrDictType[SecondQuantizedOp]
if isinstance(second_q_ops, list):
main_second_q_op = second_q_ops[0]
aux_second_q_ops = second_q_ops[1:]
elif isinstance(second_q_ops, dict):
name = problem.main_property_name
main_second_q_op = second_q_ops.pop(name, None)
if main_second_q_op is None:
raise ValueError(
f"The main `SecondQuantizedOp` associated with the {name} property cannot be "
"`None`."
)
aux_second_q_ops = second_q_ops

main_operator = self._qubit_converter.convert(
second_q_ops[0],
main_second_q_op,
num_particles=problem.num_particles,
sector_locator=problem.symmetry_sector_locator,
)
aux_ops = self._qubit_converter.convert_match(second_q_ops[1:])
aux_ops = self._qubit_converter.convert_match(aux_second_q_ops)

if aux_operators is not None:
for aux_op in aux_operators:
wrapped_aux_operators: ListOrDict[Union[SecondQuantizedOp, PauliSumOp]] = ListOrDict(
aux_operators
)
for name, aux_op in iter(wrapped_aux_operators):
if isinstance(aux_op, SecondQuantizedOp):
aux_ops.append(self._qubit_converter.convert_match(aux_op, True))
converted_aux_op = self._qubit_converter.convert_match(aux_op, True)
else:
aux_ops.append(aux_op)
converted_aux_op = aux_op
if isinstance(aux_ops, list):
aux_ops.append(converted_aux_op)
elif isinstance(aux_ops, dict):
if name in aux_ops.keys():
raise QiskitNatureError(
f"The key '{name}' is already taken by an internally constructed "
"auxliliary operator! Please use a different name for your custom "
"operator."
)
aux_ops[name] = converted_aux_op

if isinstance(self._solver, MinimumEigensolverFactory):
# this must be called after transformation.transform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from qiskit.result import Result
from qiskit.opflow import OperatorBase, PauliSumOp

from qiskit_nature import ListOrDictType
from qiskit_nature.operators.second_quantization import SecondQuantizedOp
from qiskit_nature.converters.second_quantization import QubitConverter
from qiskit_nature.problems.second_quantization import BaseProblem
Expand All @@ -43,7 +44,7 @@ def __init__(self, qubit_converter: QubitConverter) -> None:
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
) -> EigenstateResult:
"""Compute the ground state energy of the molecule that was supplied via the driver.
Expand Down
Loading

0 comments on commit ab1447e

Please sign in to comment.