Skip to content

Commit cd03c1c

Browse files
authored
Merge pull request #298 from SCM-NV/orb
ENH: Add two new fields to CP2K MO named tuple
2 parents b9c2795 + 3660a3f commit cd03c1c

File tree

9 files changed

+88
-27
lines changed

9 files changed

+88
-27
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
# Version 0.12.1 (*unreleased*)
1+
# Version 0.12.1 (18/05/2022)
22

33
## New
4-
* *placeholder*.
4+
* Add the MO index and occupation numbers to the CP2K orbital output.
5+
6+
## Changed
7+
* Explicitly raise when the line with the number of orbitals doesn't have any actual orbitals.
8+
9+
## Fixed
10+
* Fixed various bugs related to the parsing of unrestricted orbitals.
511

612

713
# Version 0.12.0 (13/04/2022)

CITATION.cff

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# YAML 1.2
22
# Metadata for citation of this software according to the CFF format (https://citation-file-format.github.io/)
3-
cff-version: 1.0.3
3+
cff-version: 1.2.0
44
message: If you use this software, please cite it as below.
55
title: QMflows
6+
abstract: QMflows library tackles the construction and efficient execution of computational chemistry workflows.
67
doi: 10.5281/zenodo.1045523
78
authors:
89
- given-names: Felipe
@@ -22,7 +23,7 @@ keywords:
2223
- materials-science
2324
- python
2425
- Workflows
25-
version: '0.12.0'
26-
date-released: 2022-04-13 # YYYY-MM-DD
26+
version: '0.12.1'
27+
date-released: 2022-05-18 # YYYY-MM-DD
2728
repository-code: https://github.com/SCM-NV/qmflows
2829
license: "LGPL-3.0"

