pesmaker 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pesmaker/__init__.py +22 -0
- pesmaker/__main__.py +21 -0
- pesmaker/artifacts.py +157 -0
- pesmaker/cli.py +525 -0
- pesmaker/cli_next.py +393 -0
- pesmaker/config/__init__.py +21 -0
- pesmaker/config/io.py +105 -0
- pesmaker/config/schema.py +570 -0
- pesmaker/dataset/__init__.py +16 -0
- pesmaker/dataset/extxyz.py +56 -0
- pesmaker/generators/__init__.py +16 -0
- pesmaker/generators/structures.py +513 -0
- pesmaker/jobs/__init__.py +16 -0
- pesmaker/jobs/resources.py +167 -0
- pesmaker/jobs/scripts.py +312 -0
- pesmaker/jobs/submit.py +133 -0
- pesmaker/labelers/__init__.py +16 -0
- pesmaker/labelers/vasp.py +639 -0
- pesmaker/parsers/__init__.py +21 -0
- pesmaker/parsers/ase.py +57 -0
- pesmaker/parsers/vasp.py +28 -0
- pesmaker/results.py +32 -0
- pesmaker/samplers/__init__.py +42 -0
- pesmaker/samplers/gpumd.py +612 -0
- pesmaker/samplers/lammps_mace.py +524 -0
- pesmaker/samplers/selection.py +583 -0
- pesmaker/structures/__init__.py +45 -0
- pesmaker/structures/defects.py +373 -0
- pesmaker/structures/io.py +98 -0
- pesmaker/structures/perturb.py +233 -0
- pesmaker/trainers/__init__.py +16 -0
- pesmaker/trainers/nep.py +71 -0
- pesmaker/workflow/__init__.py +18 -0
- pesmaker/workflow/generate.py +29 -0
- pesmaker/workflow/next.py +455 -0
- pesmaker/workflow/plan.py +100 -0
- pesmaker/workflow/stages.py +54 -0
- pesmaker/workflow/state.py +91 -0
- pesmaker-0.1.0.dist-info/METADATA +286 -0
- pesmaker-0.1.0.dist-info/RECORD +45 -0
- pesmaker-0.1.0.dist-info/WHEEL +5 -0
- pesmaker-0.1.0.dist-info/entry_points.txt +2 -0
- pesmaker-0.1.0.dist-info/licenses/LICENSE +675 -0
- pesmaker-0.1.0.dist-info/licenses/NOTICE +5 -0
- pesmaker-0.1.0.dist-info/top_level.txt +1 -0
pesmaker/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright 2026 Ting Liang and PESMaker development team
|
|
2
|
+
# This file is part of PESMaker.
|
|
3
|
+
#
|
|
4
|
+
# PESMaker is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# PESMaker is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with PESMaker. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
"""PESMaker package public API."""
|
|
17
|
+
|
|
18
|
+
from pesmaker.config.schema import PESMakerConfig
|
|
19
|
+
|
|
20
|
+
__all__ = ["PESMakerConfig", "__contact__", "__version__"]
|
|
21
|
+
__contact__ = "liangting.zj@gmail.com"
|
|
22
|
+
__version__ = "0.1.0"
|
pesmaker/__main__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 Ting Liang and PESMaker development team
|
|
2
|
+
# This file is part of PESMaker.
|
|
3
|
+
#
|
|
4
|
+
# PESMaker is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# PESMaker is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with PESMaker. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
"""Module entry point for `python -m pesmaker`."""
|
|
17
|
+
|
|
18
|
+
from pesmaker.cli import main
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
pesmaker/artifacts.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Copyright 2026 Ting Liang and PESMaker development team
|
|
2
|
+
# This file is part of PESMaker.
|
|
3
|
+
#
|
|
4
|
+
# PESMaker is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
# (at your option) any later version.
|
|
8
|
+
#
|
|
9
|
+
# PESMaker is distributed in the hope that it will be useful,
|
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
# GNU General Public License for more details.
|
|
13
|
+
#
|
|
14
|
+
# You should have received a copy of the GNU General Public License
|
|
15
|
+
# along with PESMaker. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
"""Common manifest, input-discovery, and stage path helpers."""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from pesmaker.config.schema import PESMakerConfig
|
|
26
|
+
|
|
27
|
+
STRUCTURE_INPUT_SUFFIXES = {".cif", ".extxyz", ".poscar", ".vasp", ".xyz"}
|
|
28
|
+
|
|
29
|
+
STRUCTURE_INPUT_NAMES = {"CONTCAR", "POSCAR"}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _section_output_dir(
|
|
33
|
+
config: PESMakerConfig,
|
|
34
|
+
options: dict[str, Any],
|
|
35
|
+
leaf: str,
|
|
36
|
+
) -> Path:
|
|
37
|
+
value = options.get("output_dir")
|
|
38
|
+
return Path(str(value)) if value else Path("runs") / config.project / leaf
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _load_input_records(
|
|
42
|
+
config: PESMakerConfig,
|
|
43
|
+
options: dict[str, Any],
|
|
44
|
+
) -> list[dict[str, Any]]:
|
|
45
|
+
manifest = options.get("input_manifest")
|
|
46
|
+
if manifest:
|
|
47
|
+
manifest_path = Path(str(manifest))
|
|
48
|
+
return _mark_input_records(
|
|
49
|
+
_read_manifest(manifest_path),
|
|
50
|
+
input_dir=manifest_path.parent,
|
|
51
|
+
input_mode="input_manifest",
|
|
52
|
+
)
|
|
53
|
+
input_dir = options.get("input_dir")
|
|
54
|
+
if input_dir:
|
|
55
|
+
return _load_input_dir_records(Path(str(input_dir)), input_mode="input_dir")
|
|
56
|
+
generation_dir = _generated_structures_dir(config)
|
|
57
|
+
return _load_input_dir_records(generation_dir, input_mode="generated_dir")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _load_input_dir_records(
|
|
61
|
+
input_dir: Path, *, input_mode: str
|
|
62
|
+
) -> list[dict[str, Any]]:
|
|
63
|
+
if not input_dir.exists():
|
|
64
|
+
raise ValueError(f"input structure directory does not exist: {input_dir}")
|
|
65
|
+
if not input_dir.is_dir():
|
|
66
|
+
raise ValueError(f"input structure path must be a directory: {input_dir}")
|
|
67
|
+
|
|
68
|
+
manifest_path = input_dir / "manifest.jsonl"
|
|
69
|
+
if manifest_path.exists():
|
|
70
|
+
return _mark_input_records(
|
|
71
|
+
_read_manifest(manifest_path),
|
|
72
|
+
input_dir=input_dir,
|
|
73
|
+
input_mode=f"{input_mode}_manifest",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
paths = _discover_input_structure_files(input_dir)
|
|
77
|
+
if not paths:
|
|
78
|
+
raise ValueError(f"no structure files found in {input_dir}")
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
"path": str(path),
|
|
82
|
+
"input_dir": str(input_dir),
|
|
83
|
+
"input_mode": f"{input_mode}_scan",
|
|
84
|
+
"input_relative_path": path.relative_to(input_dir).as_posix(),
|
|
85
|
+
}
|
|
86
|
+
for path in paths
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _mark_input_records(
|
|
91
|
+
records: list[dict[str, Any]],
|
|
92
|
+
*,
|
|
93
|
+
input_dir: Path,
|
|
94
|
+
input_mode: str,
|
|
95
|
+
) -> list[dict[str, Any]]:
|
|
96
|
+
marked = []
|
|
97
|
+
for source_index, record in enumerate(records):
|
|
98
|
+
path = Path(str(record["path"]))
|
|
99
|
+
if not path.is_absolute() and not path.exists():
|
|
100
|
+
candidate = input_dir / path
|
|
101
|
+
if candidate.exists():
|
|
102
|
+
path = candidate
|
|
103
|
+
marked_record = {
|
|
104
|
+
**record,
|
|
105
|
+
"path": str(path),
|
|
106
|
+
"input_dir": str(input_dir),
|
|
107
|
+
"input_mode": input_mode,
|
|
108
|
+
"source_record_index": record.get("index", source_index),
|
|
109
|
+
}
|
|
110
|
+
try:
|
|
111
|
+
marked_record["input_relative_path"] = path.relative_to(
|
|
112
|
+
input_dir
|
|
113
|
+
).as_posix()
|
|
114
|
+
except ValueError:
|
|
115
|
+
pass
|
|
116
|
+
marked.append(marked_record)
|
|
117
|
+
return marked
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _discover_input_structure_files(input_dir: Path) -> list[Path]:
|
|
121
|
+
return sorted(
|
|
122
|
+
path
|
|
123
|
+
for path in input_dir.rglob("*")
|
|
124
|
+
if path.is_file() and _is_input_structure_file(path)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_input_structure_file(path: Path) -> bool:
|
|
129
|
+
if path.name.upper() in STRUCTURE_INPUT_NAMES:
|
|
130
|
+
return True
|
|
131
|
+
return path.suffix.lower() in STRUCTURE_INPUT_SUFFIXES
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _generated_structures_dir(config: PESMakerConfig) -> Path:
|
|
135
|
+
if config.generation.output_dir:
|
|
136
|
+
return config.generation.output_dir
|
|
137
|
+
local_generated = Path("generated")
|
|
138
|
+
if local_generated.exists():
|
|
139
|
+
return local_generated
|
|
140
|
+
return Path("runs") / config.project / "generated"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _read_manifest(path: Path) -> list[dict[str, Any]]:
|
|
144
|
+
records = []
|
|
145
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
146
|
+
if line.strip():
|
|
147
|
+
record = json.loads(line)
|
|
148
|
+
if "path" in record:
|
|
149
|
+
record = {"path": str(record["path"]), **record}
|
|
150
|
+
records.append(record)
|
|
151
|
+
return records
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _read_optional_file(value: Any, *, default: str) -> str:
|
|
155
|
+
if value:
|
|
156
|
+
return Path(str(value)).read_text(encoding="utf-8")
|
|
157
|
+
return default
|