assystant 0.1.0__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 (43) hide show
  1. assystant-0.1.0/.github/workflows/clean-install.yml +143 -0
  2. assystant-0.1.0/.github/workflows/release.yml +104 -0
  3. assystant-0.1.0/.gitignore +9 -0
  4. assystant-0.1.0/CHANGELOG.md +23 -0
  5. assystant-0.1.0/LICENSE +29 -0
  6. assystant-0.1.0/PKG-INFO +171 -0
  7. assystant-0.1.0/README.md +141 -0
  8. assystant-0.1.0/assystant/__init__.py +38 -0
  9. assystant-0.1.0/assystant/cli.py +194 -0
  10. assystant-0.1.0/assystant/collect.py +182 -0
  11. assystant-0.1.0/assystant/config.py +608 -0
  12. assystant-0.1.0/assystant/data/EleBulk_MatProj_VorVol.csv +88 -0
  13. assystant-0.1.0/assystant/generate.py +378 -0
  14. assystant-0.1.0/assystant/incar.py +76 -0
  15. assystant-0.1.0/assystant/runner.py +105 -0
  16. assystant-0.1.0/assystant/status.py +125 -0
  17. assystant-0.1.0/assystant/submit.py +301 -0
  18. assystant-0.1.0/docs/2026_07_31_assystant_design.md +377 -0
  19. assystant-0.1.0/docs/2026_07_31_pyiron_workflow_vasp_port_log.md +197 -0
  20. assystant-0.1.0/docs/2026_08_01_pyiron_workflow_assyst_port_log.md +208 -0
  21. assystant-0.1.0/docs/behaviour-changes.md +187 -0
  22. assystant-0.1.0/docs/commands.md +268 -0
  23. assystant-0.1.0/docs/configuration.md +422 -0
  24. assystant-0.1.0/docs/implementation-report.txt +136 -0
  25. assystant-0.1.0/docs/installation.md +209 -0
  26. assystant-0.1.0/docs/operations.md +325 -0
  27. assystant-0.1.0/docs/overview.md +108 -0
  28. assystant-0.1.0/docs/plans/2026-07-31-pyiron-workflow-vasp-0.19-port.md +1557 -0
  29. assystant-0.1.0/docs/plans/2026-08-01-pyiron-workflow-assyst-0.19-port.md +1299 -0
  30. assystant-0.1.0/docs/troubleshooting.md +304 -0
  31. assystant-0.1.0/docs/worked-example.md +265 -0
  32. assystant-0.1.0/examples/Al_tutorial.yaml +53 -0
  33. assystant-0.1.0/examples/Fe_smoke.yaml +54 -0
  34. assystant-0.1.0/examples/Fe_unary.yaml +89 -0
  35. assystant-0.1.0/pyproject.toml +51 -0
  36. assystant-0.1.0/tests/conftest.py +72 -0
  37. assystant-0.1.0/tests/test_collect.py +171 -0
  38. assystant-0.1.0/tests/test_config.py +227 -0
  39. assystant-0.1.0/tests/test_generate.py +234 -0
  40. assystant-0.1.0/tests/test_incar.py +70 -0
  41. assystant-0.1.0/tests/test_runner.py +82 -0
  42. assystant-0.1.0/tests/test_status.py +104 -0
  43. assystant-0.1.0/tests/test_submit.py +402 -0
