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

Conversation

mrossinek
Copy link
Member

@mrossinek mrossinek commented Oct 22, 2021

Summary

This leverages Qiskit/qiskit#6870 by adding support for dict-based aux_operators.
For now, we definitely need to support both ways of adding aux_operators but I suggest that we deprecate the list-based method. See upcoming comments below.

Comments

Note, that my implementation of #26 will require this functionality.

Closes #396

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.
Copy link
Member Author

@mrossinek mrossinek left a comment

Choose a reason for hiding this comment

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

Some explanatory comments

from qiskit_nature.mappers.second_quantization import QubitMapper

from qiskit_nature.operators.second_quantization import SecondQuantizedOp

from .utils import ListOrDict
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not yet sure about the location of this utility class.

Copy link
Member

Choose a reason for hiding this comment

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

Since the dictionaries need to be passed on down to min eig solvers in Terra, which also define a type, maybe the type should come from there somehow rather than having to be independently defined here too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right now, this ListOrDict class is simply a wrapper which simplifies the iteration over whatever original type the operator were in. I did this because otherwise a lot of code would be duplicated in the internals of the QubitConverter.
In the end of the converter, the objects get's unwrapped anyways, before continuing with the rest of the stack.

That said, I agree that if we had a utility class such as this one on the Terra level, we would not need to do the unwrapping and could simply use that type everywhere. However, introducing such a class on the Terra level + using it as the type of how to pass operators back and forth would require exposing this utility class as part of the public API. I am not sure whether that is something we (or the Terra devs) want..

Comment on lines +254 to +256
qubit_ops: ListOrDict[PauliSumOp] = ListOrDict()
for name, second_q_op in iter(wrapped_second_q_ops):
qubit_ops[name] = self._map(second_q_op)
Copy link
Member Author

Choose a reason for hiding this comment

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

FYI: this is a good example show-casing the usage of the ListOrDict utility class. We avoid the duplication of many isinstance checks against list or dict

@@ -44,7 +46,9 @@ def __init__(self) -> None:
]
]
] = None
self._aux_operator_eigenvalues: Optional[List[float]] = None
self._aux_operator_eigenvalues: Optional[
List[ListOrDictType[Tuple[complex, complex]]]
Copy link
Member Author

Choose a reason for hiding this comment

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

Note the fix of the return type aligned with Qiskit/qiskit#7144

test/test_end2end_with_vqe.py Outdated Show resolved Hide resolved
@@ -102,7 +103,7 @@ def from_legacy_driver_result(cls, result: LegacyDriverResult) -> "AngularMoment
qmol.num_molecular_orbitals * 2,
)

def second_q_ops(self) -> List[FermionicOp]:
def second_q_ops(self, return_list: bool = True) -> ListOrDictType[FermionicOp]:
Copy link
Member Author

Choose a reason for hiding this comment

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

I am suggesting this once on the first occurrence in the Files tab of this PR:

I would like to add a DeprecationWarning in the case of return_list = True, stating that in 3 months from now, the default will be changed to False, immediately implying that Qiskit Nature switches to dict-based operators as the default across the entire stack.
In 3 months from now, we can then add a new DeprecationWarning, stating that another 3 months later (i.e. 6 months from now), this keyword will be removed and, therefore, support for list-based aux operators will be dropped from the properties framework (I would argue to drop it from the entire stack but this can be discussed separately).

@woodsp-ibm @pbark I'm eager to hear your comments on this proposal 🙂

Copy link
Member

Choose a reason for hiding this comment

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

Since there is a flag, that the user could switch over to dicts to immediately go the future I guess its possibly to do this in one deprecation move rather than two. Earlier in one of my comments I noted about perhaps a single global control flag - having it in one place seemed easier to change behavior without changing the overall API. Maybe that could emit a single warning at some point if its set to list behavior rather than dict. Another thought, about the 'main' operator - basically this is done by having some known name on the problem right that represents the key for the main operator - what is in index 0 currently of the list. I did not look too closely at your ListDict class but was wondering if it could be done via that instead so its more self-contained. Like having a method to add main operator and then one for getting it so you can know the key or you can do ops.main_operator for instance.

Copy link
Member Author

Choose a reason for hiding this comment

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

I was also playing around with self-containing the name of the main operator on my ListOrDict utility. However, this runs into integrates that class very tightly into our stack and makes it a core type rather than a simple utility for iterating our operators. I noted in a reply above, that this would work better if we were to extract such a class into Terra because otherwise we run into issues with the interface to them.

For now, I would consider keeping it as is, unless you believe that the Terra devs are open to such a change. (Note, that it would expose the ListOrDict class as the expected type for all aux_operators in the public API)

Copy link
Member

Choose a reason for hiding this comment

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

