atst-tools 2.3.2__tar.gz → 2.3.3__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.
- {atst_tools-2.3.2/src/atst_tools.egg-info → atst_tools-2.3.3}/PKG-INFO +10 -8
- {atst_tools-2.3.2 → atst_tools-2.3.3}/README.md +7 -7
- {atst_tools-2.3.2 → atst_tools-2.3.3}/pyproject.toml +3 -2
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/api/services.py +5 -1
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/abacuslite_backend.py +24 -3
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/constant_potential.py +38 -13
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/factory.py +152 -4
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/autoneb.py +36 -5
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/ccqn.py +69 -15
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/scripts/cli_impl.py +3 -4
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/abacus_io.py +14 -8
- atst_tools-2.3.3/src/atst_tools/utils/analysis.py +367 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/config_schema.py +97 -16
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/idpp.py +31 -2
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/io.py +10 -1
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/neb_endpoints.py +87 -34
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/reactive_modes.py +109 -1
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/reverse_config.py +4 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/constant_potential.py +2 -3
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/d2s.py +23 -5
- atst_tools-2.3.3/src/atst_tools/workflows/endpoint_relax.py +62 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/execution.py +75 -100
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/md.py +28 -1
- {atst_tools-2.3.2 → atst_tools-2.3.3/src/atst_tools.egg-info}/PKG-INFO +10 -8
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools.egg-info/SOURCES.txt +1 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools.egg-info/requires.txt +2 -0
- atst_tools-2.3.2/src/atst_tools/utils/analysis.py +0 -57
- {atst_tools-2.3.2 → atst_tools-2.3.3}/COPYING +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/LICENSE +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/setup.cfg +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/api/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/api/models.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/api/runner.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/api/validation.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/bench/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/bench/batch_runner.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/bench/batch_worker.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/bench/record.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/bench/sweep.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/backend_identity.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/base.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/calculators/dp.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/ABACUSLITE_SNAPSHOT.md +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/PATCHES.md +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/core.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/io/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/io/generalio.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/io/latestio.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/io/legacyio.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/utils/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/ASE_interface/abacuslite/utils/ksampling.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/LICENSE +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/UPSTREAM.md +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/dmf.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/fbenm.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/interpolate.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/torch/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/torch/_torch_config.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/torch/dmf.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/torch/fbenm.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/external/pydmf/dmf/torch/interpolate.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/dimer.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/neb.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/mep/sella.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/cli_dispatch.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/counters.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/devices.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/errors.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/evidence.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/runtime/launch.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/scripts/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/scripts/cli.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/scripts/main.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/artifacts.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/banner.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/config.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/config_docs.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/convergence.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/dmf_validation.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/electrochemistry.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/gate_compensation.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/geometry.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/md_post.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/mpi.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/plot.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/post.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/restart_helpers.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/sella_event_summary.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/sella_events.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/summary.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/thermochemistry.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/utils/ts_validation.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/__init__.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/cp_md.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/dmf.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/irc.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/relax.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools/workflows/vibration.py +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools.egg-info/dependency_links.txt +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools.egg-info/entry_points.txt +0 -0
- {atst_tools-2.3.2 → atst_tools-2.3.3}/src/atst_tools.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: atst-tools
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Advanced ASE Transition State Tools for ABACUS and Deep-Potential
|
|
5
5
|
License-Expression: LGPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -24,6 +24,7 @@ Requires-Dist: mpi4py>=4.1.2; extra == "parallel"
|
|
|
24
24
|
Provides-Extra: test
|
|
25
25
|
Requires-Dist: pytest<10,>=8.4; extra == "test"
|
|
26
26
|
Requires-Dist: tomli<3,>=2.0; extra == "test"
|
|
27
|
+
Requires-Dist: pyyaml<7,>=6; extra == "test"
|
|
27
28
|
Provides-Extra: release
|
|
28
29
|
Requires-Dist: build<2,>=1.5; extra == "release"
|
|
29
30
|
Requires-Dist: twine<7,>=6.2; extra == "release"
|
|
@@ -33,11 +34,12 @@ Requires-Dist: pytest<10,>=8.4; extra == "dev"
|
|
|
33
34
|
Requires-Dist: build<2,>=1.5; extra == "dev"
|
|
34
35
|
Requires-Dist: twine<7,>=6.2; extra == "dev"
|
|
35
36
|
Requires-Dist: tomli<3,>=2.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pyyaml<7,>=6; extra == "dev"
|
|
36
38
|
Dynamic: license-file
|
|
37
39
|
|
|
38
40
|
# ATST-Tools
|
|
39
41
|
|
|
40
|
-
[](pyproject.toml)
|
|
41
43
|
[](pyproject.toml)
|
|
42
44
|
[](#license)
|
|
43
45
|
|
|
@@ -67,11 +69,11 @@ Examples: [GitHub](https://github.com/QuantumMisaka/atst-tools/tree/main/example
|
|
|
67
69
|
| Intrinsic reaction coordinates | `irc` |
|
|
68
70
|
| ASE-driven or ABACUS-native molecular dynamics | `md` |
|
|
69
71
|
| Experimental Direct MaxFlux path search | `dmf` |
|
|
70
|
-
|
|
|
72
|
+
| ABACUS constant-potential single points and serial scans | `constant_potential` |
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
Constant-potential fixed-cell relaxation, serial NEB and ASE-driven MD
|
|
73
75
|
use the explicit `compensated_gate` boundary. See the
|
|
74
|
-
[configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation
|
|
76
|
+
[configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation)
|
|
75
77
|
for setup and supported boundaries.
|
|
76
78
|
|
|
77
79
|
Local commands prepare paths and ABACUS inputs, inspect trajectories, and
|
|
@@ -107,10 +109,10 @@ backend inputs.
|
|
|
107
109
|
|
|
108
110
|
## Quick Start
|
|
109
111
|
|
|
110
|
-
Obtain examples at the tag matching your installed version (shown for 2.3.
|
|
112
|
+
Obtain examples at the tag matching your installed version (shown for 2.3.3):
|
|
111
113
|
|
|
112
114
|
```bash
|
|
113
|
-
git clone --branch v2.3.
|
|
115
|
+
git clone --branch v2.3.3 https://github.com/QuantumMisaka/atst-tools.git
|
|
114
116
|
cd atst-tools
|
|
115
117
|
```
|
|
116
118
|
|
|
@@ -149,7 +151,7 @@ methods or DP variants. A complete walkthrough is also available in the
|
|
|
149
151
|
| Use CLI commands | [CLI reference](docs/user/CLI_REFERENCE.md) |
|
|
150
152
|
| Configure ABACUS input/output and parallelism | [ABACUS wrapper guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md) |
|
|
151
153
|
| 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.
|
|
154
|
+
| Check support and releases | [Feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md) · [2.3.3 release notes](docs/releases/RELEASE_NOTES_2.3.3.md) |
|
|
153
155
|
| Contribute | [Developer handover](docs/developer/HANDOVER.md) · [Source architecture](src/atst_tools/README.md) |
|
|
154
156
|
|
|
155
157
|
The primary documentation is in English, with a [Chinese user guide](docs/user/USER_GUIDE_CN.md).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ATST-Tools
|
|
2
2
|
|
|
3
|
-
[](pyproject.toml)
|
|
4
4
|
[](pyproject.toml)
|
|
5
5
|
[](#license)
|
|
6
6
|
|
|
@@ -30,11 +30,11 @@ Examples: [GitHub](https://github.com/QuantumMisaka/atst-tools/tree/main/example
|
|
|
30
30
|
| Intrinsic reaction coordinates | `irc` |
|
|
31
31
|
| ASE-driven or ABACUS-native molecular dynamics | `md` |
|
|
32
32
|
| Experimental Direct MaxFlux path search | `dmf` |
|
|
33
|
-
|
|
|
33
|
+
| ABACUS constant-potential single points and serial scans | `constant_potential` |
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Constant-potential fixed-cell relaxation, serial NEB and ASE-driven MD
|
|
36
36
|
use the explicit `compensated_gate` boundary. See the
|
|
37
|
-
[configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation
|
|
37
|
+
[configuration reference](docs/user/CONFIG_REFERENCE.md#211-constant-potential-evaluation)
|
|
38
38
|
for setup and supported boundaries.
|
|
39
39
|
|
|
40
40
|
Local commands prepare paths and ABACUS inputs, inspect trajectories, and
|
|
@@ -70,10 +70,10 @@ backend inputs.
|
|
|
70
70
|
|
|
71
71
|
## Quick Start
|
|
72
72
|
|
|
73
|
-
Obtain examples at the tag matching your installed version (shown for 2.3.
|
|
73
|
+
Obtain examples at the tag matching your installed version (shown for 2.3.3):
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
git clone --branch v2.3.
|
|
76
|
+
git clone --branch v2.3.3 https://github.com/QuantumMisaka/atst-tools.git
|
|
77
77
|
cd atst-tools
|
|
78
78
|
```
|
|
79
79
|
|
|
@@ -112,7 +112,7 @@ methods or DP variants. A complete walkthrough is also available in the
|
|
|
112
112
|
| Use CLI commands | [CLI reference](docs/user/CLI_REFERENCE.md) |
|
|
113
113
|
| Configure ABACUS input/output and parallelism | [ABACUS wrapper guide](docs/user/ABACUSLITE_WRAPPER_GUIDE.md) |
|
|
114
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.
|
|
115
|
+
| Check support and releases | [Feature matrix](docs/reports/FEATURE_STATUS_MATRIX.md) · [2.3.3 release notes](docs/releases/RELEASE_NOTES_2.3.3.md) |
|
|
116
116
|
| Contribute | [Developer handover](docs/developer/HANDOVER.md) · [Source architecture](src/atst_tools/README.md) |
|
|
117
117
|
|
|
118
118
|
The primary documentation is in English, with a [Chinese user guide](docs/user/USER_GUIDE_CN.md).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "atst-tools"
|
|
7
|
-
version = "2.3.
|
|
7
|
+
version = "2.3.3"
|
|
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"
|
|
@@ -24,13 +24,14 @@ dependencies = [
|
|
|
24
24
|
plot = ["matplotlib>=3.9,<4"]
|
|
25
25
|
dp = ["deepmd-kit>=3.1.3"]
|
|
26
26
|
parallel = ["mpi4py>=4.1.2"]
|
|
27
|
-
test = ["pytest>=8.4,<10", "tomli>=2.0,<3"]
|
|
27
|
+
test = ["pytest>=8.4,<10", "tomli>=2.0,<3", "pyyaml>=6,<7"]
|
|
28
28
|
release = ["build>=1.5,<2", "twine>=6.2,<7", "tomli>=2.0,<3"]
|
|
29
29
|
dev = [
|
|
30
30
|
"pytest>=8.4,<10",
|
|
31
31
|
"build>=1.5,<2",
|
|
32
32
|
"twine>=6.2,<7",
|
|
33
33
|
"tomli>=2.0,<3",
|
|
34
|
+
"pyyaml>=6,<7",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.scripts]
|
|
@@ -24,6 +24,7 @@ from atst_tools.api.models import (
|
|
|
24
24
|
WorkflowResult,
|
|
25
25
|
)
|
|
26
26
|
from atst_tools.calculators.abacuslite_backend import BACKEND_SOURCE
|
|
27
|
+
from atst_tools.calculators.factory import socket_session
|
|
27
28
|
from atst_tools.utils.artifacts import (
|
|
28
29
|
SCHEMA_VERSION,
|
|
29
30
|
add_backend_identity_to_manifest,
|
|
@@ -915,7 +916,10 @@ def _run_workflow(
|
|
|
915
916
|
failure = None
|
|
916
917
|
dispatch_started = time.monotonic()
|
|
917
918
|
try:
|
|
918
|
-
|
|
919
|
+
# Persistent ABACUS socket clients built by this workflow must not
|
|
920
|
+
# outlive it; clients owned by other callers are left untouched.
|
|
921
|
+
with socket_session():
|
|
922
|
+
value = _dispatch_normalized(config, options)
|
|
919
923
|
except ATSTAPIError as exc:
|
|
920
924
|
failure = exc
|
|
921
925
|
except Exception as exc:
|
|
@@ -20,12 +20,32 @@ def _load_abacuslite_backend():
|
|
|
20
20
|
"abacuslite is required by ATST-Tools; install abacuslite>=1.2.1,<2",
|
|
21
21
|
name="abacuslite",
|
|
22
22
|
)
|
|
23
|
-
from abacuslite import Abacus, AbacusProfile
|
|
23
|
+
from abacuslite import Abacus, AbacusProfile, AbacusSocketIO
|
|
24
24
|
|
|
25
|
-
return Abacus, AbacusProfile, "external"
|
|
25
|
+
return Abacus, AbacusProfile, AbacusSocketIO, "external"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
Abacus, AbacusProfile, BACKEND_SOURCE = _load_abacuslite_backend()
|
|
28
|
+
Abacus, AbacusProfile, AbacusSocketIO, BACKEND_SOURCE = _load_abacuslite_backend()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def socket_input_parameters(parameters: dict) -> dict:
|
|
32
|
+
"""Apply the backend's socket-mode INPUT rules to ABACUS parameters.
|
|
33
|
+
|
|
34
|
+
Delegates to ``AbacusSocketIO._socket_inp`` so prepared INPUT files match
|
|
35
|
+
what the socket calculator writes: ``socket_driver = 1``,
|
|
36
|
+
``calculation = scf`` and normalized ``cal_force``/``cal_stress``.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
parameters: ABACUS INPUT parameters.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A new parameter mapping with socket-mode values applied.
|
|
43
|
+
|
|
44
|
+
Raises:
|
|
45
|
+
ValueError: If ``calculation`` is not ``scf`` or a ``cal_*`` flag is
|
|
46
|
+
not a boolean value.
|
|
47
|
+
"""
|
|
48
|
+
return AbacusSocketIO._socket_inp(parameters)
|
|
29
49
|
|
|
30
50
|
|
|
31
51
|
def import_generalio():
|
|
@@ -131,6 +151,7 @@ class ATSTAbacusProfile(AbacusProfile):
|
|
|
131
151
|
__all__ = [
|
|
132
152
|
"Abacus",
|
|
133
153
|
"AbacusProfile",
|
|
154
|
+
"AbacusSocketIO",
|
|
134
155
|
"ATSTAbacusProfile",
|
|
135
156
|
"BACKEND_SOURCE",
|
|
136
157
|
"BackendSource",
|
|
@@ -20,6 +20,7 @@ from typing import Any
|
|
|
20
20
|
import numpy as np
|
|
21
21
|
from ase.calculators.calculator import Calculator, all_changes
|
|
22
22
|
|
|
23
|
+
from atst_tools.utils.config_schema import ABACUS_CONTROL_KEYS
|
|
23
24
|
from atst_tools.utils.electrochemistry import chebyshev_derivative, surface_area
|
|
24
25
|
|
|
25
26
|
|
|
@@ -61,7 +62,7 @@ _CURRENT_SCF_FAILURE_MARKER = re.compile(
|
|
|
61
62
|
CP_FACTS_INFO_KEY = "atst_constant_potential_facts"
|
|
62
63
|
CP_IDENTITY_INFO_KEY = "atst_constant_potential_identity"
|
|
63
64
|
CP_FACTS_SCHEMA = "atst-constant-potential-facts-v1"
|
|
64
|
-
_ABACUS_CONTROL_KEYS =
|
|
65
|
+
_ABACUS_CONTROL_KEYS = ABACUS_CONTROL_KEYS
|
|
65
66
|
_FIXED_BOUNDARY_KEYS = (
|
|
66
67
|
"gate_flag",
|
|
67
68
|
"imp_sol",
|
|
@@ -359,7 +360,8 @@ def _same_finite_float(left: Any, right: Any) -> bool:
|
|
|
359
360
|
|
|
360
361
|
|
|
361
362
|
def _constant_potential_facts_error(
|
|
362
|
-
atoms: Any, payload: Any, *, require_duplicate_identity: bool = True
|
|
363
|
+
atoms: Any, payload: Any, *, require_duplicate_identity: bool = True,
|
|
364
|
+
check_geometry: bool = True,
|
|
363
365
|
) -> str | None:
|
|
364
366
|
"""Return a fail-closed validation error for one durable CP envelope."""
|
|
365
367
|
if atoms is None or not hasattr(atoms, "info"):
|
|
@@ -370,12 +372,13 @@ def _constant_potential_facts_error(
|
|
|
370
372
|
return "constant-potential facts schema or workflow is unsupported"
|
|
371
373
|
if payload.get("status") != "complete" or payload.get("cp_converged") is not True:
|
|
372
374
|
return "constant-potential facts are not a complete converged result"
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
if check_geometry:
|
|
376
|
+
try:
|
|
377
|
+
geometry_fingerprint = constant_potential_geometry_fingerprint(atoms)
|
|
378
|
+
except (AttributeError, TypeError, ValueError):
|
|
379
|
+
return "constant-potential facts geometry cannot be fingerprinted"
|
|
380
|
+
if payload.get("geometry_fingerprint") != geometry_fingerprint:
|
|
381
|
+
return "constant-potential facts geometry fingerprint does not match"
|
|
379
382
|
|
|
380
383
|
identity = payload.get("identity")
|
|
381
384
|
facts = payload.get("facts")
|
|
@@ -612,12 +615,14 @@ def publish_constant_potential_facts(
|
|
|
612
615
|
return copy.deepcopy(payload)
|
|
613
616
|
|
|
614
617
|
|
|
615
|
-
def read_constant_potential_facts(
|
|
616
|
-
|
|
618
|
+
def read_constant_potential_facts(
|
|
619
|
+
atoms: Any, *, check_geometry: bool = True
|
|
620
|
+
) -> dict[str, Any] | None:
|
|
621
|
+
"""Read valid CP facts; restart checks geometry while NEB may use ASE state."""
|
|
617
622
|
if atoms is None or not hasattr(atoms, "info"):
|
|
618
623
|
return None
|
|
619
624
|
payload = atoms.info.get(CP_FACTS_INFO_KEY)
|
|
620
|
-
if _constant_potential_facts_error(atoms, payload) is not None:
|
|
625
|
+
if _constant_potential_facts_error(atoms, payload, check_geometry=check_geometry) is not None:
|
|
621
626
|
return None
|
|
622
627
|
return copy.deepcopy(dict(payload))
|
|
623
628
|
|
|
@@ -751,7 +756,14 @@ def _read_vacuum_from_logs(directory: str | Path, suffix: str | None = None) ->
|
|
|
751
756
|
|
|
752
757
|
|
|
753
758
|
def _backend_log_paths(calculator: Any) -> tuple[Path, ...]:
|
|
754
|
-
"""Return only the current backend's SCF log candidates.
|
|
759
|
+
"""Return only the current backend's SCF log candidates.
|
|
760
|
+
|
|
761
|
+
An abacuslite ``AbacusSocketIO`` keeps its ABACUS working directory and
|
|
762
|
+
INPUT parameters on the wrapped FileIO calculator (``calculator.abacus``);
|
|
763
|
+
its own ``directory`` is ASE's default ``'.'``, which is not where ABACUS
|
|
764
|
+
writes.
|
|
765
|
+
"""
|
|
766
|
+
calculator = getattr(calculator, "abacus", calculator)
|
|
755
767
|
directory = getattr(calculator, "directory", None)
|
|
756
768
|
if directory is None:
|
|
757
769
|
return ()
|
|
@@ -765,7 +777,15 @@ def _backend_log_paths(calculator: Any) -> tuple[Path, ...]:
|
|
|
765
777
|
|
|
766
778
|
|
|
767
779
|
def _backend_scf_converged(calculator: Any, results: Mapping[str, Any]) -> bool | None:
|
|
768
|
-
"""Read an explicit SCF-validity fact from results or
|
|
780
|
+
"""Read an explicit SCF-validity fact from results, the backend, or its log.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
calculator: Backend calculator that produced ``results``.
|
|
784
|
+
results: Result mapping of the evaluation being validated.
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
``True``/``False`` for an explicit fact, ``None`` when no evidence exists.
|
|
788
|
+
"""
|
|
769
789
|
for key in ("scf_converged", "scf_convergence", "scf_status", "convergence"):
|
|
770
790
|
if key not in results or results[key] is None:
|
|
771
791
|
continue
|
|
@@ -782,6 +802,11 @@ def _backend_scf_converged(calculator: Any, results: Mapping[str, Any]) -> bool
|
|
|
782
802
|
return bool(int(value))
|
|
783
803
|
except (TypeError, ValueError):
|
|
784
804
|
return None
|
|
805
|
+
# abacuslite's socket client reports the per-evaluation SCF state from the
|
|
806
|
+
# ABACUS i-PI metadata as an attribute; it is reset before every request.
|
|
807
|
+
socket_converged = getattr(calculator, "last_scf_converged", None)
|
|
808
|
+
if isinstance(socket_converged, (bool, np.bool_)):
|
|
809
|
+
return bool(socket_converged)
|
|
785
810
|
for path in _backend_log_paths(calculator):
|
|
786
811
|
if not path.is_file():
|
|
787
812
|
continue
|
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import itertools
|
|
5
6
|
import os
|
|
6
7
|
import re
|
|
7
8
|
import shlex
|
|
8
9
|
import logging
|
|
9
10
|
import math
|
|
11
|
+
from contextlib import contextmanager
|
|
12
|
+
from contextvars import ContextVar
|
|
10
13
|
from copy import deepcopy
|
|
11
|
-
from typing import Any, Dict
|
|
14
|
+
from typing import Any, Dict, Iterator
|
|
12
15
|
|
|
13
16
|
from ase.calculators.calculator import Calculator
|
|
14
17
|
|
|
15
18
|
from atst_tools.calculators.abacuslite_backend import (
|
|
16
|
-
Abacus, ATSTAbacusProfile, BACKEND_SOURCE, _default_version_command,
|
|
19
|
+
Abacus, AbacusSocketIO, ATSTAbacusProfile, BACKEND_SOURCE, _default_version_command,
|
|
17
20
|
)
|
|
18
21
|
from atst_tools.calculators.constant_potential import (
|
|
19
22
|
ConstantPotentialCalculator,
|
|
@@ -23,13 +26,18 @@ from atst_tools.calculators.dp import DeepPotentialFactory
|
|
|
23
26
|
from atst_tools.runtime import counters as runtime_counters
|
|
24
27
|
from atst_tools.runtime import launch as runtime_launch
|
|
25
28
|
from atst_tools.utils.config_schema import (
|
|
29
|
+
ABACUS_CONTROL_KEYS,
|
|
30
|
+
SOCKET_FIXED_CELL_MD_ENSEMBLES,
|
|
31
|
+
SOCKET_SUPPORTED_WORKFLOWS,
|
|
26
32
|
ConstantPotentialConfig,
|
|
27
33
|
validate_constant_potential_md_scope,
|
|
28
34
|
)
|
|
29
35
|
from atst_tools.utils.mpi import mpi_launcher_detected
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
_ABACUS_CONTROL_KEYS =
|
|
38
|
+
_ABACUS_CONTROL_KEYS = ABACUS_CONTROL_KEYS
|
|
39
|
+
# INPUT keys that AbacusSocketIO validates only when passed through ``inp``.
|
|
40
|
+
_SOCKET_INP_KEYS = ("calculation", "cal_force", "cal_stress")
|
|
33
41
|
_MPI_ENV_KEYS_TO_CLEAR = (
|
|
34
42
|
"OMPI_COMM_WORLD_SIZE",
|
|
35
43
|
"OMPI_COMM_WORLD_RANK",
|
|
@@ -46,6 +54,9 @@ LOGGER = logging.getLogger(__name__)
|
|
|
46
54
|
_ABACUS_BACKEND_LOGGED = False
|
|
47
55
|
_SHELL_ASSIGNMENT_RE = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=.*$")
|
|
48
56
|
_MPI_LAUNCHERS = {"mpirun", "mpiexec", "srun"}
|
|
57
|
+
_SOCKET_NAME_COUNTER = itertools.count()
|
|
58
|
+
#: Socket clients owned by the innermost active :func:`socket_session`.
|
|
59
|
+
_SOCKET_SESSION: ContextVar[list[Any] | None] = ContextVar("atst_abacus_socket_session", default=None)
|
|
49
60
|
|
|
50
61
|
|
|
51
62
|
def _abacus_section(config: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -133,6 +144,96 @@ def _validate_abacus_command(command: str) -> None:
|
|
|
133
144
|
)
|
|
134
145
|
|
|
135
146
|
|
|
147
|
+
def _socket_options(raw: Any) -> Dict[str, Any] | None:
|
|
148
|
+
"""Normalize ``calculator.abacus.socket`` into AbacusSocketIO keywords.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
raw: ``None``/``False`` (disabled), ``True`` (enabled with defaults), or
|
|
152
|
+
a mapping with optional ``enabled``, ``unixsocket``, ``port`` and
|
|
153
|
+
``timeout`` keys.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Keyword arguments for ``AbacusSocketIO`` or ``None`` when disabled.
|
|
157
|
+
|
|
158
|
+
Raises:
|
|
159
|
+
ValueError: If the value has an unsupported shape or both ``port`` and
|
|
160
|
+
``unixsocket`` are given.
|
|
161
|
+
"""
|
|
162
|
+
if raw is None or raw is False:
|
|
163
|
+
return None
|
|
164
|
+
if raw is True:
|
|
165
|
+
raw = {}
|
|
166
|
+
if not isinstance(raw, dict):
|
|
167
|
+
raise ValueError("calculator.abacus.socket must be a boolean or a mapping")
|
|
168
|
+
options = dict(raw)
|
|
169
|
+
if not options.pop("enabled", True):
|
|
170
|
+
return None
|
|
171
|
+
unknown = set(options) - {"unixsocket", "port", "timeout"}
|
|
172
|
+
if unknown:
|
|
173
|
+
raise ValueError(f"Unknown calculator.abacus.socket keys: {sorted(unknown)}")
|
|
174
|
+
if options.get("port") is not None and options.get("unixsocket") is not None:
|
|
175
|
+
raise ValueError("calculator.abacus.socket accepts either port or unixsocket, not both")
|
|
176
|
+
if options.get("port") is None and options.get("unixsocket") is None:
|
|
177
|
+
# A per-process, per-calculator name keeps concurrent runs on one node
|
|
178
|
+
# from colliding on /tmp/ipi_<name>.
|
|
179
|
+
options["unixsocket"] = f"atst_{os.getpid()}_{next(_SOCKET_NAME_COUNTER)}"
|
|
180
|
+
options.setdefault("timeout", None)
|
|
181
|
+
return options
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _check_socket_workflow(config: Dict[str, Any]) -> None:
|
|
185
|
+
"""Reject socket mode for workflows that are not single fixed-cell runs."""
|
|
186
|
+
calculation = config.get("calculation")
|
|
187
|
+
if not isinstance(calculation, dict):
|
|
188
|
+
return
|
|
189
|
+
workflow = calculation.get("type")
|
|
190
|
+
if workflow is not None and workflow not in SOCKET_SUPPORTED_WORKFLOWS:
|
|
191
|
+
raise ValueError(
|
|
192
|
+
"calculator.abacus.socket is supported only for calculation.type in "
|
|
193
|
+
f"{list(SOCKET_SUPPORTED_WORKFLOWS)}; got {workflow!r}"
|
|
194
|
+
)
|
|
195
|
+
if workflow == "md":
|
|
196
|
+
if calculation.get("driver", "ase") != "ase":
|
|
197
|
+
raise ValueError("calculator.abacus.socket requires calculation.driver=ase for md")
|
|
198
|
+
ensemble = calculation.get("ensemble", "nvt")
|
|
199
|
+
if ensemble not in SOCKET_FIXED_CELL_MD_ENSEMBLES:
|
|
200
|
+
raise ValueError(
|
|
201
|
+
"calculator.abacus.socket requires a fixed-cell md ensemble "
|
|
202
|
+
f"{list(SOCKET_FIXED_CELL_MD_ENSEMBLES)}; got {ensemble!r}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _close_socket_calculator(calculator: Any) -> None:
|
|
207
|
+
"""Close one socket client; failures are logged, never raised."""
|
|
208
|
+
try:
|
|
209
|
+
calculator.close()
|
|
210
|
+
except Exception as exc: # pragma: no cover - best-effort cleanup
|
|
211
|
+
LOGGER.warning("Failed to close ABACUS socket calculator: %s", exc)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@contextmanager
|
|
215
|
+
def socket_session() -> Iterator[list[Any]]:
|
|
216
|
+
"""Own the ABACUS socket clients built while this context is active.
|
|
217
|
+
|
|
218
|
+
Clients created by :class:`AbacusFactory` inside the context are closed
|
|
219
|
+
when it exits, including on error. Clients created outside any session
|
|
220
|
+
belong to the caller, who closes them. Sessions are context-local (thread
|
|
221
|
+
and asyncio safe) and may nest; each session closes only the clients it
|
|
222
|
+
created.
|
|
223
|
+
|
|
224
|
+
Yields:
|
|
225
|
+
The list of socket clients owned by this session.
|
|
226
|
+
"""
|
|
227
|
+
owned: list[Any] = []
|
|
228
|
+
token = _SOCKET_SESSION.set(owned)
|
|
229
|
+
try:
|
|
230
|
+
yield owned
|
|
231
|
+
finally:
|
|
232
|
+
_SOCKET_SESSION.reset(token)
|
|
233
|
+
while owned:
|
|
234
|
+
_close_socket_calculator(owned.pop())
|
|
235
|
+
|
|
236
|
+
|
|
136
237
|
def _resolve_directory(path: str | None) -> str | None:
|
|
137
238
|
if path is None:
|
|
138
239
|
return None
|
|
@@ -197,7 +298,13 @@ class AbacusFactory:
|
|
|
197
298
|
omp_num_threads=omp,
|
|
198
299
|
version_command=version_command,
|
|
199
300
|
)
|
|
200
|
-
|
|
301
|
+
socket = _socket_options(abacus_config.get("socket"))
|
|
302
|
+
if socket is None:
|
|
303
|
+
calculator = Abacus(directory=directory, profile=profile, **parameters)
|
|
304
|
+
else:
|
|
305
|
+
calculator = AbacusFactory._socket_calculator(
|
|
306
|
+
config, directory, profile, parameters, socket
|
|
307
|
+
)
|
|
201
308
|
return runtime_counters.instrument_calculator(
|
|
202
309
|
calculator,
|
|
203
310
|
build_key="abacus.calculator_built",
|
|
@@ -205,6 +312,42 @@ class AbacusFactory:
|
|
|
205
312
|
)
|
|
206
313
|
|
|
207
314
|
|
|
315
|
+
@staticmethod
|
|
316
|
+
def _socket_calculator(
|
|
317
|
+
config: Dict[str, Any],
|
|
318
|
+
directory: str,
|
|
319
|
+
profile: ATSTAbacusProfile,
|
|
320
|
+
parameters: Dict[str, Any],
|
|
321
|
+
socket: Dict[str, Any],
|
|
322
|
+
) -> Calculator:
|
|
323
|
+
"""Build an ``AbacusSocketIO`` calculator owned by the active socket session.
|
|
324
|
+
|
|
325
|
+
Endpoint conflicts are left to the backend: binding an occupied Unix
|
|
326
|
+
socket or TCP port fails when the first calculation starts the server.
|
|
327
|
+
"""
|
|
328
|
+
_check_socket_workflow(config)
|
|
329
|
+
endpoint = (
|
|
330
|
+
f"unix:{socket['unixsocket']}" if socket.get("unixsocket") is not None else f"port:{socket['port']}"
|
|
331
|
+
)
|
|
332
|
+
parameters = dict(parameters)
|
|
333
|
+
inp = dict(parameters.pop("inp", {}) or {})
|
|
334
|
+
for key in _SOCKET_INP_KEYS:
|
|
335
|
+
if key in parameters:
|
|
336
|
+
inp[key] = parameters.pop(key)
|
|
337
|
+
calculator = AbacusSocketIO(
|
|
338
|
+
profile=profile,
|
|
339
|
+
directory=directory,
|
|
340
|
+
inp=inp,
|
|
341
|
+
**socket,
|
|
342
|
+
**parameters,
|
|
343
|
+
)
|
|
344
|
+
session = _SOCKET_SESSION.get()
|
|
345
|
+
if session is not None:
|
|
346
|
+
session.append(calculator)
|
|
347
|
+
LOGGER.info("Using ABACUS i-PI socket client (%s).", endpoint)
|
|
348
|
+
return calculator
|
|
349
|
+
|
|
350
|
+
|
|
208
351
|
class CalculatorFactory:
|
|
209
352
|
"""Unified factory for calculator construction."""
|
|
210
353
|
|
|
@@ -218,6 +361,11 @@ class CalculatorFactory:
|
|
|
218
361
|
else None
|
|
219
362
|
)
|
|
220
363
|
if constant_potential is not None:
|
|
364
|
+
if _socket_options(_abacus_section(config).get("socket")) is not None:
|
|
365
|
+
raise ValueError(
|
|
366
|
+
"calculator.abacus.socket cannot be combined with "
|
|
367
|
+
"calculator.constant_potential"
|
|
368
|
+
)
|
|
221
369
|
return _constant_potential_calculator(config, constant_potential, kwargs)
|
|
222
370
|
return AbacusFactory.get_calculator(config, **kwargs)
|
|
223
371
|
if name in {"dp", "deepmd"}:
|
|
@@ -22,6 +22,7 @@ from ase.optimize import FIRE, BFGS
|
|
|
22
22
|
|
|
23
23
|
from atst_tools.mep.neb import AbacusNEB
|
|
24
24
|
from atst_tools.calculators.factory import CalculatorFactory
|
|
25
|
+
from atst_tools.calculators.constant_potential import read_constant_potential_facts
|
|
25
26
|
from atst_tools.calculators.dp import is_dp_calculator, should_share_calculator
|
|
26
27
|
from atst_tools.utils.artifacts import write_artifact_manifest
|
|
27
28
|
from atst_tools.utils.geometry import prepare_fixed_cell_images, fixed_cell_metadata
|
|
@@ -32,8 +33,13 @@ from atst_tools.utils.convergence import (
|
|
|
32
33
|
as_step_count,
|
|
33
34
|
emit_unconverged_advisory,
|
|
34
35
|
)
|
|
35
|
-
from atst_tools.utils.neb_endpoints import
|
|
36
|
+
from atst_tools.utils.neb_endpoints import (
|
|
37
|
+
endpoint_policy,
|
|
38
|
+
ensure_neb_endpoint_results,
|
|
39
|
+
expected_cp_endpoint_identity,
|
|
40
|
+
)
|
|
36
41
|
from atst_tools.utils.neb_endpoints import freeze_current_results, freeze_results, get_endpoint_results
|
|
42
|
+
from atst_tools.workflows.endpoint_relax import relax_neb_endpoints
|
|
37
43
|
from atst_tools.utils.mpi import (
|
|
38
44
|
get_ase_world,
|
|
39
45
|
rank_owns_local_image,
|
|
@@ -813,6 +819,7 @@ class AutoNEBRunner:
|
|
|
813
819
|
raise initial_chain_error
|
|
814
820
|
self._image_index_by_id = {id(image): index for index, image in enumerate(self.init_chain)}
|
|
815
821
|
self._active_autoneb = None
|
|
822
|
+
self._endpoint_stages = []
|
|
816
823
|
self._apply_legacy_endpoint_conditions()
|
|
817
824
|
|
|
818
825
|
def _apply_legacy_endpoint_conditions(self):
|
|
@@ -827,6 +834,7 @@ class AutoNEBRunner:
|
|
|
827
834
|
results[0],
|
|
828
835
|
results[1],
|
|
829
836
|
self.init_chain[index].info.get("atst_endpoint_result"),
|
|
837
|
+
read_constant_potential_facts(self.init_chain[index], check_geometry=False),
|
|
830
838
|
)
|
|
831
839
|
reference = self.init_chain[len(self.init_chain) // 2]
|
|
832
840
|
magmoms = reference.get_initial_magnetic_moments()
|
|
@@ -835,8 +843,8 @@ class AutoNEBRunner:
|
|
|
835
843
|
endpoint.set_initial_magnetic_moments(magmoms)
|
|
836
844
|
if constraints:
|
|
837
845
|
endpoint.set_constraint(deepcopy(constraints))
|
|
838
|
-
for index, (energy, forces, status) in endpoint_results.items():
|
|
839
|
-
freeze_results(self.init_chain[index], energy, forces, status=status)
|
|
846
|
+
for index, (energy, forces, status, cp_facts) in endpoint_results.items():
|
|
847
|
+
freeze_results(self.init_chain[index], energy, forces, status=status, cp_facts=cp_facts)
|
|
840
848
|
|
|
841
849
|
def _base_directory(self):
|
|
842
850
|
base_dir = self.calc_config['directory']
|
|
@@ -910,17 +918,39 @@ class AutoNEBRunner:
|
|
|
910
918
|
self.attach_calculators(images, synchronize=False)
|
|
911
919
|
|
|
912
920
|
def _prepare_endpoint_results(self):
|
|
913
|
-
"""
|
|
921
|
+
"""Relax missing-result endpoints, then synchronize their results."""
|
|
914
922
|
base_dir = self._base_directory()
|
|
915
923
|
policy = endpoint_policy(self.calc_config, default="auto")
|
|
924
|
+
cp_identity = expected_cp_endpoint_identity(self.config)
|
|
925
|
+
endpoint_config = self.calc_config["endpoint_optimization"]
|
|
926
|
+
self._endpoint_stages = []
|
|
916
927
|
|
|
917
928
|
def prepare():
|
|
929
|
+
if endpoint_config["enabled"]:
|
|
930
|
+
if policy == "never":
|
|
931
|
+
ensure_neb_endpoint_results(
|
|
932
|
+
self.init_chain,
|
|
933
|
+
lambda directory: None,
|
|
934
|
+
policy="never",
|
|
935
|
+
context="AutoNEB",
|
|
936
|
+
constant_potential_identity=cp_identity,
|
|
937
|
+
)
|
|
938
|
+
self._endpoint_stages = relax_neb_endpoints(
|
|
939
|
+
self.init_chain,
|
|
940
|
+
endpoint_config,
|
|
941
|
+
lambda label, atoms: self._get_calculator(
|
|
942
|
+
f"{base_dir}/endpoint_{label}_relax"
|
|
943
|
+
),
|
|
944
|
+
self._get_optimizer(),
|
|
945
|
+
constant_potential_identity=cp_identity,
|
|
946
|
+
)
|
|
918
947
|
ensure_neb_endpoint_results(
|
|
919
948
|
self.init_chain,
|
|
920
949
|
lambda directory: self._get_calculator(f"{base_dir}/{directory}"),
|
|
921
950
|
policy=policy,
|
|
922
951
|
directories=("endpoint_initial", "endpoint_final"),
|
|
923
952
|
context="AutoNEB",
|
|
953
|
+
constant_potential_identity=cp_identity,
|
|
924
954
|
)
|
|
925
955
|
|
|
926
956
|
if not self.parallel:
|
|
@@ -1167,7 +1197,8 @@ class AutoNEBRunner:
|
|
|
1167
1197
|
workflow="autoneb",
|
|
1168
1198
|
world=self.world,
|
|
1169
1199
|
)
|
|
1170
|
-
stages = [record.to_manifest() for record in
|
|
1200
|
+
stages = [record.to_manifest() for record in self._endpoint_stages]
|
|
1201
|
+
stages.extend(record.to_manifest() for record in iteration_records)
|
|
1171
1202
|
if final_record is not None:
|
|
1172
1203
|
stages.append(final_record.to_manifest())
|
|
1173
1204
|
|