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

Allow custom Property integration #312

Closed
mrossinek opened this issue Aug 11, 2021 · 5 comments
Closed

Allow custom Property integration #312

mrossinek opened this issue Aug 11, 2021 · 5 comments
Assignees

Comments

@mrossinek
Copy link
Member

What is the expected enhancement?

After #303 will be merged, it will not yet be possible to add custom Property objects to the GroupedProperty object used within the BaseProblem implementations. A major reason for holding off on this, is that we really need Qiskit/qiskit#6772 to be implemented. Otherwise, a lot of edge cases can break down when we allow arbitrary sets of properties.

As of Qiskit Nature 0.2.0, the tutorial on the Property framework will explain how to implement a custom Property and contain a note linking to this issue. A workaround by means of passing the operators of interest as aux_operators to the solver will be included.

@mrossinek
Copy link
Member Author

This issue will become resolvable once #406 is merged. However, even then, using the feature proposed in this issue will always require dictionary-based aux_operators. The reason being that only then will it become possible to refactor the GroupedElectronicProperty and GroupedVibrationalProperty classes such that they are no longer abstract (in contrast, currently they are because the second_q_ops() method cannot be implemented on their level).

Anyways, details aside, once we can ensure dict-based aux_operators, we will be able to add this feature. In terms of interface, I am envisioning that custom properties can be added via the BaseProblem.__init__ methods. It will be the user's responsibility to ensure the correct Property implementation.

Such a use case will always be limited in potential with regards to arbitrary transformer support, since a transformer may not know how to handle a certain property. This caveat should be possible to workaround in most scenarios though, especially via leveraging the electronic.IntegralProperty base class (which all electronic transformers know how to handle).

mrossinek added a commit to mrossinek/qiskit-nature that referenced this issue Feb 10, 2022
The README test previously failed because the iteration over the
auxiliary operator observables in the ElectronicStructureResult is
currently unable to handle the lack of certain properties which have
always been evaluated for legacy reasons (AngularMomentum,
Magnetization).
Even if we were to default them to an empty list instead of None, while
the zip command would execute normally, no results would be printed
since zip stops after the shortest length.

That being said, fixing ElectronicStructureResult is not the solution
right now in any case, since a user would be unable to manually request
the computation of AngularMomentum and Magnetization before we resolve
the issue ý¿¿¼£�qiskit-community#312
Thus, this commit reverts the exclusion of these auxiliary operators in
the case of `settings.dict_aux_operators`.
mrossinek added a commit that referenced this issue Feb 10, 2022
* WIP: HDF5-integration for property framework

This is an initial proof-of-concept implementation.
This needs to be reviewed extensively.

* Clean up interface

* refactor: let ElectronicIntegrals derive from PseudoProperty

* refactor: let Molecule derive from PseudoProperty

* feat: implement DriverMetadata HDF5 methods

* feat: add toggle to include PseudoProperty objects in GroupedProperty iteration

* feat: implement ParticleNumber HDF5 methods

* feat: implement AngularMomentum and Magnetization HDF5 methods

* feat: implement ElectronicBasisTransform HDF5 methods

* fix: lint

* fix: fix unittests

* remove comment

* fix: spell

* feat: also store Qiskit Nature version

* handle potential error cases in Property.import_and_build_from_hdf5

* Fix copyright

* feat: introduce individual version numbers per Property class

* WIP: HDF5 integration into vibrational properties

* Fix copyright

* fix: property tutorial

* Update typehints

* Run black

* Remove @AbstractMethod from Property.from_hdf5

This needs to be removed in order to ensure that the stable tutorials
remain working.

* Add more missing typehints

* refactor: introduce HDF5Storable Protocol

* refactor: remove PseudoProperty in favor of Interpretable Protocol

The PseudoProperty class effectively removes everything which defines
the Property class (the interpret method). So instead of having such a
pseudo-class, all previous PseudoProperty subclass are now directly
Property subclasses and the `interpret()` method existence is handled
via the `Interpretable` Protocol.

* Fix linters

* Fix ASTransformer caught error type

* Fix copyright

* Fix imports

* More guards against Property type

* More import fixes

* fix: property tutorial

* fix: ElectronicStructureDriverResult.__str__

* refactor: remove Property base class where not needed