As far as Terra is concerned there the main operator and the other operators that we wish to measure are separate - it is only in Nature where they were combined into one list/dict. I didn't look too much at the class - since as you say it needs to be unpacked/packed across the interface I guess it remains in Nature as such. The ListOrDict type however is common even if this class ends up being restricted to Nature.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes I see. So you would keep it self-contained to Nature but would still prefer it to be more integrated with the stack?

Copy link
Member

Choose a reason for hiding this comment

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

Since the intent is to eliminate List support I'm not sure an integration makes sense. The basic type, given whatever packing/upacking we do, needs to be consistent however we achieve that. The utility class I guess is a temporary artifact for Nature for the transition to dict only right - I will admit I did not look at it in detail.

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.

from qiskit_nature.mappers.second_quantization import QubitMapper

from qiskit_nature.operators.second_quantization import SecondQuantizedOp

from .utils import ListOrDict
Copy link
Member

Choose a reason for hiding this comment

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

Since the dictionaries need to be passed on down to min eig solvers in Terra, which also define a type, maybe the type should come from there somehow rather than having to be independently defined here too.

@@ -102,7 +103,7 @@ def from_legacy_driver_result(cls, result: LegacyDriverResult) -> "AngularMoment
qmol.num_molecular_orbitals * 2,
)

def second_q_ops(self) -> List[FermionicOp]:
def second_q_ops(self, return_list: bool = True) -> ListOrDictType[FermionicOp]:
Copy link
Member

Choose a reason for hiding this comment

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

Since there is a flag, that the user could switch over to dicts to immediately go the future I guess its possibly to do this in one deprecation move rather than two. Earlier in one of my comments I noted about perhaps a single global control flag - having it in one place seemed easier to change behavior without changing the overall API. Maybe that could emit a single warning at some point if its set to list behavior rather than dict. Another thought, about the 'main' operator - basically this is done by having some known name on the problem right that represents the key for the main operator - what is in index 0 currently of the list. I did not look too closely at your ListDict class but was wondering if it could be done via that instead so its more self-contained. Like having a method to add main operator and then one for getting it so you can know the key or you can do ops.main_operator for instance.

@mrossinek mrossinek marked this pull request as ready for review November 8, 2021 15:36
@coveralls
Copy link

coveralls commented Nov 22, 2021

Pull Request Test Coverage Report for Build 1516462477

  • 237 of 298 (79.53%) changed or added relevant lines in 25 files are covered.
  • 4 unchanged lines in 2 files lost coverage.
  • Overall coverage decreased (-0.2%) to 85.342%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit_nature/converters/second_quantization/utils/list_or_dict.py 17 18 94.44%
qiskit_nature/properties/second_quantization/vibrational/occupied_modals.py 8 9 88.89%
qiskit_nature/properties/second_quantization/vibrational/vibrational_energy.py 4 5 80.0%
qiskit_nature/algorithms/excited_states_solvers/qeom.py 5 7 71.43%
qiskit_nature/properties/second_quantization/vibrational/vibrational_structure_driver_result.py 10 14 71.43%
qiskit_nature/converters/second_quantization/qubit_converter.py 30 39 76.92%
qiskit_nature/algorithms/ground_state_solvers/ground_state_eigensolver.py 11 22 50.0%
qiskit_nature/algorithms/excited_states_solvers/excited_states_eigensolver.py 7 23 30.43%
qiskit_nature/algorithms/ground_state_solvers/adapt_vqe.py 6 22 27.27%
Files with Coverage Reduction New Missed Lines %
qiskit_nature/properties/second_quantization/vibrational/vibrational_structure_driver_result.py 1 87.8%
qiskit_nature/drivers/psi4d/psi4driver.py 3 85.47%
Totals Coverage Status
Change from base Build 1516460021: -0.2%
Covered Lines: 9671
Relevant Lines: 11332

💛 - Coveralls

manoelmarques
manoelmarques previously approved these changes Nov 23, 2021
Copy link
Member Author

@mrossinek mrossinek left a comment

Choose a reason for hiding this comment

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

@woodsp-ibm shared some more comments with me offline:

  • aux_operators are not being handled at all in QEOM. Arguably we should at least pass them on to the GSC
  • name conflicts for dict-based aux_operators provided by the user are not being handled at all, either. As things are currently, the user-provided operators would overwrite the internally created ones. I will add a guard against such name collisions and raise a warning. For the time being that should be sufficient (obviously it being documented, too)

@mrossinek mrossinek merged commit 80a23f4 into qiskit-community:main Dec 1, 2021
@mrossinek mrossinek deleted the dict-based-aux-operators branch December 1, 2021 14:00
Anthony-Gandon pushed a commit to Anthony-Gandon/qiskit-nature that referenced this pull request May 25, 2023
* [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 <Manoel.Marques@ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Leverage Dict-based aux_operators
4 participants