Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e1d6ad2
Implement Pacemaker support: codes are all updated and unit tests passed
yuxzhou Feb 3, 2026
96b2886
Implement Pacemaker support: error parsing and unit tests passed
yuxzhou Feb 3, 2026
3d7326e
Fix Pacemaker fitting: implementation of correct parameter passing
yuxzhou Feb 5, 2026
f7bbf62
Fix P-ACE: extract hyperparameters from fit_kwargs in machine_learnin…
yuxzhou Feb 5, 2026
4aebbd1
fix hypers passing issue for pacemaker
yuxzhou Feb 5, 2026
51b8650
implementation: Pacemaker ACE interface
yuxzhou Feb 6, 2026
ffa424a
fix: export extxyz file during pacemaker fitting
yuxzhou Feb 8, 2026
10288d2
check: e0 reading
yuxzhou Feb 8, 2026
201d12b
fix: species input
yuxzhou Feb 8, 2026
943b00f
Update README with installation instructions for Pacemaker
yuxzhou Feb 10, 2026
8a1b04e
update: remove all unnecessary comments; No changes on codes
yuxzhou Mar 3, 2026
4d691aa
Merge branch 'main' into feature/pacemaker-implementation
yuxzhou Mar 3, 2026
3e66741
pre-commit auto-fixes
pre-commit-ci[bot] Mar 3, 2026
45ecc77
Fix SIM102: Combine nested if statements in jobs.py
yuxzhou Mar 3, 2026
7de6ed8
Merge branch 'feature/pacemaker-implementation' of https://github.com…
yuxzhou Mar 3, 2026
cf27c74
fix an error in pacemaker fit unit test
yuxzhou Mar 3, 2026
91770a1
Fix dependency conflicts and update installation docs for Pacemaker
yuxzhou Mar 4, 2026
5585c47
fix: update for tensorpotential dependency
yuxzhou Apr 6, 2026
2e0e7c0
pre-commit auto-fixes
pre-commit-ci[bot] Apr 6, 2026
ea285ce
style: add noqa tags to bypass ruff PLC0415 for conditional imports
yuxzhou Apr 6, 2026
f8dbae4
chore: resolve merge conflict with pre-commit auto-fixes
yuxzhou Apr 6, 2026
d0843b4
ci: fix git dubious ownership error in docker container for python 3.10
yuxzhou Apr 8, 2026
75465b8
ci: edit python-package.yml
yuxzhou Apr 8, 2026
1aca90d
chore: remove hardcoded dependencies to fully rely on pyproject.toml …
yuxzhou Apr 10, 2026
78fcee5
Merge branch 'main' into feature/pacemaker-implementation
yuxzhou Apr 10, 2026
51af9f8
fix space typo
yuxzhou Apr 10, 2026
02ec6fb
Merge branch 'feature/pacemaker-implementation' of https://github.com…
yuxzhou Apr 10, 2026
31c5fea
ci: fix 3.10 version conflicts
yuxzhou Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
"ms-azuretools.vscode-docker"]
},
},
"postCreateCommand": "pip cache purge && uv pip install --prerelease=allow -e .[strict,docs] && pre-commit install",
"postCreateCommand": "pip cache purge && uv pip install --prerelease=allow -e '.[strict,docs,tests,pacemaker]' && pre-commit install",
}
3 changes: 2 additions & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ jobs:
run: |
micromamba activate autoplex_docs
uv pip install --upgrade pip
uv pip install --prerelease=allow .[docs,strict,tests]
uv pip install --prerelease=allow .[docs,strict,tests,pacemaker]
uv pip install --upgrade monty
uv pip install numpy==1.26.4


- name: Copy tutorials
run: |
Expand Down
50 changes: 39 additions & 11 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,39 @@ jobs:
- name: Run tests using Docker image for Python ${{ matrix.python-version }}
run: |
docker pull ghcr.io/autoatml/autoplex/autoplex-python-${{ matrix.python-version }}:${{ env.VERSION }}
docker run --rm \
-v ${{ github.workspace }}:/workspace \
-w /workspace \
ghcr.io/autoatml/autoplex/autoplex-python-${{ matrix.python-version }}:${{ env.VERSION }} \
bash -c "
git config --global --add safe.directory /workspace && \
python -m pip install --upgrade pip && \
python -m uv cache clean && \
python -m uv pip install --prerelease=allow .[strict,tests,aims] && \
OMP_NUM_THREADS=1 pytest --cache-clear --cov=autoplex --cov-report term-missing --cov-append --splits 5 --group ${{ matrix.split }} -vv --durations-path /workspace/tests/test_data/.pytest-split-durations --store-durations
"

BASE_TEST_CMD="OMP_NUM_THREADS=1 pytest --cache-clear --cov=autoplex --cov-report term-missing --cov-append --splits 5 --group ${{ matrix.split }} -vv --durations-path /workspace/tests/test_data/.pytest-split-durations --store-durations"

if [ "${{ matrix.python-version }}" == "3.10" ]; then
docker run --rm \
-v ${{ github.workspace }}:/workspace \
-w /workspace \
ghcr.io/autoatml/autoplex/autoplex-python-${{ matrix.python-version }}:${{ env.VERSION }} \
bash -c "
git config --global --add safe.directory /workspace && \
python -m pip install --upgrade pip && \
python -m uv cache clean && \
python -m uv pip install 'protobuf<3.20' tensorflow==2.8.0 && \
python -m uv pip install --no-deps git+https://github.com/ICAMS/TensorPotential.git@1e44b2558356800ae070658c0bb856ff9bf74538 && \
python -m uv pip install --no-deps git+https://github.com/ICAMS/python-ace.git@d1c213a7d9c5b809a3ae83b2e5a916be26d921f0 && \
python -m uv pip install --prerelease=allow .[strict,tests,aims] && \
$BASE_TEST_CMD
"

else
docker run --rm \
-v ${{ github.workspace }}:/workspace \
-w /workspace \
ghcr.io/autoatml/autoplex/autoplex-python-${{ matrix.python-version }}:${{ env.VERSION }} \
bash -c "
git config --global --add safe.directory /workspace && \
python -m pip install --upgrade pip && \
python -m uv cache clean && \
python -m uv pip install --prerelease=allow '.[strict,tests,aims,pacemaker]' && \
$BASE_TEST_CMD -k 'not PACE'
"

fi

- name: Upload test durations artifact
if: matrix.python-version == '3.10'
Expand Down Expand Up @@ -94,6 +116,9 @@ jobs:
run: |
micromamba activate autoplex_tests
uv pip install --upgrade pip
uv pip install tensorflow==2.8.0
uv pip install --no-deps git+https://github.com/ICAMS/TensorPotential.git@1e44b2558356800ae070658c0bb856ff9bf74538
uv pip install --no-deps git+https://github.com/ICAMS/python-ace.git@d1c213a7d9c5b809a3ae83b2e5a916be26d921f0
uv pip install --prerelease=allow .[tests,strict]

- name: Download test duration artifacts
Expand Down Expand Up @@ -167,6 +192,9 @@ jobs:
run: |
micromamba activate autoplex_docs
uv pip install --upgrade pip
uv pip install tensorflow==2.8.0
uv pip install --no-deps git+https://github.com/ICAMS/TensorPotential.git@1e44b2558356800ae070658c0bb856ff9bf74538
uv pip install --no-deps git+https://github.com/ICAMS/python-ace.git@d1c213a7d9c5b809a3ae83b2e5a916be26d921f0
uv pip install --prerelease=allow .[docs,strict,tests,aims]

