Skip to content

Commit

Permalink
HDF5 Driver Update (#550)
Browse files Browse the repository at this point in the history
* refactor: basic HDF5Driver update

* test: test both HDF5 type supports

* Fix copyright

* test: update new expected HDF5 file

* ref: align new HDF5Driver with #554

* feat: add HDF5Driver.convert utility method

* Add reno

* Fix linters

* refactor: do not replace by default

* refactor: use `_new.hdf5` instead of `.hdf5.new`

* refactor: update wording

* fix: Python <3.9 compatibility

* Update docstring

Co-authored-by: Panagiotis Barkoutsos <bpa@zurich.ibm.com>
  • Loading branch information
mrossinek and Panagiotis Barkoutsos authored Feb 22, 2022
1 parent 47eabdb commit 937cf0e
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 69 deletions.
100 changes: 85 additions & 15 deletions qiskit_nature/drivers/second_quantization/hdf5d/hdf5driver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2021.
# (C) Copyright IBM 2018, 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -12,20 +12,31 @@

""" HDF5 Driver """

import os
import logging
import pathlib
import warnings

import h5py

from qiskit_nature import QiskitNatureError
from qiskit_nature.hdf5 import load_from_hdf5, save_to_hdf5
from qiskit_nature.properties.second_quantization.second_quantized_property import (
GroupedSecondQuantizedProperty,
)
from qiskit_nature.properties.second_quantization.electronic import ElectronicStructureDriverResult

from ...qmolecule import QMolecule
from ..electronic_structure_driver import ElectronicStructureDriver
from ..base_driver import BaseDriver

LOGGER = logging.getLogger(__name__)


class HDF5Driver(ElectronicStructureDriver):
class HDF5Driver(BaseDriver):
"""
Qiskit Nature driver for reading an HDF5 file.
The HDF5 file is one as saved from a :class:`~qiskit_nature.drivers.QMolecule` instance.
The HDF5 file is one constructed with :func:`~qiskit_nature.hdf5.save_to_hdf5` or a file
containing a legacy :class:`~qiskit_nature.drivers.QMolecule` instance.
"""

def __init__(self, hdf5_input: str = "molecule.hdf5") -> None:
Expand All @@ -47,23 +58,82 @@ def work_path(self, new_work_path):
"""Sets work path."""
self._work_path = new_work_path

def run(self) -> ElectronicStructureDriverResult:
"""
def _get_path(self) -> pathlib.Path:
"""Returns the absolute path to the HDF5 file.
Returns:
ElectronicStructureDriverResult re-constructed from the HDF5 file.
The absolute path to the HDF5 file.
Raises:
LookupError: file not found.
"""
hdf5_file = self._hdf5_input
if self.work_path is not None and not os.path.isabs(hdf5_file):
hdf5_file = os.path.abspath(os.path.join(self.work_path, hdf5_file))
hdf5_file = pathlib.Path(self._hdf5_input)
if self.work_path is not None and not hdf5_file.is_absolute():
hdf5_file = pathlib.Path(self.work_path) / hdf5_file

if not os.path.isfile(hdf5_file):
if not hdf5_file.is_file():
raise LookupError(f"HDF5 file not found: {hdf5_file}")

return hdf5_file

def convert(self, replace: bool = False) -> None:
"""Converts a legacy QMolecule HDF5 file into the new Property-framework.
Args:
replace: if True, will replace the original HDF5 file. Otherwise `_new.hdf5` will be
used as a suffix.
Raises:
LookupError: file not found.
"""
hdf5_file = self._get_path()

warnings.filterwarnings("ignore", category=DeprecationWarning)
molecule = QMolecule(hdf5_file)
q_mol = QMolecule(hdf5_file)
warnings.filterwarnings("default", category=DeprecationWarning)
molecule.load()
return ElectronicStructureDriverResult.from_legacy_driver_result(molecule)
q_mol.load()

new_hdf5_file = hdf5_file
if not replace:
new_hdf5_file = hdf5_file.with_name(str(hdf5_file.stem) + "_new.hdf5")

driver_result = ElectronicStructureDriverResult.from_legacy_driver_result(q_mol)
save_to_hdf5(driver_result, str(new_hdf5_file), replace=replace)

def run(self) -> GroupedSecondQuantizedProperty:
"""
Returns:
GroupedSecondQuantizedProperty re-constructed from the HDF5 file.
Raises:
LookupError: file not found.
QiskitNatureError: if the HDF5 file did not contain a GroupedSecondQuantizedProperty.
"""
hdf5_file = self._get_path()

legacy_hdf5_file = False

with h5py.File(hdf5_file, "r") as file:
if "origin_driver" in file.keys():
legacy_hdf5_file = True
LOGGER.warning(
"Your HDF5 file contains the legacy QMolecule object! You should consider "
"converting it to the new property framework, see HDF5Driver.convert"
)

if legacy_hdf5_file:
warnings.filterwarnings("ignore", category=DeprecationWarning)
molecule = QMolecule(hdf5_file)
warnings.filterwarnings("default", category=DeprecationWarning)
molecule.load()
return ElectronicStructureDriverResult.from_legacy_driver_result(molecule)

driver_result = load_from_hdf5(str(hdf5_file))

if not isinstance(driver_result, GroupedSecondQuantizedProperty):
raise QiskitNatureError(
f"Expected a GroupedSecondQuantizedProperty but found a {type(driver_result)} "
"object instead."
)

return driver_result
11 changes: 11 additions & 0 deletions releasenotes/notes/hdf5-driver-bafbbd8e70ee15a6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
features:
- |
The HDF5Driver has been refactored to leverage the new HDF5-integration
protocol of Qiskit Nature. The driver still supports loading legacy
QMolecule HDF5 files and also provides a conversion utility:
.. code-block:: python
driver = HDF5Driver("path_to_qmolecule.hdf5")
driver.convert(replace=True)
Binary file modified test/drivers/second_quantization/hdf5d/test_driver_hdf5.hdf5
Binary file not shown.
73 changes: 72 additions & 1 deletion test/drivers/second_quantization/hdf5d/test_driver_hdf5.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2021.
# (C) Copyright IBM 2018, 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -12,7 +12,12 @@

""" Test Driver HDF5 """

import os
import pathlib
import shutil
import tempfile
import unittest

from test import QiskitNatureTestCase
from test.drivers.second_quantization.test_driver import TestDriver
from qiskit_nature.drivers.second_quantization import HDF5Driver
Expand All @@ -30,6 +35,72 @@ def setUp(self):
)
self.driver_result = driver.run()

def test_convert(self):
"""Test the legacy-conversion method."""
legacy_file_path = self.get_resource_path(
"test_driver_hdf5_legacy.hdf5", "drivers/second_quantization/hdf5d"
)

with self.subTest("replace=True"):
# pylint: disable=consider-using-with
tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".hdf5")
tmp_file.close()
os.unlink(tmp_file.name)
shutil.copy(legacy_file_path, tmp_file.name)
try:
driver = HDF5Driver(tmp_file.name)
driver.convert(replace=True)
with self.assertRaises(AssertionError):
# NOTE: we can use assertNoLogs once Python 3.10 is the default
with self.assertLogs(
logger="qiskit_nature.drivers.second_quantization.hdf5d.hdf5driver",
level="WARNING",
):
driver.run()
finally:
os.unlink(tmp_file.name)

with self.subTest("replace=False"):
# pylint: disable=consider-using-with
tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".hdf5")
tmp_file.close()
new_file_name = pathlib.Path(tmp_file.name).with_name(
str(pathlib.Path(tmp_file.name).stem) + "_new.hdf5"
)
os.unlink(tmp_file.name)
shutil.copy(legacy_file_path, tmp_file.name)
try:
driver = HDF5Driver(tmp_file.name)
driver.convert(replace=False)
with self.assertLogs(
logger="qiskit_nature.drivers.second_quantization.hdf5d.hdf5driver",
level="WARNING",
):
driver.run()
with self.assertRaises(AssertionError):
# NOTE: we can use assertNoLogs once Python 3.10 is the default
with self.assertLogs(
logger="qiskit_nature.drivers.second_quantization.hdf5d.hdf5driver",
level="WARNING",
):
HDF5Driver(new_file_name).run()
finally:
os.unlink(tmp_file.name)
os.unlink(new_file_name)


class TestDriverHDF5Legacy(QiskitNatureTestCase, TestDriver):
"""HDF5 Driver legacy file-support tests."""

def setUp(self):
super().setUp()
driver = HDF5Driver(
hdf5_input=self.get_resource_path(
"test_driver_hdf5_legacy.hdf5", "drivers/second_quantization/hdf5d"
)
)
self.driver_result = driver.run()


if __name__ == "__main__":
unittest.main()
Binary file not shown.
53 changes: 0 additions & 53 deletions test/drivers/second_quantization/hdf5d/test_driver_hdf5_save.py

This file was deleted.

0 comments on commit 937cf0e

Please sign in to comment.