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

HDF5 Driver Update #550

Merged
merged 14 commits into from
Feb 22, 2022
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` will be used as
a suffix.
mrossinek marked this conversation as resolved.
Show resolved Hide resolved

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)
mrossinek marked this conversation as resolved.
Show resolved Hide resolved
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.