- name: Copy tutorials
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ COPY . /workspace
RUN python -m pip install --upgrade pip \
&& pip install uv \
&& uv pip install --system pre-commit pytest pytest-mock pytest-split pytest-cov types-setuptools \
&& uv pip install --system --prerelease=allow ".[strict,docs]" \
&& uv pip install --system --prerelease=allow ".[strict,docs,pacemaker]" \
&& uv cache clean && rm -rf /tmp/*
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,20 @@ Once installed in the terminal, run the following commands to get Julia ACEpoten
julia -e 'using Pkg; Pkg.Registry.add("General"); Pkg.Registry.add(Pkg.Registry.RegistrySpec(url="https://github.com/ACEsuit/ACEregistry")); Pkg.add(Pkg.PackageSpec(;name="ACEpotentials", version="0.6.7")); Pkg.add("DataFrames"); Pkg.add("CSV")'
```

> ℹ️ To fit and validate `Pacemaker ACE` potentials, one also needs to install `tensorflow`, `tensorpotential`, and `python-ace`.
> Please note that Pacemaker ACE fitting can be run on both CPU and GPU.
> ⚠️ Please also note on versioning: to prevent dependency conflicts (e.g., with `pandas` versions) and ensure stability, please install the exact commit hashes listed below using the `--no-deps` flag. These specific versions have been fully tested and validated for use with Autoplex.

```bash
pip install tensorflow==2.8.0
pip install --no-deps git+https://github.com/ICAMS/TensorPotential.git@1e44b2558356800ae070658c0bb856ff9bf74538
# Ensure CMake is available before running this:
pip install --no-deps git+https://github.com/ICAMS/python-ace.git@d1c213a7d9c5b809a3ae83b2e5a916be26d921f0
```

> ℹ️ To fit and validate NEP potentials, one requires an Nvidia GPU card with compute capability no less than 3.5 and CUDA toolkit 9.0 or newer. This potential can only be trained on GPU only and currently interface to NEP potential training is provided via [calorine](https://calorine.materialsmodeling.org/) package that uses `nep` executable from the [GPUMD](https://gpumd.org/index.html) package. To get this executable please follow the compilation instructions [here](https://gpumd.org/installation.html) and add this executable to the system path.


## Enabling RSS workflows

Additionally, `buildcell` as a part of `AIRSS` needs to be installed if one wants to use the RSS functionality:
Expand Down
20 changes: 18 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ dependencies = [
"calorine>=3.0",
"matgl>=1.2.6",
"mace-torch>=0.3.12",
"numpy",
"pandas>=2.2",
"numpy<=1.26.4",
"scipy",
"ruamel.yaml",
"psutil",
"scikit-learn<=1.4.2",
"lightning-utilities==0.14.3",
"typing",
"nequip",
Expand Down Expand Up @@ -76,7 +81,12 @@ strict = [
"ase==3.24.0",
"mace-torch==0.3.13",
"lightning-utilities==0.14.3",
"numpy",
"numpy==1.26.4",
"pandas>=2.2",
"scipy",
"ruamel.yaml",
"psutil",
"scikit-learn==1.4.2",
"typing",
"dgl<=2.2.0; sys.platform != 'linux'",
"dgl<=2.4; sys.platform == 'linux'",
Expand All @@ -90,6 +100,12 @@ strict = [
dev = ["pre-commit>=2.12.1"]
tests = ["pytest", "pytest-mock", "pytest-split", "pytest-cov", "types-setuptools", "nbmake"]
aims = ["pyfhiaims"]
pacemaker = [
"protobuf<3.20 ; python_version == '3.10'",
"tensorflow==2.8.0 ; python_version == '3.10'",
"tensorpotential @ git+https://github.com/ICAMS/TensorPotential.git@1e44b2558356800ae070658c0bb856ff9bf74538 ; python_version == '3.10'",
Copy link
Copy Markdown
Collaborator

@naik-aakash naik-aakash Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will not be able to release the autoplex on PyPI with URL-based dependencies.

Can a new version for tensorpotential / pyace be requested for release on PyPI?

@yuxzhou, I assume a PyPI release of autoplex is expected after this PR is merged. Thus asking

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That it is true. We likely need to keep the additional Readme.txt stuff.

"pyace @ git+https://github.com/ICAMS/python-ace.git@d1c213a7d9c5b809a3ae83b2e5a916be26d921f0 ; python_version == '3.10'"
]

[tool.setuptools_scm]

Expand Down
2 changes: 2 additions & 0 deletions src/autoplex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
MLIPHypers,
NEPSettings,
NEQUIPSettings,
PacemakerSettings,
RssConfig,
)

Expand All @@ -27,3 +28,4 @@
MACE_HYPERS = MACESettings()
NEQUIP_HYPERS = NEQUIPSettings()
NEP_HYPERS = NEPSettings()
PACEMAKER_HYPERS = PacemakerSettings()
2 changes: 1 addition & 1 deletion src/autoplex/auto/rss/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def make(self, **kwargs):
pre_database_dir: str | None
Directory where the previous database was saved.
mlip_type: str
Choose one specific MLIP type to be fitted: 'GAP' | 'J-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE'.
Choose one specific MLIP type to be fitted: 'GAP' | 'J-ACE' | 'P-ACE' | 'NEQUIP' | 'M3GNET' | 'MACE'.
Default is 'GAP'.
ref_energy_name: str
Reference energy name. Default is 'REF_energy'.
Expand Down
12 changes: 8 additions & 4 deletions src/autoplex/auto/rss/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ def initial_rss(
force_max: float | None = None,
force_label: str = "REF_forces",
pre_database_dir: str | None = None,
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"] = "GAP",
mlip_type: Literal[
"GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"
] = "GAP",
ref_energy_name: str = "REF_energy",
ref_force_name: str = "REF_forces",
ref_virial_name: str = "REF_virial",
Expand Down Expand Up @@ -197,7 +199,7 @@ def initial_rss(
The label of force values to use for distillation. Default is 'REF_forces'.
pre_database_dir: str | None
Directory where the previous database was saved. Default is None.
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted. Default is 'GAP'.
ref_energy_name: str
Reference energy name. Default is 'REF_energy'.
Expand Down Expand Up @@ -366,7 +368,9 @@ def do_rss_iterations(
distillation: bool = True,
force_max: float = 200,
force_label: str = "REF_forces",
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"] = "GAP",
mlip_type: Literal[
"GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"
] = "GAP",
ref_energy_name: str = "REF_energy",
ref_force_name: str = "REF_forces",
ref_virial_name: str = "REF_virial",
Expand Down Expand Up @@ -508,7 +512,7 @@ def do_rss_iterations(
Maximum force value to exclude structures. Default is 200.
force_label: str
The label of force values to use for distillation. Default is 'REF_forces'.
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted. Default is 'GAP'.
ref_energy_name: str
Reference energy name. Default is 'REF_energy'.
Expand Down
8 changes: 4 additions & 4 deletions src/autoplex/data/rss/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def _parallel_process(

@job
def do_rss_single_node(
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_path: str | Path,
iteration_index: str,
structures: list[Structure],
Expand All @@ -451,7 +451,7 @@ def do_rss_single_node(

Parameters
----------
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted.
mlip_path: str | Path
Path to the MLIP model.
Expand Down Expand Up @@ -527,7 +527,7 @@ def do_rss_single_node(

@job
def do_rss_multi_node(
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_path: str | Path,
iteration_index: str,
structure: list[Structure] | list[list[Structure]] | None = None,
Expand Down Expand Up @@ -555,7 +555,7 @@ def do_rss_multi_node(

Parameters
----------
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted.
mlip_path: str | Path
Path to the MLIP model.
Expand Down
29 changes: 26 additions & 3 deletions src/autoplex/data/rss/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ def process_rss(
ASE Atoms object representing the atomic configuration.
mlip_type: str
Choose one specific MLIP type:
'GAP' | 'J-ACE' | 'NequIP' | 'M3GNet' | 'MACE'.
'GAP' | 'J-ACE' | 'P-ACE' | 'NequIP' | 'M3GNet' | 'MACE'.
mlip_path: str | Path
Path to the MLIP model.
output_file_name: str
Expand Down Expand Up @@ -420,6 +420,29 @@ def process_rss(
lmpcmds=cmds, atom_types=atom_types, log_file="test.log", keep_alive=True
)

elif mlip_type == "P-ACE":

mlip_path_obj = Path(mlip_path)
potential_file = None

if mlip_path_obj.is_file():
potential_file = mlip_path_obj
else:
target_file = mlip_path_obj / "output_potential.yaml"
if target_file.exists():
potential_file = target_file

if potential_file is None:
raise FileNotFoundError(
f"Could not find 'output_potential.yaml' in {mlip_path} for P-ACE."
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you try importing AutoplexPyACECalculator here, rather than at the beginning? I would like to make that we step by step disentangle the fitting installations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you Janine! All sound very good. I will quickly reply to your points regarding pacemaker installation / inplementation in this chat (and show more details in the following sperate chats).

For the purpose: yes, we want to have a separate ACE-driven RSS flow that is independent of any GAP-RSS runs. I am not saying ACE will completely displace GAP (people can still run GAP-RSS if they want!), but we will have other MLIP options for the RSS process (ACE is particularlly important because it enables super large-scale MD simulations).

  • Regarding the dependency conflicts, I completely agree that we shall separate the installation of tensorpotential / python-ace and their original dependencies. To this end, I have added necessary dependencies in the pyproject.toml file, and modified the README.md file so that we guide users to properly install tensorflow / tensorpotential / python-ace without dependencies (i.e., via the way you suggest here pip install --no-deps git+https://github.com/USER/REPO.git@COMMIT_HASH).

  • For implementing pacemaker, that's a great idea. I understand that autoplex should still be able to run even without the installation of tensorflow / tensorpotential / python-ace (especially for those who don't want to run ACE-RSS at all!). I will move all importings into the if clauses.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks!
In the future, we should seperate all imports for the different MLIPs but we will do this stepwise! 😃

from autoplex.fitting.common.utils import ( # noqa: PLC0415
AutoplexPyACECalculator,
)

pot = AutoplexPyACECalculator(basis_set=str(potential_file.resolve()))

elif mlip_type == "NEQUIP":
nequip_label = os.path.join(mlip_path, "deployed_nequip_model.pth")
if isolated_atom_energies:
Expand Down Expand Up @@ -539,7 +562,7 @@ def build_traj():


def minimize_structures(
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"],
mlip_path: str | Path,
iteration_index: str,
structures: list[Structure],
Expand All @@ -566,7 +589,7 @@ def minimize_structures(

Parameters
----------
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted.
mlip_path: str | Path
Path to the MLIP model.
Expand Down
19 changes: 15 additions & 4 deletions src/autoplex/fitting/common/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class MLIPFitMaker(Maker):
----------
name : str
Name of the flows produced by this maker.
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]
Choose one specific MLIP type to be fitted.
hyperpara_opt: bool
Perform hyperparameter optimization using XPOT
Expand Down Expand Up @@ -93,7 +93,9 @@ class MLIPFitMaker(Maker):
"""

name: str = "MLpotentialFit"
mlip_type: Literal["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"] = "GAP"
mlip_type: Literal["GAP", "J-ACE", "P-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"] = (
"GAP"
)
hyperpara_opt: bool = False
ref_energy_name: str = "REF_energy"
ref_force_name: str = "REF_forces"
Expand Down Expand Up @@ -149,10 +151,19 @@ def make(
fit_kwargs: dict
Additional keyword arguments for MLIP fitting.
"""
if self.mlip_type not in ["GAP", "J-ACE", "NEP", "NEQUIP", "M3GNET", "MACE"]:
if self.mlip_type not in [
"GAP",
"J-ACE",
"P-ACE",
"NEP",
"NEQUIP",
"M3GNET",
"MACE",
]:
raise ValueError(
"Please correct the MLIP name!"
"The current version ONLY supports the following models: GAP, J-ACE, NEP, NEQUIP, M3GNET, and MACE."
"The current version ONLY supports the following models: "
"GAP, J-ACE, P-ACE, NEP, NEQUIP, M3GNET, and MACE."
)

if self.apply_data_preprocessing:
Expand Down
Loading
Loading