Skip to content

Adopt lazy imports from Terra#535

Merged
manoelmarques merged 4 commits into
qiskit-community:mainfrom
manoelmarques:lazy
Feb 8, 2022
Merged

Adopt lazy imports from Terra#535
manoelmarques merged 4 commits into
qiskit-community:mainfrom
manoelmarques:lazy

Conversation

@manoelmarques
Copy link
Copy Markdown
Contributor

Summary

Adopt the API implemented in Qiskit/qiskit#7525

Details and comments

@coveralls
Copy link
Copy Markdown

coveralls commented Feb 4, 2022

Pull Request Test Coverage Report for Build 1799728890

  • 65 of 69 (94.2%) changed or added relevant lines in 11 files are covered.
  • 9 unchanged lines in 3 files lost coverage.
  • Overall coverage increased (+0.07%) to 82.661%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py 4 5 80.0%
qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py 10 11 90.91%
qiskit_nature/drivers/second_quantization/gaussiand/gaussian_utils.py 1 3 33.33%
Files with Coverage Reduction New Missed Lines %
qiskit_nature/drivers/second_quantization/gaussiand/gaussian_log_driver.py 2 46.43%
qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py 2 86.51%
qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py 5 72.5%
Totals Coverage Status
Change from base Build 1796228481: 0.07%
Covered Lines: 8338
Relevant Lines: 10087

💛 - Coveralls

Copy link
Copy Markdown
Member

@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.

In general this looks good to me 👍 I have some minor comments.

I did grep for ImportError on the repo and did find a few more occurrences though. Maybe those can be refactored, too?

qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py:257:        except ImportError as mnfe:
test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.py:138:        except ImportError:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:320:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:408:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:434:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:465:        except ImportError as ex:
test/test_readme_sample.py:41:        except ImportError as ex:

Grepping for MissingOptionalLibraryError gives the following:

qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py:216:            MissingOptionalLibraryError: Requires matplotlib.
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:22:from qiskit.exceptions import MissingOptionalLibraryError
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:54:            MissingOptionalLibraryError: Driver not installed.
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:64:                    except MissingOptionalLibraryError as ex:
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:73:                raise MissingOptionalLibraryError(
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:22:from qiskit.exceptions import MissingOptionalLibraryError
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:59:            MissingOptionalLibraryError: Driver not installed.
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:72:                    except (MissingOptionalLibraryError, UnsupportMethodError) as ex:
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:81:                raise MissingOptionalLibraryError(
test/drivers/second_quantization/test_molecule_driver.py:20:from qiskit.exceptions import MissingOptionalLibraryError
test/drivers/second_quantization/test_molecule_driver.py:73:        except MissingOptionalLibraryError as ex:
test/drivers/second_quantization/test_molecule_driver.py:92:                except MissingOptionalLibraryError as ex:
test/drivers/second_quantization/test_molecule_driver.py:156:        except MissingOptionalLibraryError as ex:

Comment thread qiskit_nature/optionals.py
Comment thread qiskit_nature/optionals.py Outdated
Comment on lines +83 to +84
if PSI4PROG is None:
PSI4PROG = PSI4
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why is this needed? If which cannot find the command how would we be able to use it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@mrossinek Just because the LazySubprocessTester from Terra won't accept a None or empty command in the constructor and I still wanted to use its capabilities of actually running the command to see if it is installed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Okay, then why is the same not being done in the gaussian case a few lines further up?

Copy link
Copy Markdown
Contributor Author

@manoelmarques manoelmarques Feb 4, 2022

Choose a reason for hiding this comment

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

Ahhh, because unfortunately Gaussian doesn't have a command line option like version or whatever that lets me just simply run it without side effect of actually running input data. For this I created my own Lazy class derived from Terra that just checks if the command is None. Effectively just what was being done before:

class NatureLazyCommandTester(LazyDependencyManager):

I didn't use it for PSI4 because in that case I could do a better check of actually running it because it has a version option.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Oh I was not careful. The first time I read it, it looked like you used NatureLazyCommandTester for Gaussian and Psi4. But I see now that the latter actually uses the Terra-provided one 👍

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What functionality would you like in LazySubprocessTester? I made it reject "empty" commands as a way of catching bugs because I couldn't think of any use-cases, but if you've got a use-case for empty commands, we could add it.

Here, I would note that calling which on import isn't lazy - if you wanted it for your Gaussian one, perhaps your derived class could run shutil.which to define its _is_available? It depends how lazy you care about being, though.

Comment thread test/drivers/second_quantization/test_molecule_driver.py
@manoelmarques
Copy link
Copy Markdown
Contributor Author

manoelmarques commented Feb 4, 2022

In general this looks good to me 👍 I have some minor comments.

I did grep for ImportError on the repo and did find a few more occurrences though. Maybe those can be refactored, too?

qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py:257:        except ImportError as mnfe:
test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.py:138:        except ImportError:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:320:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:408:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:434:        except ImportError as ex:
test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py:465:        except ImportError as ex:
test/test_readme_sample.py:41:        except ImportError as ex:

Grepping for MissingOptionalLibraryError gives the following:

qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py:216:            MissingOptionalLibraryError: Requires matplotlib.
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:22:from qiskit.exceptions import MissingOptionalLibraryError
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:54:            MissingOptionalLibraryError: Driver not installed.
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:64:                    except MissingOptionalLibraryError as ex:
qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py:73:                raise MissingOptionalLibraryError(
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:22:from qiskit.exceptions import MissingOptionalLibraryError
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:59:            MissingOptionalLibraryError: Driver not installed.
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:72:                    except (MissingOptionalLibraryError, UnsupportMethodError) as ex:
qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py:81:                raise MissingOptionalLibraryError(
test/drivers/second_quantization/test_molecule_driver.py:20:from qiskit.exceptions import MissingOptionalLibraryError
test/drivers/second_quantization/test_molecule_driver.py:73:        except MissingOptionalLibraryError as ex:
test/drivers/second_quantization/test_molecule_driver.py:92:                except MissingOptionalLibraryError as ex:
test/drivers/second_quantization/test_molecule_driver.py:156:        except MissingOptionalLibraryError as ex:

@mrossinek I will review the ImportError to see if any can be refactored but the MissingOptionalLibraryError needs to be checked and needs to be kept in the documentation as being raised. That exception didn't go away. It is still being thrown by the Lazy import framework. It just has been delayed at the point where it is used.

@manoelmarques manoelmarques force-pushed the lazy branch 2 times, most recently from 90dc2e2 to b894525 Compare February 4, 2022 16:02
Copy link
Copy Markdown
Member

@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.

Just one question otw this LGTM 🙂

@manoelmarques manoelmarques added the run_slow PR will run all unit tests decorated as slow label Feb 4, 2022
@manoelmarques manoelmarques force-pushed the lazy branch 2 times, most recently from d95369e to 0c6ea0b Compare February 4, 2022 17:41
Copy link
Copy Markdown

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

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

I hope the tools are useful - let us/me know if there's anything about the existing Terra objects that might want expanding.

_optionals.HAS_GAUSSIAN.require_now("GaussianForcesDriver __init__")

@staticmethod
@_optionals.HAS_GAUSSIAN.require_in_call("GaussianForcesDriver from_molecule")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If you don't supply an argument in the decorator form, it should use the qualified name of the method automatically. If you prefer, this line is equivalent to decorating with @_optionals.HAS_GAUSSIAN.require_in_call (i.e. without "calling" the decorator)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh that is helpful. Thanks for the tip. I will change it.

logger = logging.getLogger(__name__)

try:
if _optionals.HAS_PYQUANTE2:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I don't know if you mind, but this isn't a lazy test - the Boolean evaluation will be triggered as soon as this module is imported.

Copy link
Copy Markdown
Contributor Author

@manoelmarques manoelmarques Feb 4, 2022

Choose a reason for hiding this comment

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

Yes, I knew. We had those imports at the top because they are being used in different parts of the file and even in type annotations like:

 self._mol: pyquante_molecule = None
 self._bfs: basisset = None

I will see if it is possible to move them inside the methods.

Comment on lines -250 to +248
args = inspect.getfullargspec(PyQuanteDriver.__init__).args
args = inspect.signature(PyQuanteDriver.__init__).parameters.keys()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I put some effort into trying to make sure the wrappers would propagate through the signature and documentation (require_in_instance does a bit of magic to wrap the __init__ method). Let me know if something's broken about that.

(inspect.signature is the recommended way, though)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, the getfullargspec args started returning an empty array. I changed to signature which works.

Comment thread qiskit_nature/optionals.py Outdated
Comment on lines +83 to +84
if PSI4PROG is None:
PSI4PROG = PSI4
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What functionality would you like in LazySubprocessTester? I made it reject "empty" commands as a way of catching bugs because I couldn't think of any use-cases, but if you've got a use-case for empty commands, we could add it.

Here, I would note that calling which on import isn't lazy - if you wanted it for your Gaussian one, perhaps your derived class could run shutil.which to define its _is_available? It depends how lazy you care about being, though.

Comment thread qiskit_nature/optionals.py Outdated
not empty.
"""

__slots__ = ("_command",)
Copy link
Copy Markdown

@jakelishman jakelishman Feb 4, 2022

Choose a reason for hiding this comment

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

Also, you don't need to do this if you don't want, especially if you'd like to attach data to the objects. To be honest, I probably shouldn't have done it in Terra to give you the choice of adding arbitrary data - from a Python perspective, it's a bad habit of mine that I over-use __slots__ (it comes from me preferring stricter typing, where you can't add attributes to stuff).

There's no real harm in it if you don't want to attach data to the objects, it's just a bit of overkill, and makes it harder if you later change your mind.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, since I was creating a lazy class I decided to stick to the idiom there but already had someone else asking me about it too :-) I will remove it.

@manoelmarques
Copy link
Copy Markdown
Contributor Author

@jakelishman About the LazySubprocessTester:

Nature has two drivers, PSI4 and Gaussian, that have their own commands. We use which as a way of figuring out if they are installed. In the PSI4 case, I used LazySubprocessTester because it is an improvement by running also the command. I don't use LazySubprocessTester for Gaussian though because it doesn't have any command option that would let it run without actually feeding chemistry input which gets complicated.

A subprocess tester that would be useful would be one where I construct it with a command string, but inside _is_available it runs which and if it returns None, is an error. Also it doesn't run the command automatically since there is no good way to run Gaussian. Maybe an additional flag in the __init__ telling if it should run the command.

I created a very simple subclass for Gaussian just to test None or not but I was thinking of doing what you suggested and put the which inside the _is_available. I will try this and you can take a look. If you think it is useful, it could be moved to Terra at some point.

Copy link
Copy Markdown
Member

@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.

LGTM! Thanks @manoelmarques!

@manoelmarques manoelmarques merged commit 508b81f into qiskit-community:main Feb 8, 2022
@manoelmarques manoelmarques deleted the lazy branch February 8, 2022 14:03
Anthony-Gandon pushed a commit to Anthony-Gandon/qiskit-nature that referenced this pull request May 25, 2023
* Adopt lazy imports from Terra

* Migrate most ImportError to Lazy framework

* Remove global optionals check from pyscf,pyquante

* Create NatureLazySubprocessTester for Gaussian and PSI4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

run_slow PR will run all unit tests decorated as slow type: enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants