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.
Files changed (45) hide show
  1. pesmaker/__init__.py +22 -0
  2. pesmaker/__main__.py +21 -0
  3. pesmaker/artifacts.py +157 -0
  4. pesmaker/cli.py +525 -0
  5. pesmaker/cli_next.py +393 -0
  6. pesmaker/config/__init__.py +21 -0
  7. pesmaker/config/io.py +105 -0
  8. pesmaker/config/schema.py +570 -0
  9. pesmaker/dataset/__init__.py +16 -0
  10. pesmaker/dataset/extxyz.py +56 -0
  11. pesmaker/generators/__init__.py +16 -0
  12. pesmaker/generators/structures.py +513 -0
  13. pesmaker/jobs/__init__.py +16 -0
  14. pesmaker/jobs/resources.py +167 -0
  15. pesmaker/jobs/scripts.py +312 -0
  16. pesmaker/jobs/submit.py +133 -0
  17. pesmaker/labelers/__init__.py +16 -0
  18. pesmaker/labelers/vasp.py +639 -0
  19. pesmaker/parsers/__init__.py +21 -0
  20. pesmaker/parsers/ase.py +57 -0
  21. pesmaker/parsers/vasp.py +28 -0
  22. pesmaker/results.py +32 -0
  23. pesmaker/samplers/__init__.py +42 -0
  24. pesmaker/samplers/gpumd.py +612 -0
  25. pesmaker/samplers/lammps_mace.py +524 -0
  26. pesmaker/samplers/selection.py +583 -0
  27. pesmaker/structures/__init__.py +45 -0
  28. pesmaker/structures/defects.py +373 -0
  29. pesmaker/structures/io.py +98 -0
  30. pesmaker/structures/perturb.py +233 -0
  31. pesmaker/trainers/__init__.py +16 -0
  32. pesmaker/trainers/nep.py +71 -0
  33. pesmaker/workflow/__init__.py +18 -0
  34. pesmaker/workflow/generate.py +29 -0
  35. pesmaker/workflow/next.py +455 -0
  36. pesmaker/workflow/plan.py +100 -0
  37. pesmaker/workflow/stages.py +54 -0
  38. pesmaker/workflow/state.py +91 -0
  39. pesmaker-0.1.0.dist-info/METADATA +286 -0
  40. pesmaker-0.1.0.dist-info/RECORD +45 -0
  41. pesmaker-0.1.0.dist-info/WHEEL +5 -0
  42. pesmaker-0.1.0.dist-info/entry_points.txt +2 -0
  43. pesmaker-0.1.0.dist-info/licenses/LICENSE +675 -0
  44. pesmaker-0.1.0.dist-info/licenses/NOTICE +5 -0
  45. 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