@@ -0,0 +1,143 @@
1
+ # Proves a fresh environment can `pip install assystant` with no
2
+ # --no-build-isolation and no pre-installed scientific stack: build sdist and
3
+ # wheel in isolation, install each into an empty venv from outside the
4
+ # checkout, then run the CLI and the test suite against the installed copy.
5
+ #
6
+ # Upstream requirements that are not released yet are built as release
7
+ # candidates by the `upstream` job (see there).
8
+
9
+ name: clean-install
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ pull_request:
15
+
16
+ jobs:
17
+ # Release candidates for upstream packages that are not on PyPI yet.
18
+ # For each requirement below, if PyPI already satisfies it nothing is built
19
+ # and the install jobs test against the published release. Otherwise a wheel
20
+ # is built from the upstream pull request that prepares that release (the
21
+ # same way its release workflow will build it) and handed to the install
22
+ # jobs via --find-links. Once the releases are out this job is a no-op and
23
+ # the CANDIDATES entries can be deleted.
24
+ upstream:
25
+ runs-on: ubuntu-latest
26
+ env:
27
+ # requirement | GitHub repo | git ref | versioneer tag to stamp (or "-")
28
+ CANDIDATES: |
29
+ pyiron_workflow_vasp>=0.2.0,<0.3 | pyiron/pyiron_workflow_vasp | pull/7/head | pyiron_workflow_vasp-0.2.0
30
+ pyiron_workflow_assyst>=0.2.0,<0.3 | ligerzero-ai/pyiron_workflow_assyst | pull/2/head | -
31
+ steps:
32
+ - uses: actions/setup-python@v5
33
+ with:
34
+ python-version: "3.12"
35
+ - name: Build wheels for unreleased upstream requirements
36
+ run: |
37
+ set -euo pipefail
38
+ python -m pip install --upgrade pip build pyyaml==6.0.1 toml==0.10.2
39
+ WH="$RUNNER_TEMP/wheelhouse"; mkdir -p "$WH"
40
+ while IFS='|' read -r req repo ref tag; do
41
+ req=$(echo "$req" | xargs); repo=$(echo "$repo" | xargs)
42
+ ref=$(echo "$ref" | xargs); tag=$(echo "$tag" | xargs)
43
+ [ -z "$req" ] && continue
44
+ if python -m pip download -q --no-deps -d "$RUNNER_TEMP/probe" "$req" 2>/dev/null; then
45
+ echo "::notice::$req is on PyPI; testing against the release"
46
+ continue
47
+ fi
48
+ echo "::warning::$req is not on PyPI yet; building a candidate from $repo@$ref"
49
+ src="$RUNNER_TEMP/src-${repo##*/}"
50
+ git clone -q "https://github.com/$repo" "$src"
51
+ git -C "$src" fetch -q origin "$ref"
52
+ git -C "$src" checkout -q FETCH_HEAD
53
+ if [ "$tag" != "-" ]; then
54
+ # -f: the upstream tag may already exist on another commit; this
55
+ # clone is throwaway, so stamping the candidate is local only.
56
+ git -C "$src" -c user.name=ci -c user.email=ci@localhost tag -f "$tag"
57
+ fi
58
+ # pyiron release workflows rewrite == pins into ranges before
59
+ # building; do the same so the candidate carries release metadata.
60
+ script="$src/.github/actions/.support/update_pyproject_dependencies.py"
61
+ if [ -f "$script" ]; then
62
+ (cd "$src" && python "$script" --input_toml=pyproject.toml \
63
+ --lower_bound_yaml=.ci_support/lower-bound.yml \
64
+ --upper_bound_yaml=.ci_support/upper-bound.yml \
65
+ --semantic_upper_bound=minor --always_pin_unstable=yes \
66
+ --output_toml=none \
67
+ --pypi_to_conda_name_map_file=.github/actions/.support/pypi_vs_conda_names.json)
68
+ fi
69
+ python -m build --wheel --outdir "$WH" "$src"
70
+ done <<< "$CANDIDATES"
71
+ # The artifact must exist even when nothing was built, and upload-artifact
72
+ # skips hidden files, so leave a visible manifest.
73
+ (cd "$WH" && ls *.whl 2>/dev/null || true) > "$WH/MANIFEST.txt"
74
+ ls -la "$WH"
75
+ - uses: actions/upload-artifact@v4
76
+ with:
77
+ name: wheelhouse
78
+ path: ${{ runner.temp }}/wheelhouse/
79
+ if-no-files-found: ignore
80
+
81
+ build:
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+ - uses: actions/setup-python@v5
86
+ with:
87
+ python-version: "3.12"
88
+ - name: Build sdist and wheel (isolated)
89
+ run: |
90
+ python -m pip install --upgrade pip build twine
91
+ python -m build
92
+ python -m twine check --strict dist/*
93
+ - uses: actions/upload-artifact@v4
94
+ with:
95
+ name: dist
96
+ path: dist/
97
+
98
+ install:
99
+ needs: [build, upstream]
100
+ runs-on: ubuntu-latest
101
+ strategy:
102
+ fail-fast: false
103
+ matrix:
104
+ python-version: ["3.11", "3.12", "3.13"]
105
+ source: [wheel, sdist]
106
+ steps:
107
+ - uses: actions/checkout@v4
108
+ - uses: actions/setup-python@v5
109
+ with:
110
+ python-version: ${{ matrix.python-version }}
111
+ - uses: actions/download-artifact@v4
112
+ with:
113
+ name: dist
114
+ path: dist/
115
+ - uses: actions/download-artifact@v4
116
+ with:
117
+ name: wheelhouse
118
+ path: ${{ runner.temp }}/wheelhouse
119
+ - name: Install into a fresh venv from outside the checkout
120
+ run: |
121
+ WORK="$RUNNER_TEMP/clean"
122
+ mkdir -p "$WORK"
123
+ python -m venv "$WORK/venv"
124
+ "$WORK/venv/bin/python" -m pip install --upgrade pip
125
+ if [ "${{ matrix.source }}" = wheel ]; then
126
+ PKG=$(ls "$GITHUB_WORKSPACE"/dist/*.whl)
127
+ else
128
+ PKG=$(ls "$GITHUB_WORKSPACE"/dist/*.tar.gz)
129
+ fi
130
+ cd "$WORK"
131
+ "$WORK/venv/bin/python" -m pip install --find-links "$RUNNER_TEMP/wheelhouse" "${PKG}[test]"
132
+ "$WORK/venv/bin/python" -m pip check
133
+ - name: Import and CLI smoke test
134
+ working-directory: ${{ runner.temp }}/clean
135
+ run: |
136
+ venv/bin/python -c "import assystant, sys; print(assystant.__version__, assystant.__file__); assert 'site-packages' in assystant.__file__"
137
+ venv/bin/assystant --help
138
+ venv/bin/assystant gen "$GITHUB_WORKSPACE/examples/Al_tutorial.yaml" --root "$RUNNER_TEMP/clean/camp" --no-progress
139
+ - name: Test suite against the installed package
140
+ working-directory: ${{ runner.temp }}/clean
141
+ run: |
142
+ cp -r "$GITHUB_WORKSPACE/tests" tests
143
+ venv/bin/python -m pytest tests -q -p no:cacheprovider
@@ -0,0 +1,104 @@
1
+ # Build and publish assystant to PyPI.
2
+ #
3
+ # - pull_request: build + `twine check` only (dry run, nothing is published).
4
+ # - release published: build, verify the release tag matches the package
5
+ # version, then publish via PyPI trusted publishing (OIDC, no API token).
6
+ #
7
+ # Trusted publisher on PyPI: owner ligerzero-ai, repository assystant,
8
+ # workflow release.yml, environment pypi.
9
+
10
+ name: release
11
+
12
+ on:
13
+ release:
14
+ types: [published]
15
+ pull_request:
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ jobs:
21
+ build:
22
+ name: Build distributions
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ with:
27
+ persist-credentials: false
28
+
29
+ - uses: actions/setup-python@v5
30
+ with:
31
+ python-version: "3.12"
32
+
33
+ - name: Install build tooling
34
+ run: python -m pip install build twine
35
+
36
+ - name: Build sdist and wheel
37
+ run: python -m build
38
+
39
+ - name: Check distributions
40
+ run: twine check --strict dist/*
41
+
42
+ - name: Verify release tag matches package version
43
+ if: github.event_name == 'release'
44
+ env:
45
+ TAG: ${{ github.event.release.tag_name }}
46
+ DIST: assystant
47
+ run: |
48
+ set -euo pipefail
49
+ wheels=(dist/*.whl)
50
+ if [ "${#wheels[@]}" -ne 1 ]; then
51
+ echo "::error::expected exactly one wheel in dist/, found ${#wheels[@]}"
52
+ exit 1
53
+ fi
54
+ wheel_version=$(basename "${wheels[0]}" | cut -d- -f2)
55
+ meta_version=$(python - "${wheels[0]}" <<'PY'
56
+ import sys, zipfile
57
+ from email.parser import Parser
58
+ with zipfile.ZipFile(sys.argv[1]) as zf:
59
+ name = next(n for n in zf.namelist() if n.endswith(".dist-info/METADATA"))
60
+ print(Parser().parsestr(zf.read(name).decode())["Version"])
61
+ PY
62
+ )
63
+ if [ "$wheel_version" != "$meta_version" ]; then
64
+ echo "::error::wheel filename version $wheel_version != METADATA version $meta_version"
65
+ exit 1
66
+ fi
67
+ # Accept "v<ver>", "<dist>-<ver>" (underscore or hyphen spelling), or "<ver>".
68
+ ver="$TAG"
69
+ for prefix in "v" "${DIST}-" "${DIST//_/-}-"; do
70
+ if [ "${ver#"$prefix"}" != "$ver" ]; then
71
+ ver="${ver#"$prefix"}"
72
+ break
73
+ fi
74
+ done
75
+ if [ "$ver" != "$meta_version" ]; then
76
+ echo "::error::release tag '$TAG' (version '$ver') does not match package version '$meta_version'"
77
+ exit 1
78
+ fi
79
+ echo "Tag '$TAG' matches package version $meta_version"
80
+
81
+ - uses: actions/upload-artifact@v4
82
+ with:
83
+ name: dist
84
+ path: dist/
85
+ if-no-files-found: error
86
+
87
+ publish:
88
+ name: Publish to PyPI
89
+ needs: build
90
+ if: github.event_name == 'release'
91
+ runs-on: ubuntu-latest
92
+ environment:
93
+ name: pypi
94
+ url: https://pypi.org/p/assystant
95
+ permissions:
96
+ id-token: write
97
+ steps:
98
+ - uses: actions/download-artifact@v4
99
+ with:
100
+ name: dist
101
+ path: dist/
102
+
103
+ - name: Publish to PyPI
104
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .superpowers/
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .ipynb_checkpoints/
9
+ pyiron.log
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [semantic versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] - Unreleased
8
+
9
+ Initial release.
10
+
11
+ ### Added
12
+
13
+ - `assystant` command-line driver for ASSYST campaigns:
14
+ - `gen`: PyXtal structure generation and INCAR assembly from a campaign config.
15
+ - `run`: per-structure DFT submission to SLURM via `sbatch`, with an in-job
16
+ runner and a `--dry-run` mode.
17
+ - `status`: campaign progress across structures.
18
+ - `collect`: collation of finished calculations into a training dataset.
19
+ - `vasp.launcher` config option for the MPI launcher.
20
+ - User documentation set under `docs/` and a runnable example campaign
21
+ (`examples/Fe_smoke.yaml`).
22
+ - CI: `clean-install` workflow and a `release` workflow that publishes to PyPI
23
+ via trusted publishing.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Han Lin Mai, Max-Planck-Institut für Nachhaltige Materialien GmbH (MPI-SusMat)
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.5
2
+ Name: assystant
3
+ Version: 0.1.0
4
+ Summary: Command-line driver for ASSYST campaigns: PyXtal generation -> per-structure DFT on SLURM -> collated training dataset
5
+ Author: Han Lin Mai
6
+ License: BSD-3-Clause
7
+ License-File: LICENSE
8
+ Keywords: assyst,dft,machine-learning-potentials,pyxtal,slurm,vasp
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Scientific/Engineering :: Physics
15
+ Requires-Python: <3.14,>=3.11
16
+ Requires-Dist: numpy
17
+ Requires-Dist: pandas>=1.3.0
18
+ Requires-Dist: pyiron-workflow-assyst<0.3,>=0.2.0
19
+ Requires-Dist: pyiron-workflow<0.21,>=0.19
20
+ Requires-Dist: pymatgen>=2023.0.0
21
+ Requires-Dist: pyxtal>=1.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: tqdm>=4.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-cov>=2.0; extra == 'dev'
26
+ Requires-Dist: pytest>=6.0; extra == 'dev'
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=6.0; extra == 'test'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # assystant
32
+
33
+ Run an ASSYST campaign end to end from a single YAML file: PyXtal structure
34
+ generation → one SLURM job per structure running the ISIF7 → ISIF5 → ISIF2
35
+ relaxation and permutation-statics chain → a single collated training dataset.
36
+
37
+ ASSYST generates DFT training data for interatomic potentials: random crystals
38
+ across all 230 spacegroups, relaxed through a three-stage ISIF chain, then
39
+ high-accuracy statics on the relaxed images *and* on rattled, triaxially strained
40
+ and sheared permutations of them — the off-equilibrium configurations a potential
41
+ needs and that ordinary MD never visits.
42
+
43
+ `assystant` drives that per campaign, replacing hand-copied
44
+ `2025_03_16_Unaries.py` scripts, element lists hardcoded in notebook cells, and a
45
+ `submit_temp.sh` maintained by hand with one line per element.
46
+
47
+ ```bash
48
+ assystant gen camp.yaml # PyXtal scan -> POSCARs + manifest
49
+ assystant run camp.yaml --dry-run --limit 1 # render one job; read it
50
+ assystant run camp.yaml # submit; also the resume command
51
+ assystant status camp.yaml --watch 300 # progress
52
+ assystant collect camp.yaml -o train.pkl # one dataframe
53
+ ```
54
+
55
+ ## Documentation
56
+
57
+ | | |
58
+ |---|---|
59
+ | [**Overview**](docs/overview.md) | what ASSYST does, what `assystant` adds, how the three repos fit together |
60
+ | [**Installation**](docs/installation.md) | dependencies, install order, the POTCAR config |
61
+ | [**Worked example**](docs/worked-example.md) | a complete campaign, start to finish, that you can copy |
62
+ | [**Configuration**](docs/configuration.md) | every YAML key: meaning, default, when to change it |
63
+ | [**Commands**](docs/commands.md) | `gen` / `run` / `status` / `collect` reference |
64
+ | [**Operations**](docs/operations.md) | content-hashed directories, the staleness guard, `--from-scratch`, smoke testing — the part that saves days |
65
+ | [**Behaviour changes**](docs/behaviour-changes.md) | read before comparing new data against pre-2026 campaigns |
66
+ | [**Troubleshooting**](docs/troubleshooting.md) | real failure modes and their actual error text |
67
+
68
+ Background: [design spec](docs/2026_07_31_assystant_design.md) ·
69
+ [implementation report](docs/implementation-report.txt)
70
+
71
+ ## Install
72
+
73
+ ```bash
74
+ python -m pip install assystant
75
+ ```
76
+
77
+ Python 3.11–3.13. No `--no-build-isolation`, no pre-installed scientific stack:
78
+ `pip`, `uv pip install assystant`, a `venv`, a conda/mamba env or a pixi project
79
+ all work the same way. See [installation](docs/installation.md) for each variant,
80
+ the POTCAR configuration and what a real run additionally needs (SLURM, a VASP
81
+ binary).
82
+
83
+ > **Pre-release:** until `pyiron_workflow_vasp` 0.2.0 and
84
+ > `pyiron_workflow_assyst` 0.2.0 are on PyPI, install those two from their wheels
85
+ > first — see [installation](docs/installation.md#before-the-dependencies-are-on-pypi).
86
+
87
+ ## Quick start
88
+
89
+ ```bash
90
+ assystant --help
91
+ assystant gen examples/Al_tutorial.yaml --root ./al_campaign --no-progress
92
+ ```
93
+
94
+ `gen` needs neither SLURM nor VASP, so this works on any machine: it writes 43
95
+ POSCARs and a `manifest.csv` under `./al_campaign/Al/`. `--root` overrides the
96
+ cluster path in the YAML. To go further — `run`, `status`, `collect` — edit the
97
+ `slurm:` and `vasp:` sections to point at your partition, environment and VASP
98
+ binary, and set up the [POTCAR config](docs/installation.md#the-potcar-library).
99
+
100
+ [`examples/Al_tutorial.yaml`](examples/Al_tutorial.yaml) is a small, complete,
101
+ minutes-long campaign walked through in
102
+ [`docs/worked-example.md`](docs/worked-example.md).
103
+ [`examples/Fe_unary.yaml`](examples/Fe_unary.yaml) shows production settings.
104
+ [`examples/Fe_smoke.yaml`](examples/Fe_smoke.yaml) proves an install actually runs
105
+ DFT, on a login node, in under a minute.
106
+
107
+ ## Layout on disk
108
+
109
+ ```
110
+ <root>/
111
+ assystant_config.yaml the resolved config `run` was launched with
112
+ Fe/
113
+ manifest.csv index, hash, spacegroup, composition, n_atoms, V/atom
114
+ structures/00000_structure.vasp ...
115
+ struct_00000_3f9c1a20/ one per structure, content-hashed
116
+ assystant_job.sh
117
+ assystant.out / assystant.err
118
+ ISIF7.tar.gz ISIF5.tar.gz ISIF2.tar.gz ...
119
+ df_ASSYST_jobs.pkl present == finished
120
+ ```
121
+
122
+ The `_3f9c1a20` is the first eight hex digits of the SHA-256 of the POSCAR. That
123
+ is a correctness mechanism, not a naming convention — the caching layer
124
+ underneath keys on directory path and never hashes its inputs, so a
125
+ content-derived name is what stops one structure's results from being served for
126
+ another. See
127
+ [content-hashed run directories](docs/operations.md#content-hashed-run-directories).
128
+
129
+ ## Three things to know before your first campaign
130
+
131
+ 1. **Start with `--limit 1`.** A dry run proves the script renders; it cannot
132
+ prove the VASP binary, the modules, the POTCAR library or your INCAR work on a
133
+ compute node. Submit one, check it, then submit the rest.
134
+ 2. **`IBRION` is required** whenever `assyst.ionic_steps > 0`. A missing one makes
135
+ VASP default to molecular dynamics and refuse the job. Use `IBRION: 2`.
136
+ 3. **`ionic_steps` now applies to all three ISIF stages**, where pre-2026
137
+ campaigns applied it to ISIF7 only. New ISIF5/ISIF2 data is not directly
138
+ comparable with old datasets — see
139
+ [behaviour changes](docs/behaviour-changes.md).
140
+
141
+ ## Tests
142
+
143
+ ```bash
144
+ python -m pip install "assystant[test]"
145
+ pytest tests/ -q
146
+ ```
147
+
148
+ 108 tests. None call SLURM or VASP; `submit_campaign` takes `queued_dirs` and
149
+ `submit_fn` injection seams for exactly that reason. For an end-to-end check that
150
+ runs real DFT, use [`examples/Fe_smoke.yaml`](examples/Fe_smoke.yaml).
151
+
152
+ ## Releasing
153
+
154
+ Releases go to PyPI from GitHub Actions via
155
+ [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no API token
156
+ is stored anywhere.
157
+
158
+ 1. Bump `__version__` in `assystant/__init__.py` (the only place the version lives) and add
159
+ an entry to [`CHANGELOG.md`](CHANGELOG.md).
160
+ 2. Open a PR and merge it to `main`. The
161
+ [`release`](.github/workflows/release.yml) workflow builds the sdist and
162
+ wheel and runs `twine check --strict` on every PR as a dry run.
163
+ 3. Create a GitHub release with tag `v<version>` (e.g. `v0.1.0`). Publishing
164
+ the release builds again, checks that the tag matches the package version
165
+ (a `v` or `assystant-` prefix is stripped), and uploads to
166
+ [PyPI](https://pypi.org/p/assystant).
167
+
168
+ One-time setup: on PyPI, add a trusted publisher for `assystant` with owner
169
+ `ligerzero-ai`, repository `assystant`, workflow `release.yml` and environment
170
+ `pypi`, and create a `pypi` environment in the GitHub repository settings
171
+ (optionally with required reviewers).
@@ -0,0 +1,141 @@
1
+ # assystant
2
+
3
+ Run an ASSYST campaign end to end from a single YAML file: PyXtal structure
4
+ generation → one SLURM job per structure running the ISIF7 → ISIF5 → ISIF2
5
+ relaxation and permutation-statics chain → a single collated training dataset.
6
+
7
+ ASSYST generates DFT training data for interatomic potentials: random crystals
8
+ across all 230 spacegroups, relaxed through a three-stage ISIF chain, then
9
+ high-accuracy statics on the relaxed images *and* on rattled, triaxially strained
10
+ and sheared permutations of them — the off-equilibrium configurations a potential
11
+ needs and that ordinary MD never visits.
12
+
13
+ `assystant` drives that per campaign, replacing hand-copied
14
+ `2025_03_16_Unaries.py` scripts, element lists hardcoded in notebook cells, and a
15
+ `submit_temp.sh` maintained by hand with one line per element.
16
+
17
+ ```bash
18
+ assystant gen camp.yaml # PyXtal scan -> POSCARs + manifest
19
+ assystant run camp.yaml --dry-run --limit 1 # render one job; read it
20
+ assystant run camp.yaml # submit; also the resume command
21
+ assystant status camp.yaml --watch 300 # progress
22
+ assystant collect camp.yaml -o train.pkl # one dataframe
23
+ ```
24
+
25
+ ## Documentation
26
+
27
+ | | |
28
+ |---|---|
29
+ | [**Overview**](docs/overview.md) | what ASSYST does, what `assystant` adds, how the three repos fit together |
30
+ | [**Installation**](docs/installation.md) | dependencies, install order, the POTCAR config |
31
+ | [**Worked example**](docs/worked-example.md) | a complete campaign, start to finish, that you can copy |
32
+ | [**Configuration**](docs/configuration.md) | every YAML key: meaning, default, when to change it |
33
+ | [**Commands**](docs/commands.md) | `gen` / `run` / `status` / `collect` reference |
34
+ | [**Operations**](docs/operations.md) | content-hashed directories, the staleness guard, `--from-scratch`, smoke testing — the part that saves days |
35
+ | [**Behaviour changes**](docs/behaviour-changes.md) | read before comparing new data against pre-2026 campaigns |
36
+ | [**Troubleshooting**](docs/troubleshooting.md) | real failure modes and their actual error text |
37
+
38
+ Background: [design spec](docs/2026_07_31_assystant_design.md) ·
39
+ [implementation report](docs/implementation-report.txt)
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ python -m pip install assystant
45
+ ```
46
+
47
+ Python 3.11–3.13. No `--no-build-isolation`, no pre-installed scientific stack:
48
+ `pip`, `uv pip install assystant`, a `venv`, a conda/mamba env or a pixi project
49
+ all work the same way. See [installation](docs/installation.md) for each variant,
50
+ the POTCAR configuration and what a real run additionally needs (SLURM, a VASP
51
+ binary).
52
+
53
+ > **Pre-release:** until `pyiron_workflow_vasp` 0.2.0 and
54
+ > `pyiron_workflow_assyst` 0.2.0 are on PyPI, install those two from their wheels
55
+ > first — see [installation](docs/installation.md#before-the-dependencies-are-on-pypi).
56
+
57
+ ## Quick start
58
+
59
+ ```bash
60
+ assystant --help
61
+ assystant gen examples/Al_tutorial.yaml --root ./al_campaign --no-progress
62
+ ```
63
+
64
+ `gen` needs neither SLURM nor VASP, so this works on any machine: it writes 43
65
+ POSCARs and a `manifest.csv` under `./al_campaign/Al/`. `--root` overrides the
66
+ cluster path in the YAML. To go further — `run`, `status`, `collect` — edit the
67
+ `slurm:` and `vasp:` sections to point at your partition, environment and VASP
68
+ binary, and set up the [POTCAR config](docs/installation.md#the-potcar-library).
69
+
70
+ [`examples/Al_tutorial.yaml`](examples/Al_tutorial.yaml) is a small, complete,
71
+ minutes-long campaign walked through in
72
+ [`docs/worked-example.md`](docs/worked-example.md).
73
+ [`examples/Fe_unary.yaml`](examples/Fe_unary.yaml) shows production settings.
74
+ [`examples/Fe_smoke.yaml`](examples/Fe_smoke.yaml) proves an install actually runs
75
+ DFT, on a login node, in under a minute.
76
+
77
+ ## Layout on disk
78
+
79
+ ```
80
+ <root>/
81
+ assystant_config.yaml the resolved config `run` was launched with
82
+ Fe/
83
+ manifest.csv index, hash, spacegroup, composition, n_atoms, V/atom
84
+ structures/00000_structure.vasp ...
85
+ struct_00000_3f9c1a20/ one per structure, content-hashed
86
+ assystant_job.sh
87
+ assystant.out / assystant.err
88
+ ISIF7.tar.gz ISIF5.tar.gz ISIF2.tar.gz ...
89
+ df_ASSYST_jobs.pkl present == finished
90
+ ```
91
+
92
+ The `_3f9c1a20` is the first eight hex digits of the SHA-256 of the POSCAR. That
93
+ is a correctness mechanism, not a naming convention — the caching layer
94
+ underneath keys on directory path and never hashes its inputs, so a
95
+ content-derived name is what stops one structure's results from being served for
96
+ another. See
97
+ [content-hashed run directories](docs/operations.md#content-hashed-run-directories).
98
+
99
+ ## Three things to know before your first campaign
100
+
101
+ 1. **Start with `--limit 1`.** A dry run proves the script renders; it cannot
102
+ prove the VASP binary, the modules, the POTCAR library or your INCAR work on a
103
+ compute node. Submit one, check it, then submit the rest.
104
+ 2. **`IBRION` is required** whenever `assyst.ionic_steps > 0`. A missing one makes
105
+ VASP default to molecular dynamics and refuse the job. Use `IBRION: 2`.
106
+ 3. **`ionic_steps` now applies to all three ISIF stages**, where pre-2026
107
+ campaigns applied it to ISIF7 only. New ISIF5/ISIF2 data is not directly
108
+ comparable with old datasets — see
109
+ [behaviour changes](docs/behaviour-changes.md).
110
+
111
+ ## Tests
112
+
113
+ ```bash
114
+ python -m pip install "assystant[test]"
115
+ pytest tests/ -q
116
+ ```
117
+
118
+ 108 tests. None call SLURM or VASP; `submit_campaign` takes `queued_dirs` and
119
+ `submit_fn` injection seams for exactly that reason. For an end-to-end check that
120
+ runs real DFT, use [`examples/Fe_smoke.yaml`](examples/Fe_smoke.yaml).
121
+
122
+ ## Releasing
123
+
124
+ Releases go to PyPI from GitHub Actions via
125
+ [trusted publishing](https://docs.pypi.org/trusted-publishers/), so no API token
126
+ is stored anywhere.
127
+
128
+ 1. Bump `__version__` in `assystant/__init__.py` (the only place the version lives) and add
129
+ an entry to [`CHANGELOG.md`](CHANGELOG.md).
130
+ 2. Open a PR and merge it to `main`. The
131
+ [`release`](.github/workflows/release.yml) workflow builds the sdist and
132
+ wheel and runs `twine check --strict` on every PR as a dry run.
133
+ 3. Create a GitHub release with tag `v<version>` (e.g. `v0.1.0`). Publishing
134
+ the release builds again, checks that the tag matches the package version
135
+ (a `v` or `assystant-` prefix is stripped), and uploads to
136
+ [PyPI](https://pypi.org/p/assystant).
137
+
138
+ One-time setup: on PyPI, add a trusted publisher for `assystant` with owner
139
+ `ligerzero-ai`, repository `assystant`, workflow `release.yml` and environment
140
+ `pypi`, and create a `pypi` environment in the GitHub repository settings
141
+ (optionally with required reviewers).
@@ -0,0 +1,38 @@
1
+ """assystant -- run an ASSYST campaign end to end from one YAML file.
2
+
3
+ ``gen`` generates PyXtal structures, ``run`` submits one SLURM job per
4
+ structure, ``status`` reports progress, ``collect`` concatenates the results
5
+ into a single training dataset.
6
+ """
7
+
8
+ from .config import (
9
+ CampaignConfig,
10
+ ConfigError,
11
+ StaleConfigError,
12
+ system_name,
13
+ )
14
+ from .generate import (
15
+ enumerate_compositions,
16
+ parse_struct_dir,
17
+ poscar_hash,
18
+ struct_dir_name,
19
+ )
20
+ from .incar import get_incar_magmom_string, stack_element_string
21
+ from .submit import RESULT_FILENAME
22
+
23
+ __version__ = "0.1.0"
24
+
25
+ __all__ = [
26
+ "CampaignConfig",
27
+ "ConfigError",
28
+ "StaleConfigError",
29
+ "system_name",
30
+ "enumerate_compositions",
31
+ "parse_struct_dir",
32
+ "poscar_hash",
33
+ "struct_dir_name",
34
+ "get_incar_magmom_string",
35
+ "stack_element_string",
36
+ "RESULT_FILENAME",
37
+ "__version__",
38
+ ]