* test: basic hdf5 method unittests

* test: *StructureDriverResult from_hdf5 methods

* docs: HDF5 documentation

* refactor: formally deprecate PseudoProperty class

* docs: actual HDF5 save and load examples

* Fix spell

* fix: avoid name clash with multiple atoms of same kind

* fix: ElectronicEnergy.from_hdf5 group access

* Update unittest HDF5 resource

* fix: ParticleNumber.from_hdf5 occupation dataset access

* Fix #519

This is actually required for the matrices loaded during
ElectronicIntegrals.from_hdf5 to be in the correct order!

* Update qiskit_nature/properties/property.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Update docs

* Rename save_to_hdf5(..., force -> replace)

* refactor: fix DriverMetadata HDF5 attribute names

* refactor: make from_hdf5 a staticmethod

* feat: store Molecule.units in HDF5

* fix: update expected HDF5 result

* docs: include backwards compatibility expectations

* feat: add skip_unreadable_data toggle to HDF5 loading methods

* Fix spell

Apparently Sphinx can now use `kwds` instead of `kwargs`

* docs: explicitly request error raising

* docs: use `:func:` instead of `:class:`

* docs: ensure *StructureDriverResults are documented

* refactor: enforce keyword arguments in hdf5 module

* Update driver return types

While the previous return types were not wrong, for documentation
purposes using these concrete implementations is a bit nicer.

* Run black

* Add reno

* feat: use Molecule.units during from_hdf5

* test: refactor _hdf5 method tests

Instead of hard-coding the expected HDF5 file structure, we test that
`to_hdf5` and `from_hdf5` work consistently with each other.
The `test_to_hdf5` tests ensure that this method executes correctly
(i.e. without errors). The `from_hdf5` tests ensure that first writing
and subsequently reading a property from a file produces an identical
instance.

In the future, once version numbers of certain properties may increase,
we should store HDF5 files and compare those against expected instances.

* fix: README test

The README test previously failed because the iteration over the
auxiliary operator observables in the ElectronicStructureResult is
currently unable to handle the lack of certain properties which have
always been evaluated for legacy reasons (AngularMomentum,
Magnetization).
Even if we were to default them to an empty list instead of None, while
the zip command would execute normally, no results would be printed
since zip stops after the shortest length.

That being said, fixing ElectronicStructureResult is not the solution
right now in any case, since a user would be unable to manually request
the computation of AngularMomentum and Magnetization before we resolve
the issue ý¿¿¼£�#312
Thus, this commit reverts the exclusion of these auxiliary operators in
the case of `settings.dict_aux_operators`.

* refactor: use only public API in PropertyTest

* refactor: update type hints

* fix: update TestVibrationalStructureDriverResult to G16 Rev.C01

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
@mrossinek
Copy link
Member Author

I gave this some more thought and wanted to query @pbark and @woodsp-ibm for the following ideas:

  • aux_properties (or whatever we may call them) should be supplied BaseProblem.__init__
    The reason being that the properties are blueprints for operators which should be able to undergo the transformer. Hence, these should be supplied at problem level construction rather than during .solve() like the aux_operators.

  • We should support both, instances and generators, to be passed into aux_properties.
    Simply accepting instances is pretty straight forward. These would then simply be inserted in the GroupedProperty which was returned by the driver.
    However, also accepting generators which take the GroupedProperty into which they will be inserted as an input, will allow things like dynamic construction of the correct system size (prominent examples are AngularMomentum and Magnetization which require num_spin_orbitals as input)

  • How should we handle scenarios in which a user provides an auxiliary property which is already present in the GroupedProperty? Raise an error? Log a warning?
    We could take a similar approach to the HDF5 case where we raise an error by default but provide a way of suppressing the error to a warning.

@woodsp-ibm
Copy link
Member

Hopefully I understood the intent of the above; anyway here is my thought back

aux_properties (or whatever we may call them) should be supplied BaseProblem.init

Are we always able to do this? If they are dependent on the problem being further processed, which I think what we generate today are e.g. at least going through the driver to get more information on the problem, does it work at that level in general? The current properties are the result of the driver being run, they are not formulated on the input, rather some entity, which is part of the problem configuration supplied to the init, i.e. the driver, produces them - they are not directly supplied as such.

We should support both, instances and generators,

A generator is pretty much saying one supports a list right - albeit that its a bit more lazily done. In the past in Aqua VQE supported aux_ops as a single instance or a list so as to be convenient in that the user did not have to wrap it in a list if they just had a single instance (not that its of course must to actually do that). Maybe a Generator is what you had in mind for the above so it can be input and only lazily created on demand later - but isn;t information that it might need still an issue.

How should we handle scenarios in which a user provides an auxiliary property which is already present in the GroupedProperty

How do you define already present? I think internally we store these via their name in some map. Hence a unique name would allow it to be stored whereas presumably storing something with the same name (key) would overwrite any existing one. If someone came up with different implementation of something we have is that supported/ok if its called by the same name. I believe we expect/refer to them by name to get them from the current driver result group. In a regular Python dict I any object that existed is overwritten by a new one with the same key right. That seems to be what happens today when you add a property. (Just as an aside I guess the reason the GroupedProperty does not implement Mapping is because we get the key from the item, it name or class name).

As a thought the other way, although this is more about customizing the built-in property behavior rather than strictly custom properties, one of the things we had talked about in the past when having more granular properties was allowing a user to say exclude dipole, or some other other properties from the computation. Though of course in practice the overhead is just some creation of smaller operators and a single evaluation at the result state(s).

@mrossinek
Copy link
Member Author

Are we always able to do this?

No, this is not generally possible. As you point out correctly, cases like e.g. the ElectronicEnergy or ElectronicDipoleMoment could not be handled in such a way. If a user creates a custom property which relies on data that needs to be extracted manually from the driver, they also need to write a custom driver to construct such a property.
However, some cases can be handled quite simply. One example is the OccupiedModals in the vibrational stack. It does not require any input data and is a pure blueprint method to construct operators. Almost the same can be said for AngularMomentum, Magnetization and in the future ElectronicDensity (to some degree) which can be constructed with merely the number of spin orbitals as input. If a user knows this a priori, it is easy to inject these in the initializer. If they do not know them a prior, see below.

A generator is pretty much saying one supports a list right

This is not quite what I meant. In general I think we will support ListOrDictType but I also would like to be able to pass in a function (or list there of) which gets the (e.g.) ElectronicStructureDriverResult as input and can generate additional properties from it. Here is a very simple example:

def construct_magnetization(driver_result: ElectronicStructureDriverResult) -> Magnetization:
    num_spin_orbitals = driver_result.get_property("ParticleNumber").num_spin_orbitals
    return Magnetization(num_spin_orbitals)

problem = ElectronicStructureProblem(driver, transformers=[], aux_properties=[construct_magnetization])

This is a very minimal example but more fancy operations could be done, too.

I think internally we store these via their name in some map.

Yes this is correct. And you're right I guess a user can use an identical name if they want to overwrite, and a different one if they do not want to do that. This will need clear documentation.

was allowing a user to say exclude dipole, or some other other properties from the computation.

This is a very good point actually. Maybe we do not need to provide aux_properties at all but instead expand on my generator idea above and simply allow the user to pass in a callback which can modify the driver result in place? This would allow not only extraction of data for further property construction and addition but could also allow removal of unwanted properties.
Converting the above example:

def extend_properties(driver_result: ElectronicStructureDriverResult) -> None:
    num_spin_orbitals = driver_result.get_property("ParticleNumber").num_spin_orbitals

    driver_result.add_property(Magnetization(num_spin_orbitals))

    driver_result.pop("ElectronicDipoleMoment")

problem = ElectronicStructureProblem(driver, transformers=[], property_cb=extend_properties)

@mrossinek
Copy link
Member Author

In the design described in #701 this feature will directly be available without further changes. Thus, I am closing this issue as it is superseded by that new design.

@mrossinek mrossinek closed this as not planned Won't fix, can't repro, duplicate, stale Jun 20, 2022
Anthony-Gandon pushed a commit to Anthony-Gandon/qiskit-nature that referenced this issue May 25, 2023
* WIP: HDF5-integration for property framework

This is an initial proof-of-concept implementation.
This needs to be reviewed extensively.

* Clean up interface

* refactor: let ElectronicIntegrals derive from PseudoProperty

* refactor: let Molecule derive from PseudoProperty

