Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dict-based aux_operators #406

Merged
merged 35 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5763cb7
[WIP] naive migration to dict-based aux_operators
mrossinek Oct 15, 2021
ef8ca14
[WIP] extract ListOrDict logic into class
mrossinek Oct 21, 2021
51f2580
Revert ListOrDict integration
mrossinek Oct 21, 2021
f1852a1
Add basic unittest for dict-based aux ops
mrossinek Oct 21, 2021
9fa525c
Refactor
mrossinek Oct 22, 2021
e8a8f97
Extend aux_operators public extension to support dict too
mrossinek Oct 22, 2021
b520b1e
Fix lint
mrossinek Oct 22, 2021
ef06a84
Revert some unnecessary changes
mrossinek Oct 22, 2021
b9904ea
Update docstrings
mrossinek Oct 26, 2021
50fc492
Fix spell
mrossinek Oct 26, 2021
cb4ff7f
Remove unused import
mrossinek Oct 26, 2021
bffba0f
Merge branch 'main' into dict-based-aux-operators
manoelmarques Oct 29, 2021
49d116d
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 3, 2021
250f3df
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 8, 2021
6448811
Fix lint
mrossinek Nov 8, 2021
702812a
Reuse ListOrDict-type alias from Terra
mrossinek Nov 8, 2021
3a518f4
Remove BaseProblem.main_property_name setter
mrossinek Nov 8, 2021
a0cc750
Improve commutation debug message
mrossinek Nov 8, 2021
1c05bd4
Extract new aux_operators interface into global setting
mrossinek Nov 8, 2021
49870c4
Run black
mrossinek Nov 8, 2021
c45c6de
Add DeprecationWarning for list-based aux_operators
mrossinek Nov 8, 2021
c344832
Log warning instead of raising it
mrossinek Nov 8, 2021
f65105f
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 8, 2021
057c4ce
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 10, 2021
93632ef
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 11, 2021
ab3e4d6
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 22, 2021
e21bfa7
Merge main and fix conflicts
manoelmarques Nov 23, 2021
985e2be
Merge branch 'main' into dict-based-aux-operators
manoelmarques Nov 23, 2021
a5de212
Merge branch 'main' into dict-based-aux-operators
manoelmarques Nov 24, 2021
646f0ea
Merge branch 'main' into dict-based-aux-operators
mrossinek Nov 25, 2021
374e30c
Raise error upon aux_operator name clash
mrossinek Nov 25, 2021
58ab2ed
Evaluate aux_operators at ground state during QEOM
mrossinek Nov 25, 2021
1f36750
Merge branch 'main' into dict-based-aux-operators
manoelmarques Nov 25, 2021
727460f
Merge branch 'main' into dict-based-aux-operators
manoelmarques Nov 26, 2021
bd3368b
Merge branch 'main' into dict-based-aux-operators
manoelmarques Nov 29, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
7 changes: 7 additions & 0 deletions qiskit_nature/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,18 @@

"""

from typing import Dict, List, Optional, TypeVar, Union
from .version import __version__
from .exceptions import QiskitNatureError, UnsupportMethodError

# pylint: disable=invalid-name
T = TypeVar("T")
ListOrDictType = Union[List[Optional[T]], Dict[Union[int, str], T]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to do the dict so its different than the minimum eigensolver - i.e it defines keys as just 'str'? The reason was to ensure it can be serialized. So if you define int here there arguably be an impedance mismatch when going down the stack whereby perhaps int will not behave as expected - I'm thinking here more around the runtime and remoting these.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeh you're right. Duplicating this is probably not a good way to go. Unfortunately, Qiskit Terra does not have one ground truth. Instead they have two definitions of ListOrDict in qiskit.algorithms.minimum_eigen_solvers and in qiskit.algorithms.eigen_solvers. While they are currently identical, this is rather error prone and could probably be improved.



__all__ = [
"__version__",
"QiskitNatureError",
"UnsupportMethodError",
"ListOrDictType",
]
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
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,17 +60,20 @@ 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,
dict_based_aux_ops: bool = False,
mrossinek marked this conversation as resolved.
Show resolved Hide resolved
) -> EigenstateResult:
"""Compute Ground and Excited States properties.

Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

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`)

Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand All @@ -77,21 +82,42 @@ def solve(
# get the operator and auxiliary operators, and transform the provided auxiliary operators
# note that ``aux_operators`` contains not only the transformed ``aux_operators`` passed
# by the user but also additional ones from the transformation
second_q_ops = problem.second_q_ops()
second_q_ops = problem.second_q_ops(return_list=not dict_based_aux_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):
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,13 +30,16 @@ class ExcitedStatesSolver(ABC):
def solve(
self,
problem: BaseProblem,
aux_operators: Optional[List[Union[SecondQuantizedOp, PauliSumOp]]] = None,
aux_operators: Optional[ListOrDictType[Union[SecondQuantizedOp, PauliSumOp]]] = None,
dict_based_aux_ops: bool = False,
) -> EigenstateResult:
r"""Compute the excited states energies of the molecule that was supplied via the driver.

Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand Down
14 changes: 12 additions & 2 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,8 @@ 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,
dict_based_aux_ops: bool = False,
) -> EigenstateResult:
"""Run the excited-states calculation.

Expand All @@ -89,6 +91,8 @@ def solve(
Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand All @@ -105,7 +109,13 @@ def solve(
groundstate_result = self._gsc.solve(problem)

# 2. Prepare the excitation operators
self._untapered_qubit_op_main = self._gsc._qubit_converter.map(problem.second_q_ops()[0])
second_q_ops = problem.second_q_ops(return_list=not dict_based_aux_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.map(main_second_q_op)
matrix_operators_dict, size = self._prepare_matrix_operators(problem)

# 3. Evaluate eom operators
Expand Down
42 changes: 35 additions & 7 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,38 +138,64 @@ 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,
dict_based_aux_ops: bool = False,
) -> "AdaptVQEResult":
"""Computes the ground state.

Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

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`)

Returns:
An AdaptVQEResult which is an ElectronicStructureResult but also includes runtime
information about the AdaptVQE algorithm like the number of iterations, finishing
criterion, and the final maximum gradient.
"""
second_q_ops = problem.second_q_ops()
second_q_ops = problem.second_q_ops(return_list=not dict_based_aux_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):
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
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,17 +67,20 @@ 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,
dict_based_aux_ops: bool = False,
) -> EigenstateResult:
"""Compute Ground State properties.

Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

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`)

Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand All @@ -84,21 +89,42 @@ def solve(
# get the operator and auxiliary operators, and transform the provided auxiliary operators
# note that ``aux_ops`` contains not only the transformed ``aux_operators`` passed by the
# user but also additional ones from the transformation
second_q_ops = problem.second_q_ops()
second_q_ops = problem.second_q_ops(return_list=not dict_based_aux_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):
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,13 +44,16 @@ 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,
dict_based_aux_ops: bool = False,
) -> EigenstateResult:
"""Compute the ground state energy of the molecule that was supplied via the driver.

Args:
problem: a class encoding a problem to be solved.
aux_operators: Additional auxiliary operators to evaluate.
dict_based_aux_ops: if True, the auxiliary operators will be stored in a `dict` rather
than `list`.

Returns:
An interpreted :class:`~.EigenstateResult`. For more information see also
Expand Down
Loading