Skip to content

Commit 8437f68

Browse files
committed
Add .pes file parser
1 parent 0f8adef commit 8437f68

File tree

8 files changed

+434
-1
lines changed

8 files changed

+434
-1
lines changed

castep_outputs/parsers/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
parse_elf_fmt_file,
2020
parse_pot_fmt_file,
2121
)
22+
from .pes_file_parser import parse_pes_file
2223
from .phonon_dos_file_parser import parse_phonon_dos_file
2324
from .phonon_file_parser import parse_phonon_file
2425
from .tddft_file_parser import parse_tddft_file
@@ -41,6 +42,7 @@
4142
"parse_magres_file",
4243
"parse_md_geom_file",
4344
"parse_md_geom_file",
45+
"parse_pes_file",
4446
"parse_phonon_dos_file",
4547
"parse_phonon_file",
4648
"parse_pot_fmt_file",
@@ -73,6 +75,7 @@
7375
"err": parse_err_file,
7476
"phonon": parse_phonon_file,
7577
"epme": parse_epme_file,
78+
"pes": parse_pes_file,
7679
}
7780

7881
#: Names of parsers/parsable file extensions (without ``"."``).
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Parse castep .pes files."""
2+
3+
from __future__ import annotations
4+
5+
from typing import TextIO, TypedDict
6+
7+
from ..utilities.datatypes import ThreeByThreeMatrix, ThreeVector
8+
from ..utilities.filewrapper import Block
9+
from ..utilities.utility import file_or_path, to_type
10+
11+
12+
class PESData(TypedDict):
13+
"""PES sample points."""
14+
15+
pos: ThreeVector
16+
e: float
17+
f_opt: float | None
18+
19+
20+
class PESFileInfo(TypedDict, total=False):
21+
"""Full pes file information."""
22+
23+
probe_species: str
24+
probe_method: str
25+
cell: ThreeByThreeMatrix
26+
date_started: str
27+
data: list[PESData]
28+
units: dict[str, str]
29+
samples: dict[str, int]
30+
31+
32+
def _parse_header(header: Block) -> PESFileInfo:
33+
accum: PESFileInfo = {"units": {}, "samples": {}, "data": []}
34+
35+
header = map(str.strip, header)
36+
37+
for line in header:
38+
if line.startswith("Job:"):
39+
_, accum["probe_method"] = line.split(": ")
40+
elif line.startswith("Probe species:"):
41+
_, accum["probe_species"] = line.split(": ")
42+
elif "unit:" in line:
43+
typ_, _, unit = line.split()
44+
accum["units"][typ_.lower()] = unit
45+
elif line.startswith("Number of samples"):
46+
*_, direc, count = line.split()
47+
accum["samples"][direc.strip(":")] = int(count)
48+
elif line.startswith("Cell Vectors"):
49+
*_, unit = line.split()
50+
accum["units"]["cell"] = unit.strip("()")
51+
accum["cell"] = tuple(to_type(cell.split(), float)
52+
for cell, _ in zip(header, range(3)))
53+
elif line.startswith("Date"):
54+
_, accum["date_started"] = line.split(": ", maxsplit=1)
55+
56+
return accum
57+
58+
59+
@file_or_path(mode="r")
60+
def parse_pes_file(pes_file: TextIO) -> PESFileInfo:
61+
"""Parse castep .pes file.
62+
63+
Parameters
64+
----------
65+
pes_file
66+
A handle to a CASTEP .pes file.
67+
68+
Returns
69+
-------
70+
PESFileInfo
71+
Parsed data.
72+
"""
73+
line = next(pes_file)
74+
block = Block.from_re(line, pes_file, "BEGIN header", "END header")
75+
76+
accum = _parse_header(block)
77+
78+
line = next(pes_file)
79+
block = Block.from_re(line, pes_file, " BLOCK DATA", "ENDBLOCK DATA")
80+
block.remove_bounds(1, 1)
81+
82+
data = accum["data"]
83+
84+
for line in block:
85+
a, b, c, e, *extra = to_type(line.split(), float)
86+
87+
curr = {}
88+
curr["pos"] = a, b, c
89+
curr["e"] = e
90+
curr["f_opt"] = extra[0] if extra else None
91+
92+
data.append(curr)
93+
94+
return accum

test/data_files/gen_data.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"efield",
3232
"tddft",
3333
"err",
34+
"pes",
3435
"epme",
3536
("pp-md", "castep"),
3637
("si8-md", "castep"),
@@ -63,7 +64,10 @@ def gen_data(
6364

6465
argp.add_argument("datasets", nargs=REMAINDER, help="Sets to generate.", default=ALL_SETS)
6566
argp.add_argument(
66-
"--formats", nargs="+", help="Formats to generate.", default=("json", "pyyaml", "ruamel"),
67+
"--formats",
68+
nargs="+",
69+
help="Formats to generate.",
70+
default=("json", "pyyaml", "ruamel"),
6771
)
6872
args = argp.parse_args()
6973

test/data_files/pes.json

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
{
2+
"units": {
3+
"energy": "eV",
4+
"force": "eV/A",
5+
"cell": "Bohr"
6+
},
7+
"samples": {
8+
"a": 10,
9+
"b": 10
10+
},
11+
"data": [
12+
{
13+
"pos": [
14+
0.5,
15+
0.5,
16+
0.50017
17+
],
18+
"e": -231.164733,
19+
"f_opt": 0.148
20+
},
21+
{
22+
"pos": [
23+
0.61111,
24+
0.5,
25+
0.50029
26+
],
27+
"e": -231.345479,
28+
"f_opt": 0.42
29+
},
30+
{
31+
"pos": [
32+
0.72222,
33+
0.5,
34+
0.50042
35+
],
36+
"e": -231.557748,
37+
"f_opt": 0.194
38+
},
39+
{
40+
"pos": [
41+
0.83333,
42+
0.5,
43+
0.50074
44+
],
45+
"e": -231.445747,
46+
"f_opt": -0.582
47+
},
48+
{
49+
"pos": [
50+
0.94444,
51+
0.5,
52+
0.50149
53+
],
54+
"e": -231.041607,
55+
"f_opt": -0.531
56+
},
57+
{
58+
"pos": [
59+
1.05556,
60+
0.5,
61+
0.50233
62+
],
63+
"e": -231.015796,
64+
"f_opt": 0.408
65+
},
66+
{
67+
"pos": [
68+
1.16667,
69+
0.5,
70+
0.50266
71+
],
72+
"e": -231.314431,
73+
"f_opt": 0.401
74+
},
75+
{
76+
"pos": [
77+
1.27778,
78+
0.5,
79+
0.50263
80+
],
81+
"e": -231.347231,
82+
"f_opt": -0.201
83+
},
84+
{
85+
"pos": [
86+
1.38889,
87+
0.5,
88+
0.50254
89+
],
90+
"e": -231.199074,
91+
"f_opt": -0.212
92+
},
93+
{
94+
"pos": [
95+
1.5,
96+
0.5,
97+
0.50258
98+
],
99+
"e": -231.166451,
100+
"f_opt": 0.16
101+
},
102+
{
103+
"pos": [
104+
1.5,
105+
0.61111,
106+
0.5027
107+
],
108+
"e": -231.336101,
109+
"f_opt": 0.413
110+
},
111+
{
112+
"pos": [
113+
1.38889,
114+
0.61111,
115+
0.5027
116+
],
117+
"e": -231.21672,
118+
"f_opt": 0.0254
119+
}
120+
],
121+
"date_started": "Fri Jun 27 13:43:56 +0100 2025",
122+
"probe_method": "min_c",
123+
"probe_species": "H",
124+
"cell": [
125+
[
126+
0.0,
127+
5.077705,
128+
5.077705
129+
],
130+
[
131+
5.077705,
132+
0.0,
133+
5.077705
134+
],
135+
[
136+
5.077705,
137+
5.077705,
138+
0.0
139+
]
140+
]
141+
}

test/data_files/pes.pyyaml

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
cell: !!python/tuple
2+
- !!python/tuple
3+
- 0.0
4+
- 5.077705
5+
- 5.077705
6+
- !!python/tuple
7+
- 5.077705
8+
- 0.0
9+
- 5.077705
10+
- !!python/tuple
11+
- 5.077705
12+
- 5.077705
13+
- 0.0
14+
data:
15+
- e: -231.164733
16+
f_opt: 0.148
17+
pos: !!python/tuple
18+
- 0.5
19+
- 0.5
20+
- 0.50017
21+
- e: -231.345479
22+
f_opt: 0.42
23+
pos: !!python/tuple
24+
- 0.61111
25+
- 0.5
26+
- 0.50029
27+
- e: -231.557748
28+
f_opt: 0.194
29+
pos: !!python/tuple
30+
- 0.72222
31+
- 0.5
32+
- 0.50042
33+
- e: -231.445747
34+
f_opt: -0.582
35+
pos: !!python/tuple
36+
- 0.83333
37+
- 0.5
38+
- 0.50074
39+
- e: -231.041607
40+
f_opt: -0.531
41+
pos: !!python/tuple
42+
- 0.94444
43+
- 0.5
44+
- 0.50149
45+
- e: -231.015796
46+
f_opt: 0.408
47+
pos: !!python/tuple
48+
- 1.05556
49+
- 0.5
50+
- 0.50233
51+
- e: -231.314431
52+
f_opt: 0.401
53+
pos: !!python/tuple
54+
- 1.16667
55+
- 0.5
56+
- 0.50266
57+
- e: -231.347231
58+
f_opt: -0.201
59+
pos: !!python/tuple
60+
- 1.27778
61+
- 0.5
62+
- 0.50263
63+
- e: -231.199074
64+
f_opt: -0.212
65+
pos: !!python/tuple
66+
- 1.38889
67+
- 0.5
68+
- 0.50254
69+
- e: -231.166451
70+
f_opt: 0.16
71+
pos: !!python/tuple
72+
- 1.5
73+
- 0.5
74+
- 0.50258
75+
- e: -231.336101
76+
f_opt: 0.413
77+
pos: !!python/tuple
78+
- 1.5
79+
- 0.61111
80+
- 0.5027
81+
- e: -231.21672
82+
f_opt: 0.0254
83+
pos: !!python/tuple
84+
- 1.38889
85+
- 0.61111
86+
- 0.5027
87+
date_started: Fri Jun 27 13:43:56 +0100 2025
88+
probe_method: min_c
89+
probe_species: H
90+
samples:
91+
a: 10
92+
b: 10
93+
units:
94+
cell: Bohr
95+
energy: eV
96+
force: eV/A

0 commit comments

Comments
 (0)