diff --git a/express/parsers/apps/nwchem/formats/txt.py b/express/parsers/apps/nwchem/formats/txt.py index 30202e4f..a388caf2 100644 --- a/express/parsers/apps/nwchem/formats/txt.py +++ b/express/parsers/apps/nwchem/formats/txt.py @@ -39,3 +39,27 @@ def total_energy_contributions(self, text): if value is not None: energy_contributions.update({contribution: {"name": contribution, "value": value}}) return energy_contributions + + def homo_energy(self, text): + """ + Extracts HOMO energy. + + Args: + text (str): text to extract data from. + + Returns: + float | None + """ + return self._general_output_parser(text, **settings.REGEX["homo_energy"]) + + def lumo_energy(self, text): + """ + Extracts LUMO energy. + + Args: + text (str): text to extract data from. + + Returns: + float | None + """ + return self._general_output_parser(text, **settings.REGEX["lumo_energy"]) diff --git a/express/parsers/apps/nwchem/parser.py b/express/parsers/apps/nwchem/parser.py index aa6d09cf..21fbcf32 100644 --- a/express/parsers/apps/nwchem/parser.py +++ b/express/parsers/apps/nwchem/parser.py @@ -45,6 +45,26 @@ def total_energy_contributions(self): value1[key2] = value2 * Constant.HARTREE return energy_contributions + def homo_energy(self): + """ + Returns HOMO energy. + + Reference: + NWChem orbital energies are defaulted to hartrees and are converted to eV in this method. + """ + homo_energy = self.txt_parser.homo_energy(self._get_file_content(self.stdout_file)) + return None if homo_energy is None else Constant.HARTREE * homo_energy + + def lumo_energy(self): + """ + Returns LUMO energy. + + Reference: + NWChem orbital energies are defaulted to hartrees and are converted to eV in this method. + """ + lumo_energy = self.txt_parser.lumo_energy(self._get_file_content(self.stdout_file)) + return None if lumo_energy is None else Constant.HARTREE * lumo_energy + def _is_nwchem_output_file(self, path): """ Checks whether the given file is nwchem output file. diff --git a/express/parsers/apps/nwchem/settings.py b/express/parsers/apps/nwchem/settings.py index c9cd92f5..dc6419cd 100644 --- a/express/parsers/apps/nwchem/settings.py +++ b/express/parsers/apps/nwchem/settings.py @@ -4,7 +4,11 @@ DOUBLE_REGEX = GENERAL_REGEX.double_number NWCHEM_OUTPUT_FILE_REGEX = "Northwest Computational Chemistry Package" -REGEX = {"total_energy": {"regex": COMMON_REGEX.format("Total DFT energy"), "occurrences": -1, "output_type": "float"}} +REGEX = { + "total_energy": {"regex": COMMON_REGEX.format("Total DFT energy"), "occurrences": -1, "output_type": "float"}, + "homo_energy": {"regex": COMMON_REGEX.format("HOMO"), "occurrences": -1, "output_type": "float"}, + "lumo_energy": {"regex": COMMON_REGEX.format("LUMO"), "occurrences": -1, "output_type": "float"}, +} TOTAL_ENERGY_CONTRIBUTIONS = { "one_electron": {"regex": COMMON_REGEX.format("One electron energy"), "occurrences": -1, "output_type": "float"}, "coulomb": {"regex": COMMON_REGEX.format("Coulomb Energy"), "occurrences": -1, "output_type": "float"}, diff --git a/express/properties/scalar/homo_energy.py b/express/properties/scalar/homo_energy.py new file mode 100644 index 00000000..fcb6278d --- /dev/null +++ b/express/properties/scalar/homo_energy.py @@ -0,0 +1,11 @@ +from express.properties.scalar import ScalarProperty + + +class HOMOEnergy(ScalarProperty): + """ + The highest occupied molecular orbital energy. + """ + + def __init__(self, name, parser, *args, **kwargs): + super(HOMOEnergy, self).__init__(name, parser, *args, **kwargs) + self.value = self.parser.homo_energy() diff --git a/express/properties/scalar/lumo_energy.py b/express/properties/scalar/lumo_energy.py new file mode 100644 index 00000000..747df139 --- /dev/null +++ b/express/properties/scalar/lumo_energy.py @@ -0,0 +1,11 @@ +from express.properties.scalar import ScalarProperty + + +class LUMOEnergy(ScalarProperty): + """ + The lowest unoccupied molecular orbital energy. + """ + + def __init__(self, name, parser, *args, **kwargs): + super(LUMOEnergy, self).__init__(name, parser, *args, **kwargs) + self.value = self.parser.lumo_energy() diff --git a/express/settings.py b/express/settings.py index 3dfa46a4..dfd87748 100644 --- a/express/settings.py +++ b/express/settings.py @@ -3,6 +3,8 @@ SCALAR_PROPERTIES_MANIFEST = { "total_energy": {"reference": "express.properties.scalar.total_energy.TotalEnergy"}, "fermi_energy": {"reference": "express.properties.scalar.fermi_energy.FermiEnergy"}, + "homo_energy": {"reference": "express.properties.scalar.homo_energy.HOMOEnergy"}, + "lumo_energy": {"reference": "express.properties.scalar.lumo_energy.LUMOEnergy"}, "pressure": {"reference": "express.properties.scalar.pressure.Pressure"}, "total_force": {"reference": "express.properties.scalar.total_force.TotalForce"}, "volume": {"reference": "express.properties.scalar.volume.Volume"}, diff --git a/pyproject.toml b/pyproject.toml index f1b8061b..2562b960 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = [ "munch==2.5.0", "pymatgen>=2023.8.10", "ase>=3.17.0", - "mat3ra-esse>=2026.1.23", + "mat3ra-esse>=2026.3.25.post0", "jarvis-tools>=2023.12.12", # To avoid module 'numpy.linalg._umath_linalg' has no attribute '_ilp64' in Colab "numpy>=1.24.4,<2", diff --git a/tests/fixtures/nwchem/references.py b/tests/fixtures/nwchem/references.py index 839085da..0285cd1a 100644 --- a/tests/fixtures/nwchem/references.py +++ b/tests/fixtures/nwchem/references.py @@ -4,6 +4,8 @@ All reference energies are in eV. """ TOTAL_ENERGY = -2079.18666382721904 +HOMO_ENERGY = -12.800485418916242 +LUMO_ENERGY = 3.1242763921882197 TOTAL_ENERGY_CONTRIBUTION = { "one_electron": {"name": "one_electron", "value": -3350.531714067630674}, diff --git a/tests/integration/parsers/apps/nwchem/test_parser.py b/tests/integration/parsers/apps/nwchem/test_parser.py index b5d7f7ff..6de2e873 100644 --- a/tests/integration/parsers/apps/nwchem/test_parser.py +++ b/tests/integration/parsers/apps/nwchem/test_parser.py @@ -15,5 +15,11 @@ def tearDown(self): def test_nwchem_total_energy(self): self.assertAlmostEqual(self.parser.total_energy(), TOTAL_ENERGY, places=2) + def test_nwchem_homo_energy(self): + self.assertAlmostEqual(self.parser.homo_energy(), HOMO_ENERGY, places=2) + + def test_nwchem_lumo_energy(self): + self.assertAlmostEqual(self.parser.lumo_energy(), LUMO_ENERGY, places=2) + def test_nwchem_total_energy_contributions(self): self.assertDeepAlmostEqual(self.parser.total_energy_contributions(), TOTAL_ENERGY_CONTRIBUTION, places=2) diff --git a/tests/manifest.yaml b/tests/manifest.yaml index cd8467d8..8224375f 100644 --- a/tests/manifest.yaml +++ b/tests/manifest.yaml @@ -2,6 +2,14 @@ test_nwchem_total_energy: workDir: fixtures/nwchem/test-001 stdoutFile: fixtures/nwchem/test-001/nwchem-total-energy.log +test_nwchem_homo_energy: + workDir: fixtures/nwchem/test-001 + stdoutFile: fixtures/nwchem/test-001/nwchem-total-energy.log + +test_nwchem_lumo_energy: + workDir: fixtures/nwchem/test-001 + stdoutFile: fixtures/nwchem/test-001/nwchem-total-energy.log + test_nwchem_total_energy_contributions: workDir: fixtures/nwchem/test-001 stdoutFile: fixtures/nwchem/test-001/nwchem-total-energy.log diff --git a/tests/unit/properties/scalar/test_homo_energy.py b/tests/unit/properties/scalar/test_homo_energy.py new file mode 100644 index 00000000..503a617a --- /dev/null +++ b/tests/unit/properties/scalar/test_homo_energy.py @@ -0,0 +1,17 @@ +from tests.unit import UnitTestBase +from express.properties.scalar.homo_energy import HOMOEnergy + +HOMO_ENERGY = {"units": "eV", "name": "homo_energy", "value": 1} + + +class HOMOEnergyTest(UnitTestBase): + def setUp(self): + super(HOMOEnergyTest, self).setUp() + + def tearDown(self): + super(HOMOEnergyTest, self).setUp() + + def test_homo_energy(self): + parser = self.get_mocked_parser("homo_energy", 1) + property_ = HOMOEnergy("homo_energy", parser) + self.assertDeepAlmostEqual(property_.serialize_and_validate(), HOMO_ENERGY) diff --git a/tests/unit/properties/scalar/test_lumo_energy.py b/tests/unit/properties/scalar/test_lumo_energy.py new file mode 100644 index 00000000..efaa16f0 --- /dev/null +++ b/tests/unit/properties/scalar/test_lumo_energy.py @@ -0,0 +1,17 @@ +from tests.unit import UnitTestBase +from express.properties.scalar.lumo_energy import LUMOEnergy + +LUMO_ENERGY = {"units": "eV", "name": "lumo_energy", "value": 1} + + +class LUMOEnergyTest(UnitTestBase): + def setUp(self): + super(LUMOEnergyTest, self).setUp() + + def tearDown(self): + super(LUMOEnergyTest, self).setUp() + + def test_lumo_energy(self): + parser = self.get_mocked_parser("lumo_energy", 1) + property_ = LUMOEnergy("lumo_energy", parser) + self.assertDeepAlmostEqual(property_.serialize_and_validate(), LUMO_ENERGY)