MANIFEST.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exclude test/*
2+
include src/qmflows/py.typed
3+
include src/qmflows/data/dictionaries/*yaml

setup.cfg

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
[metadata]
2-
description_file = README.rst
3-
license_file = LICENSE.md
4-
51
[aliases]
62
# Define `python setup.py test`
73
test = pytest

src/qmflows/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""The QMFlows version."""
22

3-
__version__ = "0.12.1.dev0"
3+
__version__ = "0.12.1"

src/qmflows/common.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
else:
1010
ParserElement = 'pyparsing.ParserElement'
1111

12-
__all__ = ['AtomBasisKey', 'AtomBasisData', 'AtomXYZ', 'CGF',
12+
__all__ = ['AtomBasisKey', 'AtomBasisData', 'AtomXYZ', 'CGF', 'CP2KInfoMO',
1313
'InfoMO', 'MO_metadata', 'ParseWarning', 'CP2KVersion']
1414

1515

@@ -61,16 +61,60 @@ class InfoMO(NamedTuple):
6161
#: Orbital eigenvectors.
6262
eigenvectors: np.ndarray
6363

64+
65+
class CP2KInfoMO(NamedTuple):
66+
"""Energies and coefficients of the CP2K molecular orbitals."""
67+
68+
#: Orbital eigenvalues.
69+
eigenvalues: np.ndarray
70+
71+
#: Orbital eigenvectors.
72+
eigenvectors: np.ndarray
73+
74+
#: MO indices.
75+
orb_index: np.ndarray
76+
77+
#: Occupation numbers.
78+
occupation: np.ndarray
79+
80+
def get_nocc_nvirt(self, threshold: "None | float" = None) -> "tuple[int, int]":
81+
"""Return the number of occupied and virtual orbitals within the MO range spanned \
82+
by this instance.
83+
84+
Parameters
85+
----------
86+
threshold : None | float
87+
The occupation number threshold for determining what consitutes an occupied orbital.
88+
If ``None`` assume that all occupied orbitals are defined by a non-zero occupation
89+
number (*e.g.* canonical orbitals).
90+
91+
Returns
92+
-------
93+
tuple[int, int]
94+
A 2-tuple with the number of occupied and virtual orbitals.
95+
96+
"""
97+
if threshold is None:
98+
is_occ = self.occupation != 0
99+
else:
100+
is_occ = self.occupation >= threshold
101+
nocc = is_occ.sum().item()
102+
return nocc, len(self.occupation) - nocc
103+
64104
def __array__(self, dtype: "None | np.dtype" = None) -> np.ndarray:
65105
"""Convert this instance into a structured array."""
66106
struc_dtype = np.dtype([
67107
("eigenvalues", "f8"),
68108
("eigenvectors", "f8", (self.eigenvectors.shape[0],)),
109+
("orb_index", "i8"),
110+
("occupation", "f8"),
69111
])
70112

71113
ret = np.empty(self.eigenvalues.shape[0], dtype=struc_dtype)
72114
ret["eigenvalues"] = self.eigenvalues
73115
ret["eigenvectors"] = np.abs(self.eigenvectors.T)
116+
ret["orb_index"] = self.orb_index
117+
ret["occupation"] = self.occupation
74118
return ret if dtype is None else ret.astype(dtype)
75119

76120

src/qmflows/packages/_cp2k.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .._settings import Settings
1414
from ..warnings_qmflows import cp2k_warnings, Key_Warning
1515
from ..type_hints import Final, _Settings, Generic2Special
16-
from ..common import InfoMO
16+
from ..common import CP2KInfoMO
1717

1818
if TYPE_CHECKING:
1919
from numpy.typing import NDArray
@@ -141,7 +141,7 @@ class CP2K_Result(Result):
141141
geometry: "None | plams.Molecule"
142142
enthalpy: "None | float"
143143
free_energy: "None | float"
144-
orbitals: "None | InfoMO | tuple[InfoMO, InfoMO]"
144+
orbitals: "None | CP2KInfoMO | tuple[CP2KInfoMO, CP2KInfoMO]"
145145
forces: "None | NDArray[f8]"
146146
coordinates: "None | NDArray[f8]"
147147
temperature: "None | NDArray[f8]"

src/qmflows/parsers/_cp2k_orbital_parser.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010

1111
import numpy as np
1212

13-
from ..common import InfoMO, MO_metadata
13+
from ..common import CP2KInfoMO, MO_metadata
1414

1515
__all__ = ["read_cp2k_coefficients"]
1616

1717

1818
def read_cp2k_coefficients(
1919
path_mos: "str | os.PathLike[str]",
2020
plams_dir: "None | str | os.PathLike[str]" = None,
21-
) -> "InfoMO | tuple[InfoMO, InfoMO]":
21+
) -> "CP2KInfoMO | tuple[CP2KInfoMO, CP2KInfoMO]":
2222
"""Read the MO's from the CP2K output.
2323
2424
First it reads the number of ``Orbitals`` and ``Orbital`` functions from the
@@ -39,7 +39,7 @@ def read_cp2k_coefficients(
3939
def read_log_file(
4040
path: "str | os.PathLike[str]",
4141
orbitals_info: MO_metadata,
42-
) -> "InfoMO | tuple[InfoMO, InfoMO]":
42+
) -> "CP2KInfoMO | tuple[CP2KInfoMO, CP2KInfoMO]":
4343
"""
4444
Read the orbitals from the Log file.
4545
@@ -58,9 +58,9 @@ def read_log_file(
5858
5959
Returns
6060
-------
61-
InfoMO or tuple[InfoMO, InfoMO]
61+
CP2KInfoMO or tuple[CP2KInfoMO, CP2KInfoMO]
6262
Molecular orbitals and orbital energies.
63-
Returns a tuple of two ``InfoMO`` objects if its an unrestricted calculation.
63+
Returns a tuple of two ``CP2KInfoMO`` objects if its an unrestricted calculation.
6464
6565
"""
6666
n_orb_list = orbitals_info.nOrbitals
@@ -114,7 +114,7 @@ def read_coefficients(
114114
n_orb: int,
115115
n_orb_funcs: int,
116116
start: int = 0,
117-
) -> InfoMO:
117+
) -> CP2KInfoMO:
118118
"""Read the coefficients from the plain text output.
119119
120120
MO coefficients are stored in Column-major order.
@@ -146,6 +146,8 @@ def read_coefficients(
146146
"""
147147
energies = np.empty(n_orb, dtype=np.float64)
148148
coefs = np.empty((n_orb_funcs, n_orb), dtype=np.float64)
149+
orb_index = np.empty(n_orb, dtype=np.int64)
150+
occupation = np.empty(n_orb, dtype=np.float64)
149151

150152
j0 = 0
151153
j1 = 0
@@ -159,13 +161,15 @@ def read_coefficients(
159161
break
160162

161163
j1 += len(headers)
164+
orb_index[j0:j1] = headers
162165
energies[j0:j1] = next(iterator).split()
163-
coefs[..., j0:j1] = [i.split()[4:] for i in islice(iterator, 1, 1 + n_orb_funcs)]
166+
occupation[j0:j1] = next(iterator).split()
167+
coefs[..., j0:j1] = [i.split()[4:] for i in islice(iterator, 0, n_orb_funcs)]
164168
j0 += len(headers)
165169

166170
# `n_orb` is an upper bound to the actual printed number of orbitals,
167171
# so some trimming might be required
168-
return InfoMO(energies[:j0], coefs[:, :j0])
172+
return CP2KInfoMO(energies[:j0], coefs[:, :j0], orb_index[:j0], occupation[:j0])
169173

170174

171175
_ORBITAL_PATTERN = re.compile((

test/test_common.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import numpy as np
22
from assertionlib import assertion
33

4-
from qmflows.common import InfoMO
4+
from qmflows.common import CP2KInfoMO
55

66

77
class TestInfoMO:
88
def test_array(self) -> None:
9-
info_mo = InfoMO(
9+
info_mo = CP2KInfoMO(
1010
eigenvalues=np.arange(3),
1111
eigenvectors=np.arange(12).reshape(4, 3),
12+
orb_index=np.array([5, 6, 7]),
13+
occupation=np.array([2.0, 2.0, 0.0]),
1214
)
1315
ref = np.array([
14-
(0, [0, 3, 6, 9]),
15-
(1, [1, 4, 7, 10]),
16-
(2, [2, 5, 8, 11])
17-
], dtype=[("eigenvalues", "f8"), ("eigenvectors", "f8", (4,))])
16+
(0, [0, 3, 6, 9], 5, 2),
17+
(1, [1, 4, 7, 10], 6, 2),
18+
(2, [2, 5, 8, 11], 7, 0),
19+
], dtype=[
20+
("eigenvalues", "f8"),
21+
("eigenvectors", "f8", (4,)),
22+
("orb_index", "i8"),
23+
("occupation", "f8"),
24+
])
1825

1926
ar = np.array(info_mo)
2027
assertion.eq(ar.dtype, ref.dtype)

0 commit comments

Comments
 (0)