* feat: implement DriverMetadata HDF5 methods

* feat: add toggle to include PseudoProperty objects in GroupedProperty iteration

* feat: implement ParticleNumber HDF5 methods

* feat: implement AngularMomentum and Magnetization HDF5 methods

* feat: implement ElectronicBasisTransform HDF5 methods

* fix: lint

* fix: fix unittests

* remove comment

* fix: spell

* feat: also store Qiskit Nature version

* handle potential error cases in Property.import_and_build_from_hdf5

* Fix copyright

* feat: introduce individual version numbers per Property class

* WIP: HDF5 integration into vibrational properties

* Fix copyright

* fix: property tutorial

* Update typehints

* Run black

* Remove @AbstractMethod from Property.from_hdf5

This needs to be removed in order to ensure that the stable tutorials
remain working.

* Add more missing typehints

* refactor: introduce HDF5Storable Protocol

* refactor: remove PseudoProperty in favor of Interpretable Protocol

The PseudoProperty class effectively removes everything which defines
the Property class (the interpret method). So instead of having such a
pseudo-class, all previous PseudoProperty subclass are now directly
Property subclasses and the `interpret()` method existence is handled
via the `Interpretable` Protocol.

* Fix linters

* Fix ASTransformer caught error type

* Fix copyright

* Fix imports

* More guards against Property type

* More import fixes

* fix: property tutorial

* fix: ElectronicStructureDriverResult.__str__

* refactor: remove Property base class where not needed

* test: basic hdf5 method unittests

* test: *StructureDriverResult from_hdf5 methods

* docs: HDF5 documentation

* refactor: formally deprecate PseudoProperty class

* docs: actual HDF5 save and load examples

* Fix spell

* fix: avoid name clash with multiple atoms of same kind

* fix: ElectronicEnergy.from_hdf5 group access

* Update unittest HDF5 resource

* fix: ParticleNumber.from_hdf5 occupation dataset access

* Fix qiskit-community#519

This is actually required for the matrices loaded during
ElectronicIntegrals.from_hdf5 to be in the correct order!

* Update qiskit_nature/properties/property.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Update docs

* Rename save_to_hdf5(..., force -> replace)

* refactor: fix DriverMetadata HDF5 attribute names

* refactor: make from_hdf5 a staticmethod

* feat: store Molecule.units in HDF5

* fix: update expected HDF5 result

* docs: include backwards compatibility expectations

* feat: add skip_unreadable_data toggle to HDF5 loading methods

* Fix spell

Apparently Sphinx can now use `kwds` instead of `kwargs`

* docs: explicitly request error raising

* docs: use `:func:` instead of `:class:`

* docs: ensure *StructureDriverResults are documented

* refactor: enforce keyword arguments in hdf5 module

* Update driver return types

While the previous return types were not wrong, for documentation
purposes using these concrete implementations is a bit nicer.

* Run black

* Add reno

* feat: use Molecule.units during from_hdf5

* test: refactor _hdf5 method tests

Instead of hard-coding the expected HDF5 file structure, we test that
`to_hdf5` and `from_hdf5` work consistently with each other.
The `test_to_hdf5` tests ensure that this method executes correctly
(i.e. without errors). The `from_hdf5` tests ensure that first writing
and subsequently reading a property from a file produces an identical
instance.

In the future, once version numbers of certain properties may increase,
we should store HDF5 files and compare those against expected instances.

* fix: README test

The README test previously failed because the iteration over the
auxiliary operator observables in the ElectronicStructureResult is
currently unable to handle the lack of certain properties which have
always been evaluated for legacy reasons (AngularMomentum,
Magnetization).
Even if we were to default them to an empty list instead of None, while
the zip command would execute normally, no results would be printed
since zip stops after the shortest length.

That being said, fixing ElectronicStructureResult is not the solution
right now in any case, since a user would be unable to manually request
the computation of AngularMomentum and Magnetization before we resolve
the issue ý¿¿¼£�qiskit-community#312
Thus, this commit reverts the exclusion of these auxiliary operators in
the case of `settings.dict_aux_operators`.

* refactor: use only public API in PropertyTest

* refactor: update type hints

* fix: update TestVibrationalStructureDriverResult to G16 Rev.C01

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants