atst-tools 2.3.0__tar.gz → 2.3.2__tar.gz

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 (112) hide show
  1. atst_tools-2.3.2/PKG-INFO +184 -0
  2. atst_tools-2.3.2/README.md +147 -0
  3. {atst_tools-2.3.0 → atst_tools-2.3.2}/pyproject.toml +2 -2
  4. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/abacuslite_backend.py +1 -1
  5. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/constant_potential.py +22 -4
  6. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/factory.py +10 -2
  7. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/config_schema.py +55 -7
  8. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/md_post.py +71 -9
  9. atst_tools-2.3.2/src/atst_tools/workflows/cp_md.py +466 -0
  10. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/md.py +33 -4
  11. atst_tools-2.3.2/src/atst_tools.egg-info/PKG-INFO +184 -0
  12. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools.egg-info/SOURCES.txt +1 -0
  13. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools.egg-info/requires.txt +1 -1
  14. atst_tools-2.3.0/PKG-INFO +0 -462
  15. atst_tools-2.3.0/README.md +0 -425
  16. atst_tools-2.3.0/src/atst_tools.egg-info/PKG-INFO +0 -462
  17. {atst_tools-2.3.0 → atst_tools-2.3.2}/COPYING +0 -0
  18. {atst_tools-2.3.0 → atst_tools-2.3.2}/LICENSE +0 -0
  19. {atst_tools-2.3.0 → atst_tools-2.3.2}/setup.cfg +0 -0
  20. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/__init__.py +0 -0
  21. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/api/__init__.py +0 -0
  22. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/api/models.py +0 -0
  23. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/api/runner.py +0 -0
  24. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/api/services.py +0 -0
  25. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/api/validation.py +0 -0
  26. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/bench/__init__.py +0 -0
  27. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/bench/batch_runner.py +0 -0
  28. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/bench/batch_worker.py +0 -0
  29. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/bench/record.py +0 -0
  30. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/bench/sweep.py +0 -0
  31. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/__init__.py +0 -0
  32. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/backend_identity.py +0 -0
  33. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/base.py +0 -0
  34. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/calculators/dp.py +0 -0
  35. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/ABACUSLITE_SNAPSHOT.md +0 -0
  36. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/PATCHES.md +0 -0
  37. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/__init__.py +0 -0
  38. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/__init__.py +0 -0
  39. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/core.py +0 -0
  40. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/io/__init__.py +0 -0
  41. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/io/generalio.py +0 -0
  42. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/io/latestio.py +0 -0
  43. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/io/legacyio.py +0 -0
  44. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/utils/__init__.py +0 -0
  45. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/ASE_interface/abacuslite/utils/ksampling.py +0 -0
  46. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/__init__.py +0 -0
  47. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/LICENSE +0 -0
  48. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/UPSTREAM.md +0 -0
  49. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/__init__.py +0 -0
  50. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/__init__.py +0 -0
  51. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/dmf.py +0 -0
  52. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/fbenm.py +0 -0
  53. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/interpolate.py +0 -0
  54. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/torch/__init__.py +0 -0
  55. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/torch/_torch_config.py +0 -0
  56. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/torch/dmf.py +0 -0
  57. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/torch/fbenm.py +0 -0
  58. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/external/pydmf/dmf/torch/interpolate.py +0 -0
  59. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/__init__.py +0 -0
  60. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/autoneb.py +0 -0
  61. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/ccqn.py +0 -0
  62. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/dimer.py +0 -0
  63. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/neb.py +0 -0
  64. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/mep/sella.py +0 -0
  65. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/__init__.py +0 -0
  66. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/cli_dispatch.py +0 -0
  67. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/counters.py +0 -0
  68. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/devices.py +0 -0
  69. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/errors.py +0 -0
  70. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/evidence.py +0 -0
  71. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/runtime/launch.py +0 -0
  72. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/scripts/__init__.py +0 -0
  73. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/scripts/cli.py +0 -0
  74. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/scripts/cli_impl.py +0 -0
  75. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/scripts/main.py +0 -0
  76. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/__init__.py +0 -0
  77. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/abacus_io.py +0 -0
  78. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/analysis.py +0 -0
  79. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/artifacts.py +0 -0
  80. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/banner.py +0 -0
  81. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/config.py +0 -0
  82. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/config_docs.py +0 -0
  83. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/convergence.py +0 -0
  84. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/dmf_validation.py +0 -0
  85. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/electrochemistry.py +0 -0
  86. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/gate_compensation.py +0 -0
  87. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/geometry.py +0 -0
  88. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/idpp.py +0 -0
  89. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/io.py +0 -0
  90. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/mpi.py +0 -0
  91. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/neb_endpoints.py +0 -0
  92. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/plot.py +0 -0
  93. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/post.py +0 -0
  94. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/reactive_modes.py +0 -0
  95. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/restart_helpers.py +0 -0
  96. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/reverse_config.py +0 -0
  97. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/sella_event_summary.py +0 -0
  98. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/sella_events.py +0 -0
  99. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/summary.py +0 -0
  100. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/thermochemistry.py +0 -0
  101. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/utils/ts_validation.py +0 -0
  102. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/__init__.py +0 -0
  103. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/constant_potential.py +0 -0
  104. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/d2s.py +0 -0
  105. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/dmf.py +0 -0
  106. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/execution.py +0 -0
  107. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/irc.py +0 -0
  108. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/relax.py +0 -0
  109. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools/workflows/vibration.py +0 -0
  110. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools.egg-info/dependency_links.txt +0 -0
  111. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools.egg-info/entry_points.txt +0 -0
  112. {atst_tools-2.3.0 → atst_tools-2.3.2}/src/atst_tools.egg-info/top_level.txt +0 -0
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: atst-tools
3
+ Version: 2.3.2
4
+ Summary: Advanced ASE Transition State Tools for ABACUS and Deep-Potential
5
+ License-Expression: LGPL-3.0-or-later
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ License-File: COPYING
10
+ Requires-Dist: abacuslite<2,>=1.2.1
11
+ Requires-Dist: ase>=3.28.0
12
+ Requires-Dist: numpy<3,>=1.26
13
+ Requires-Dist: scipy<2,>=1.13
14
+ Requires-Dist: pydantic<3,>=2
15
+ Requires-Dist: ruamel.yaml<0.20,>=0.18
16
+ Requires-Dist: seekpath<3,>=2.2
17
+ Requires-Dist: sella<3,>=2.5
18
+ Provides-Extra: plot
19
+ Requires-Dist: matplotlib<4,>=3.9; extra == "plot"
20
+ Provides-Extra: dp
21
+ Requires-Dist: deepmd-kit>=3.1.3; extra == "dp"
22
+ Provides-Extra: parallel
23
+ Requires-Dist: mpi4py>=4.1.2; extra == "parallel"
24
+ Provides-Extra: test
25
+ Requires-Dist: pytest<10,>=8.4; extra == "test"
26
+ Requires-Dist: tomli<3,>=2.0; extra == "test"
27
+ Provides-Extra: release
28
+ Requires-Dist: build<2,>=1.5; extra == "release"
29
+ Requires-Dist: twine<7,>=6.2; extra == "release"
30
+ Requires-Dist: tomli<3,>=2.0; extra == "release"
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest<10,>=8.4; extra == "dev"
33
+ Requires-Dist: build<2,>=1.5; extra == "dev"
34
+ Requires-Dist: twine<7,>=6.2; extra == "dev"
35
+ Requires-Dist: tomli<3,>=2.0; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ # ATST-Tools
39
+
40
+ [![Version](https://img.shields.io/badge/version-2.3.2-blue)](pyproject.toml)
41
+ [![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](pyproject.toml)
42
+ [![License](https://img.shields.io/badge/license-LGPL--v3-blue)](#license)
43
+
44
+ ATST-Tools is an **ASE transition-state workflow toolkit** for ABACUS and
45
+ DeePMD-kit. Run calculations from YAML with the `atst` command, or use the
46
+ [Python API](docs/user/PYTHON_API_REFERENCE.md) to integrate the same workflows
47
+ into another program.
48
+
49
+ ```bash
50
+ atst run config.yaml
51
+ ```
52
+
53
+ Chinese guide: [中文 · GitHub](https://github.com/QuantumMisaka/atst-tools/blob/main/docs/user/USER_GUIDE_CN.md)
54
+ · [Gitee](https://gitee.com/jamesmisaka/atst-tools/blob/main/docs/user/USER_GUIDE_CN.md).
55
+ Examples: [GitHub](https://github.com/QuantumMisaka/atst-tools/tree/main/examples)
56
+ · [Gitee](https://gitee.com/jamesmisaka/atst-tools/tree/main/examples).
57
+
58
+ ## What You Can Run
59
+
60
+ | Workflow | `calculation.type` |
61
+ | :--- | :--- |
62
+ | NEB / DyNEB and adaptive-image NEB | `neb`, `autoneb` |
63
+ | Dimer, Sella and CCQN saddle searches | `dimer`, `sella`, `ccqn` |
64
+ | Double-ended to single-ended TS search | `d2s` |
65
+ | Structure relaxation | `relax` |
66
+ | Vibrations and thermochemistry | `vibration` |
67
+ | Intrinsic reaction coordinates | `irc` |
68
+ | ASE-driven or ABACUS-native molecular dynamics | `md` |
69
+ | Experimental Direct MaxFlux path search | `dmf` |
70
+ | Experimental ABACUS constant-potential single points and serial scans | `constant_potential` |
71
+
72
+ Experimental constant-potential fixed-cell relaxation, NEB and ASE-driven MD
73
+ use the explicit `compensated_gate` boundary. See the
74
+ [configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation-development-candidate)
75
+ for setup and supported boundaries.
76
+
77
+ Local commands prepare paths and ABACUS inputs, inspect trajectories, and
78
+ summarize existing results. See the [CLI reference](docs/user/CLI_REFERENCE.md).
79
+ A transition-state candidate can be screened with `atst vibration validate`
80
+ using saved vibration results; workflow completion alone does not establish a
81
+ validated TS. Current support and experimental limits are summarized in the
82
+ [feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md).
83
+
84
+ ## Installation
85
+
86
+ Requires Python 3.10 or later:
87
+
88
+ ```bash
89
+ pip install atst-tools
90
+ atst --version
91
+ ```
92
+
93
+ | Use | Installation |
94
+ | :--- | :--- |
95
+ | Serial workflows, including serial NEB/AutoNEB | `pip install atst-tools` |
96
+ | DeePMD-kit calculator | `pip install "atst-tools[dp]"` |
97
+ | Image-parallel NEB/AutoNEB | `pip install "atst-tools[parallel]"` |
98
+
99
+ The default installation includes `abacuslite>=1.2.1,<2` and Sella; it does not
100
+ install `mpi4py`. ABACUS calculations also need an ABACUS executable and the
101
+ pseudopotential/orbital files referenced by the configuration. DP calculations
102
+ need a compatible model. Image-parallel runs require a matching MPI runtime;
103
+ DMF additionally requires `cyipopt` and IPOPT. See the
104
+ [MPI setup guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md#neb-image-level-mpi) for
105
+ image-parallel setup and the [examples guide](examples/README.md#usage) for
106
+ backend inputs.
107
+
108
+ ## Quick Start
109
+
110
+ Obtain examples at the tag matching your installed version (shown for 2.3.2):
111
+
112
+ ```bash
113
+ git clone --branch v2.3.2 https://github.com/QuantumMisaka/atst-tools.git
114
+ cd atst-tools
115
+ ```
116
+
117
+ First try a local path-preparation command; it needs no ABACUS, DP model or GPU:
118
+
119
+ ```bash
120
+ cd examples/09_lightweight_cli
121
+ atst neb make inputs/init.xyz inputs/final.xyz 3 -o first_chain.traj --method linear
122
+ atst neb summary inputs/neb_result.extxyz --n-max 1 --tail 5
123
+ cd ../..
124
+ ```
125
+
126
+ `first_chain.traj` contains five initial-guess frames. To inspect a calculation
127
+ configuration, enter its example directory so relative input paths resolve:
128
+
129
+ ```bash
130
+ cd examples/06_relax_H2-Au
131
+ atst config validate config.yaml --print-normalized
132
+ atst run --dry-run config.yaml
133
+ # After configuring the ABACUS runtime and compute resources:
134
+ atst run config.yaml
135
+ ```
136
+
137
+ This example uses four ABACUS MPI ranks, the GPU `cusolver` solver and a
138
+ one-step optimization limit. Validation and dry-run do not launch ABACUS;
139
+ check the resulting convergence status separately. Follow the
140
+ [examples guide](examples/README.md#usage) to inspect results and choose other
141
+ methods or DP variants. A complete walkthrough is also available in the
142
+ [Chinese guide](docs/user/USER_GUIDE_CN.md#3-10-分钟快速上手).
143
+
144
+ ## Documentation
145
+
146
+ | Task | Reference |
147
+ | :--- | :--- |
148
+ | Configure a calculation | [Configuration reference](docs/user/CONFIG_REFERENCE.md) · [All YAML fields](docs/user/YAML_INPUT_VARIABLES.md) |
149
+ | Use CLI commands | [CLI reference](docs/user/CLI_REFERENCE.md) |
150
+ | Configure ABACUS input/output and parallelism | [ABACUS wrapper guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md) |
151
+ | Embed workflows or use the JSON process runner | [Python API](docs/user/PYTHON_API_REFERENCE.md) |
152
+ | Check support and releases | [Feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md) · [2.3.2 release notes](docs/releases/RELEASE_NOTES_2.3.2.md) |
153
+ | Contribute | [Developer handover](docs/developer/HANDOVER.md) · [Source architecture](src/atst_tools/README.md) |
154
+
155
+ The primary documentation is in English, with a [Chinese user guide](docs/user/USER_GUIDE_CN.md).
156
+ The [documentation index](docs/index.md) organizes user, developer and project
157
+ status material. GitHub is the development source; Gitee is a manually updated
158
+ mirror. PyPI distributes the Python package; obtain examples from the repository.
159
+
160
+ ## References
161
+
162
+ If you publish work that uses the transition-state workflows in ATST-Tools,
163
+ please cite the underlying methods alongside ATST-Tools itself:
164
+
165
+ - ATST-Tools and the double-to-single (D2S) transition-state workflow:
166
+ Liu, Z.-Q.; Deng, Z.; Zhao, H.; Wang, H.; Chen, M.; Jiang, H.
167
+ *FT²DP: Large Atomic Model Fine-Tuned Machine Learning Potential for
168
+ Accelerating Atomistic Simulation of Iron-Based Fischer–Tropsch Synthesis.*
169
+ J. Mater. Inform. **5** (2), 27 (2025).
170
+ <https://doi.org/10.20517/jmi.2024.105>
171
+ - Sella saddle-point search:
172
+ Hermes, E. D.; Sargsyan, K.; Najm, H. N.; Zádor, J.
173
+ *Sella, an Open-Source Automation-Friendly Molecular Saddle Point Optimizer.*
174
+ J. Chem. Theory Comput. **18** (11), 6974-6988 (2022).
175
+ <https://doi.org/10.1021/acs.jctc.2c00395>
176
+ - CCQN (cone-shaped constrained quasi-Newton) transition-state optimization:
177
+ Wu, Y.; Wang, H. *Cone-Shaped Constrained Quasi-Newton Method: Efficient
178
+ and Robust Single-Ended Transition State Optimization Algorithm.*
179
+ J. Chem. Theory Comput. **21** (18), 9054-9065 (2025).
180
+ <https://doi.org/10.1021/acs.jctc.5c01015>
181
+
182
+ ## License
183
+
184
+ LGPL-v3 License.
@@ -0,0 +1,147 @@
1
+ # ATST-Tools
2
+
3
+ [![Version](https://img.shields.io/badge/version-2.3.2-blue)](pyproject.toml)
4
+ [![Python](https://img.shields.io/badge/python-%3E%3D3.10-blue)](pyproject.toml)
5
+ [![License](https://img.shields.io/badge/license-LGPL--v3-blue)](#license)
6
+
7
+ ATST-Tools is an **ASE transition-state workflow toolkit** for ABACUS and
8
+ DeePMD-kit. Run calculations from YAML with the `atst` command, or use the
9
+ [Python API](docs/user/PYTHON_API_REFERENCE.md) to integrate the same workflows
10
+ into another program.
11
+
12
+ ```bash
13
+ atst run config.yaml
14
+ ```
15
+
16
+ Chinese guide: [中文 · GitHub](https://github.com/QuantumMisaka/atst-tools/blob/main/docs/user/USER_GUIDE_CN.md)
17
+ · [Gitee](https://gitee.com/jamesmisaka/atst-tools/blob/main/docs/user/USER_GUIDE_CN.md).
18
+ Examples: [GitHub](https://github.com/QuantumMisaka/atst-tools/tree/main/examples)
19
+ · [Gitee](https://gitee.com/jamesmisaka/atst-tools/tree/main/examples).
20
+
21
+ ## What You Can Run
22
+
23
+ | Workflow | `calculation.type` |
24
+ | :--- | :--- |
25
+ | NEB / DyNEB and adaptive-image NEB | `neb`, `autoneb` |
26
+ | Dimer, Sella and CCQN saddle searches | `dimer`, `sella`, `ccqn` |
27
+ | Double-ended to single-ended TS search | `d2s` |
28
+ | Structure relaxation | `relax` |
29
+ | Vibrations and thermochemistry | `vibration` |
30
+ | Intrinsic reaction coordinates | `irc` |
31
+ | ASE-driven or ABACUS-native molecular dynamics | `md` |
32
+ | Experimental Direct MaxFlux path search | `dmf` |
33
+ | Experimental ABACUS constant-potential single points and serial scans | `constant_potential` |
34
+
35
+ Experimental constant-potential fixed-cell relaxation, NEB and ASE-driven MD
36
+ use the explicit `compensated_gate` boundary. See the
37
+ [configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation-development-candidate)
38
+ for setup and supported boundaries.
39
+
40
+ Local commands prepare paths and ABACUS inputs, inspect trajectories, and
41
+ summarize existing results. See the [CLI reference](docs/user/CLI_REFERENCE.md).
42
+ A transition-state candidate can be screened with `atst vibration validate`
43
+ using saved vibration results; workflow completion alone does not establish a
44
+ validated TS. Current support and experimental limits are summarized in the
45
+ [feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md).
46
+
47
+ ## Installation
48
+
49
+ Requires Python 3.10 or later:
50
+
51
+ ```bash
52
+ pip install atst-tools
53
+ atst --version
54
+ ```
55
+
56
+ | Use | Installation |
57
+ | :--- | :--- |
58
+ | Serial workflows, including serial NEB/AutoNEB | `pip install atst-tools` |
59
+ | DeePMD-kit calculator | `pip install "atst-tools[dp]"` |
60
+ | Image-parallel NEB/AutoNEB | `pip install "atst-tools[parallel]"` |
61
+
62
+ The default installation includes `abacuslite>=1.2.1,<2` and Sella; it does not
63
+ install `mpi4py`. ABACUS calculations also need an ABACUS executable and the
64
+ pseudopotential/orbital files referenced by the configuration. DP calculations
65
+ need a compatible model. Image-parallel runs require a matching MPI runtime;
66
+ DMF additionally requires `cyipopt` and IPOPT. See the
67
+ [MPI setup guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md#neb-image-level-mpi) for
68
+ image-parallel setup and the [examples guide](examples/README.md#usage) for
69
+ backend inputs.
70
+
71
+ ## Quick Start
72
+
73
+ Obtain examples at the tag matching your installed version (shown for 2.3.2):
74
+
75
+ ```bash
76
+ git clone --branch v2.3.2 https://github.com/QuantumMisaka/atst-tools.git
77
+ cd atst-tools
78
+ ```
79
+
80
+ First try a local path-preparation command; it needs no ABACUS, DP model or GPU:
81
+
82
+ ```bash
83
+ cd examples/09_lightweight_cli
84
+ atst neb make inputs/init.xyz inputs/final.xyz 3 -o first_chain.traj --method linear
85
+ atst neb summary inputs/neb_result.extxyz --n-max 1 --tail 5
86
+ cd ../..
87
+ ```
88
+
89
+ `first_chain.traj` contains five initial-guess frames. To inspect a calculation
90
+ configuration, enter its example directory so relative input paths resolve:
91
+
92
+ ```bash
93
+ cd examples/06_relax_H2-Au
94
+ atst config validate config.yaml --print-normalized
95
+ atst run --dry-run config.yaml
96
+ # After configuring the ABACUS runtime and compute resources:
97
+ atst run config.yaml
98
+ ```
99
+
100
+ This example uses four ABACUS MPI ranks, the GPU `cusolver` solver and a
101
+ one-step optimization limit. Validation and dry-run do not launch ABACUS;
102
+ check the resulting convergence status separately. Follow the
103
+ [examples guide](examples/README.md#usage) to inspect results and choose other
104
+ methods or DP variants. A complete walkthrough is also available in the
105
+ [Chinese guide](docs/user/USER_GUIDE_CN.md#3-10-分钟快速上手).
106
+
107
+ ## Documentation
108
+
109
+ | Task | Reference |
110
+ | :--- | :--- |
111
+ | Configure a calculation | [Configuration reference](docs/user/CONFIG_REFERENCE.md) · [All YAML fields](docs/user/YAML_INPUT_VARIABLES.md) |
112
+ | Use CLI commands | [CLI reference](docs/user/CLI_REFERENCE.md) |
113
+ | Configure ABACUS input/output and parallelism | [ABACUS wrapper guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md) |
114
+ | Embed workflows or use the JSON process runner | [Python API](docs/user/PYTHON_API_REFERENCE.md) |
115
+ | Check support and releases | [Feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md) · [2.3.2 release notes](docs/releases/RELEASE_NOTES_2.3.2.md) |
116
+ | Contribute | [Developer handover](docs/developer/HANDOVER.md) · [Source architecture](src/atst_tools/README.md) |
117
+
118
+ The primary documentation is in English, with a [Chinese user guide](docs/user/USER_GUIDE_CN.md).
119
+ The [documentation index](docs/index.md) organizes user, developer and project
120
+ status material. GitHub is the development source; Gitee is a manually updated
121
+ mirror. PyPI distributes the Python package; obtain examples from the repository.
122
+
123
+ ## References
124
+
125
+ If you publish work that uses the transition-state workflows in ATST-Tools,
126
+ please cite the underlying methods alongside ATST-Tools itself:
127
+
128
+ - ATST-Tools and the double-to-single (D2S) transition-state workflow:
129
+ Liu, Z.-Q.; Deng, Z.; Zhao, H.; Wang, H.; Chen, M.; Jiang, H.
130
+ *FT²DP: Large Atomic Model Fine-Tuned Machine Learning Potential for
131
+ Accelerating Atomistic Simulation of Iron-Based Fischer–Tropsch Synthesis.*
132
+ J. Mater. Inform. **5** (2), 27 (2025).
133
+ <https://doi.org/10.20517/jmi.2024.105>
134
+ - Sella saddle-point search:
135
+ Hermes, E. D.; Sargsyan, K.; Najm, H. N.; Zádor, J.
136
+ *Sella, an Open-Source Automation-Friendly Molecular Saddle Point Optimizer.*
137
+ J. Chem. Theory Comput. **18** (11), 6974-6988 (2022).
138
+ <https://doi.org/10.1021/acs.jctc.2c00395>
139
+ - CCQN (cone-shaped constrained quasi-Newton) transition-state optimization:
140
+ Wu, Y.; Wang, H. *Cone-Shaped Constrained Quasi-Newton Method: Efficient
141
+ and Robust Single-Ended Transition State Optimization Algorithm.*
142
+ J. Chem. Theory Comput. **21** (18), 9054-9065 (2025).
143
+ <https://doi.org/10.1021/acs.jctc.5c01015>
144
+
145
+ ## License
146
+
147
+ LGPL-v3 License.
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "atst-tools"
7
- version = "2.3.0"
7
+ version = "2.3.2"
8
8
  description = "Advanced ASE Transition State Tools for ABACUS and Deep-Potential"
9
9
  readme = "README.md"
10
10
  license = "LGPL-3.0-or-later"
11
11
  requires-python = ">=3.10"
12
12
  dependencies = [
13
- "abacuslite>=1.2.0,<2",
13
+ "abacuslite>=1.2.1,<2",
14
14
  "ase>=3.28.0",
15
15
  "numpy>=1.26,<3",
16
16
  "scipy>=1.13,<2",
@@ -17,7 +17,7 @@ BackendSource = Literal["external"]
17
17
  def _load_abacuslite_backend():
18
18
  if find_spec("abacuslite") is None:
19
19
  raise ModuleNotFoundError(
20
- "abacuslite is required by ATST-Tools; install abacuslite>=1.2.0,<2",
20
+ "abacuslite is required by ATST-Tools; install abacuslite>=1.2.1,<2",
21
21
  name="abacuslite",
22
22
  )
23
23
  from abacuslite import Abacus, AbacusProfile
@@ -1074,6 +1074,14 @@ class ConstantPotentialCalculator(Calculator):
1074
1074
  identity["fixed_hamiltonian"] = copy.deepcopy(self._fixed_hamiltonian_identity)
1075
1075
  return identity
1076
1076
 
1077
+ def stage_initial_electrons(self, nelec: float) -> None:
1078
+ """Stage a validated next-solve guess without clearing current results.
1079
+
1080
+ This is for sequential CP MD evaluations. The electron count is only
1081
+ an initial guess; the next geometry still runs the complete CP loop.
1082
+ """
1083
+ self.initial_electrons = self._validate_candidate(_finite(nelec, "initial_electrons"))
1084
+
1077
1085
  def set(self, **kwargs: Any) -> dict[str, Any]:
1078
1086
  """Update CP parameters and invalidate cached ASE results."""
1079
1087
  changed = super().set(**kwargs)
@@ -1254,8 +1262,12 @@ class ConstantPotentialCalculator(Calculator):
1254
1262
  # consumes the published CP result. Suppress them on this private
1255
1263
  # probe so Hookean/constraint energy and force contributions are
1256
1264
  # not applied a second time inside the electronic loop.
1257
- energy = float(probe.get_potential_energy(force_consistent=False, apply_constraint=False))
1258
- free_energy = float(probe.get_potential_energy(force_consistent=True, apply_constraint=False))
1265
+ kohn_sham_energy = float(
1266
+ probe.get_potential_energy(force_consistent=False, apply_constraint=False)
1267
+ )
1268
+ force_consistent_energy = float(
1269
+ probe.get_potential_energy(force_consistent=True, apply_constraint=False)
1270
+ )
1259
1271
  forces = np.asarray(probe.get_forces(apply_constraint=False), dtype=float)
1260
1272
  except ConstantPotentialError:
1261
1273
  raise
@@ -1274,12 +1286,17 @@ class ConstantPotentialCalculator(Calculator):
1274
1286
  raise ConstantPotentialEvaluationError(
1275
1287
  f"fixed-electron evaluation at nelec={nelec} returned invalid forces"
1276
1288
  )
1289
+ # Preserve the backend's ASE energy keys for the compensated boundary.
1290
+ # The reference-only FCP convention uses the explicit no-entropy
1291
+ # energy, without changing the force-consistent free energy.
1277
1292
  raw_energy = _first_finite(raw_results, ("energy",))
1278
1293
  raw_free_energy = _first_finite(raw_results, ("free_energy",))
1279
1294
  if raw_energy is None:
1280
- raw_energy = _finite(energy, "raw energy")
1295
+ raw_energy = _finite(kohn_sham_energy, "raw energy")
1281
1296
  if raw_free_energy is None:
1282
- raw_free_energy = _finite(free_energy, "raw free energy")
1297
+ raw_free_energy = _finite(force_consistent_energy, "raw free energy")
1298
+ if self.energy_boundary == "reference_fcp" and "energy_without_entropy" in raw_results:
1299
+ raw_energy = _finite(raw_results["energy_without_entropy"], "energy without entropy")
1283
1300
  efermi = _backend_efermi(inner, raw_results)
1284
1301
  if efermi is None:
1285
1302
  raise ConstantPotentialEvaluationError(
@@ -1534,6 +1551,7 @@ class ConstantPotentialCalculator(Calculator):
1534
1551
  "scf_converged": True,
1535
1552
  "cp_converged": True,
1536
1553
  "cp_iterations": int(final["evaluation_index"]),
1554
+ "capacitance_e_per_V": float(capacitance),
1537
1555
  "cp_history": self._jsonable(self.evaluation_history),
1538
1556
  "cp_identity": self.constant_potential_identity,
1539
1557
  }
@@ -22,6 +22,10 @@ from atst_tools.calculators.constant_potential import (
22
22
  from atst_tools.calculators.dp import DeepPotentialFactory
23
23
  from atst_tools.runtime import counters as runtime_counters
24
24
  from atst_tools.runtime import launch as runtime_launch
25
+ from atst_tools.utils.config_schema import (
26
+ ConstantPotentialConfig,
27
+ validate_constant_potential_md_scope,
28
+ )
25
29
  from atst_tools.utils.mpi import mpi_launcher_detected
26
30
 
27
31
 
@@ -228,14 +232,18 @@ def _constant_potential_calculator(
228
232
  config: Dict[str, Any], cp_config: Dict[str, Any], kwargs: Dict[str, Any]
229
233
  ) -> ConstantPotentialCalculator:
230
234
  """Build one CP decorator around fresh, unwrapped ABACUS calculators."""
231
- supported_workflows = {"constant_potential", "relax", "neb"}
232
- workflow = config.get("calculation", {}).get("type") if isinstance(config.get("calculation"), dict) else None
235
+ supported_workflows = {"constant_potential", "relax", "neb", "md"}
236
+ calculation = config.get("calculation", {})
237
+ workflow = calculation.get("type") if isinstance(calculation, dict) else None
233
238
  if workflow is not None and workflow not in supported_workflows:
234
239
  raise ValueError(
235
240
  "calculator.constant_potential is supported only for calculation.type in "
236
241
  f"{sorted(supported_workflows)}; got {workflow!r}"
237
242
  )
238
243
  options = dict(cp_config)
244
+ if workflow == "md":
245
+ validate_constant_potential_md_scope(calculation, options)
246
+ options = ConstantPotentialConfig.model_validate(options).model_dump(mode="python")
239
247
  energy_boundary = str(options.get("energy_boundary", "reference_fcp"))
240
248
  if energy_boundary not in {"reference_fcp", "compensated_gate"}:
241
249
  raise ValueError("calculator.constant_potential.energy_boundary is unsupported")
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import math
6
6
  from types import UnionType
7
- from typing import Annotated, Any, Dict, Literal, Union, get_args, get_origin
7
+ from typing import Annotated, Any, Dict, Literal, Mapping, Union, get_args, get_origin
8
8
 
9
9
  from pydantic import (
10
10
  AliasChoices,
@@ -144,7 +144,7 @@ class ConstantPotentialConfig(StrictConfig):
144
144
  target_mu_ev: float | None = Field(
145
145
  default=None,
146
146
  allow_inf_nan=False,
147
- description="Target conjugate electron chemical potential in eV for compensated_gate.",
147
+ description="Scalar target chemical potential in eV for compensated_gate; required for CP MD.",
148
148
  )
149
149
  target_mu_values_ev: list[float] | None = Field(
150
150
  default=None,
@@ -868,8 +868,12 @@ class MDCalculation(StrictConfig):
868
868
  )
869
869
  init_structure: str = Field(description="Initial structure file.")
870
870
  steps: int = Field(default=100, gt=0, description="Number of MD steps.")
871
- ensemble: Literal["nve", "nvt", "npt"] = Field(default="nvt", description="ASE MD ensemble.")
872
- algorithm: str = Field(default="bussi", description="ASE MD algorithm.")
871
+ ensemble: Literal["nve", "nvt", "npt"] = Field(
872
+ default="nvt", description="ASE MD ensemble; CP MD supports fixed-cell nve and nvt only."
873
+ )
874
+ algorithm: str = Field(
875
+ default="bussi", description="ASE MD algorithm; CP MD supports velocityverlet, bussi, langevin and nvtberendsen."
876
+ )
873
877
  timestep_fs: float = Field(default=1.0, gt=0, description="ASE MD timestep in fs.")
874
878
  temperature_K: float = Field(default=300.0, gt=0, description="Target or initial temperature in K.")
875
879
  seed: int | None = Field(default=None, description="Random seed for initial velocities.")
@@ -911,6 +915,46 @@ class MDCalculation(StrictConfig):
911
915
  return self
912
916
 
913
917
 
918
+ def validate_constant_potential_md_scope(
919
+ calculation: Mapping[str, Any], constant_potential: Mapping[str, Any]
920
+ ) -> None:
921
+ """Reject MD/CP combinations outside the fixed-cell ASE contract.
922
+
923
+ Args:
924
+ calculation: Raw or normalized MD calculation settings.
925
+ constant_potential: Raw or normalized CP decorator settings.
926
+
927
+ Raises:
928
+ ValueError: If the MD driver, integrator, or electronic boundary is unsupported.
929
+ """
930
+ if calculation.get("driver", "ase") != "ase":
931
+ raise ValueError("constant-potential MD requires calculation.driver=ase")
932
+ ensemble = calculation.get("ensemble", "nvt")
933
+ if ensemble not in {"nve", "nvt"}:
934
+ raise ValueError("constant-potential MD requires a fixed-cell nve or nvt ensemble")
935
+ algorithm = str(calculation.get("algorithm", "bussi")).lower()
936
+ allowed = {"nve": {"velocityverlet"}, "nvt": {"bussi", "langevin", "nvtberendsen"}}
937
+ if algorithm not in allowed[ensemble]:
938
+ raise ValueError(
939
+ f"calculation.algorithm={algorithm!r} is not supported for constant-potential MD ensemble={ensemble!r}"
940
+ )
941
+ if constant_potential.get("energy_boundary") != "compensated_gate":
942
+ raise ValueError("constant-potential MD requires energy_boundary=compensated_gate")
943
+ if str(constant_potential.get("reference_electrode", "SHE")).strip().lower() != "custom":
944
+ raise ValueError("constant-potential MD requires reference_electrode=custom")
945
+ target = constant_potential.get("target_mu_ev")
946
+ if constant_potential.get("target_mu_values_ev") is not None or constant_potential.get("potentials_v") is not None:
947
+ raise ValueError("constant-potential MD requires one scalar target_mu_ev, not a target list")
948
+ if target is None or isinstance(target, (bool, list, tuple, dict)):
949
+ raise ValueError("constant-potential MD requires one scalar target_mu_ev")
950
+ try:
951
+ finite_target = math.isfinite(float(target))
952
+ except (TypeError, ValueError):
953
+ finite_target = False
954
+ if not finite_target:
955
+ raise ValueError("constant-potential MD requires one finite scalar target_mu_ev")
956
+
957
+
914
958
  CalculationConfig = Annotated[
915
959
  Union[
916
960
  ConstantPotentialCalculation,
@@ -1075,7 +1119,7 @@ class CalculatorConfig(StrictConfig):
1075
1119
  dp: DPConfig | None = Field(default=None, description="DeepMD-kit calculator settings.")
1076
1120
  constant_potential: ConstantPotentialConfig | None = Field(
1077
1121
  default=None,
1078
- description="Optional strict constant-potential decorator settings.",
1122
+ description="Optional strict constant-potential decorator settings; MD requires ASE fixed-cell compensated_gate with one scalar target.",
1079
1123
  )
1080
1124
 
1081
1125
  @model_validator(mode="after")
@@ -1233,10 +1277,14 @@ class ATSTConfig(StrictConfig):
1233
1277
  if merged_parameters.get("two_fermi"):
1234
1278
  raise ValueError("calculator.constant_potential does not support two_fermi")
1235
1279
  workflow = self.calculation.type
1236
- if workflow not in {"constant_potential", "relax", "neb"}:
1280
+ if workflow not in {"constant_potential", "relax", "neb", "md"}:
1237
1281
  raise ValueError(
1238
1282
  "calculator.constant_potential is supported only for calculation.type="
1239
- "constant_potential, relax, or neb"
1283
+ "constant_potential, relax, neb, or md"
1284
+ )
1285
+ if workflow == "md":
1286
+ validate_constant_potential_md_scope(
1287
+ self.calculation.model_dump(mode="python"), cp.model_dump(mode="python")
1240
1288
  )
1241
1289
  if workflow in {"relax", "neb"} and (
1242
1290
  cp.potentials_v is not None or cp.target_mu_values_ev is not None
@@ -2,12 +2,18 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import math
5
6
  from pathlib import Path
6
7
  from typing import Any
7
8
 
8
9
  import numpy as np
9
10
  from ase.io import read, write
10
11
 
12
+ from atst_tools.calculators.constant_potential import (
13
+ CP_FACTS_INFO_KEY,
14
+ CP_IDENTITY_INFO_KEY,
15
+ read_constant_potential_facts,
16
+ )
11
17
  from atst_tools.utils.summary import max_force, energy, write_summary_json
12
18
 
13
19
 
@@ -36,6 +42,70 @@ def _write_md_summary(summary: dict[str, Any], output: str | Path) -> None:
36
42
  write_summary_json(summary, output)
37
43
 
38
44
 
45
+ def _saved_step_time(atoms: Any, frame_index: int) -> dict[str, Any]:
46
+ """Return saved MD progress, falling back to frame index for old trajectories."""
47
+ step_key = "atst_md_step"
48
+ time_key = "atst_md_time_fs"
49
+ has_step = step_key in atoms.info
50
+ has_time = time_key in atoms.info
51
+ if not has_step and not has_time:
52
+ return {"step": frame_index}
53
+ if not has_step:
54
+ raise ValueError(f"saved MD frame is missing {step_key}")
55
+ if not has_time:
56
+ raise ValueError(f"saved MD frame is missing {time_key}")
57
+ step = atoms.info[step_key]
58
+ if isinstance(step, (bool, np.bool_)) or not isinstance(step, (int, np.integer)) or step < 0:
59
+ raise ValueError(f"saved MD frame has invalid {step_key}")
60
+ time_fs = atoms.info[time_key]
61
+ if (
62
+ isinstance(time_fs, (bool, np.bool_))
63
+ or not isinstance(time_fs, (int, float, np.integer, np.floating))
64
+ or not math.isfinite(float(time_fs))
65
+ or time_fs < 0
66
+ ):
67
+ raise ValueError(f"saved MD frame has invalid {time_key}")
68
+ return {"step": int(step), "time_fs": float(time_fs)}
69
+
70
+
71
+ def _saved_constant_potential(atoms: Any) -> dict[str, Any] | None:
72
+ """Summarize only a complete CP facts envelope for the saved geometry."""
73
+ if CP_FACTS_INFO_KEY not in atoms.info and CP_IDENTITY_INFO_KEY not in atoms.info:
74
+ return None
75
+ payload = read_constant_potential_facts(atoms)
76
+ if payload is None:
77
+ raise ValueError("saved MD frame has malformed or stale constant-potential facts")
78
+ facts = payload["facts"]
79
+ return {
80
+ "energy_boundary": facts["energy_boundary"],
81
+ "energy_definition": payload["identity"]["energy_definition"],
82
+ "nelec": float(facts["nelec"]),
83
+ "mu_target_ev": float(facts["mu_target"]),
84
+ "mu_calc_ev": float(facts["mu_calc"]),
85
+ "residual_mu_ev": float(facts["residual_mu"]),
86
+ "cp_iterations": int(facts["cp_iterations"]),
87
+ "raw_energy_eV": float(facts["raw_energy"]),
88
+ "raw_free_energy_eV": float(facts["raw_free_energy"]),
89
+ "grand_potential_eV": float(facts["energy"]),
90
+ "grand_potential_free_energy_eV": float(facts["free_energy"]),
91
+ }
92
+
93
+
94
+ def _frame_summary(atoms: Any, frame_index: int) -> dict[str, Any]:
95
+ """Return one trajectory frame summary from saved nuclear and CP state."""
96
+ progress = _saved_step_time(atoms, frame_index)
97
+ cp = _saved_constant_potential(atoms)
98
+ summary = {
99
+ **progress,
100
+ "energy_eV": energy(atoms),
101
+ "temperature_K": _temperature(atoms),
102
+ "max_force_eV_per_A": max_force(atoms),
103
+ }
104
+ if cp is not None:
105
+ summary["constant_potential"] = cp
106
+ return summary
107
+
108
+
39
109
  def summarize_md_trajectory(
40
110
  traj_file: str | Path,
41
111
  *,
@@ -44,15 +114,7 @@ def summarize_md_trajectory(
44
114
  ) -> dict[str, Any]:
45
115
  """Read and summarize an MD trajectory."""
46
116
  frames = read(str(traj_file), index=":")
47
- frame_summaries = [
48
- {
49
- "step": index,
50
- "energy_eV": energy(atoms),
51
- "temperature_K": _temperature(atoms),
52
- "max_force_eV_per_A": max_force(atoms),
53
- }
54
- for index, atoms in enumerate(frames)
55
- ]
117
+ frame_summaries = [_frame_summary(atoms, index) for index, atoms in enumerate(frames)]
56
118
  selected = frame_summaries[-tail:] if tail and tail > 0 else frame_summaries
57
119
  latest = dict(frame_summaries[-1]) if frame_summaries else {}
58
120
  return {