Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
35103ff
Add PET-OAM
frostedoyster Jan 30, 2026
4e0be09
Apply suggestion from review
frostedoyster Jan 30, 2026
cc68b2a
Apply one more suggestion
frostedoyster Jan 30, 2026
7e5fbf5
Add PET-MAD
frostedoyster Jan 30, 2026
c2fb4a7
Add package to optional dependencies
frostedoyster Jan 30, 2026
5f5dc43
Merge branch 'develop' into add/pet-mad
chiang-yuan Apr 16, 2026
22b5113
adjust deps group
chiang-yuan Apr 16, 2026
360c2ea
ruff format
chiang-yuan Apr 16, 2026
1babd6d
bump versions; fix tests
chiang-yuan May 3, 2026
c46c463
remove 3.10, add 3.13
chiang-yuan May 5, 2026
0582298
fix diatomics output path
chiang-yuan May 29, 2026
0cd6b7c
move stability flow under mlip_arena.flows and benchmarks/stability.py
chiang-yuan May 29, 2026
0a0df46
rollback dependencies
chiang-yuan Jun 4, 2026
3b85011
Refactor CI test environments to split conflicting models
chiang-yuan Jun 5, 2026
03e7e83
Refactor CI test dependencies: move model deps to optional group 'all…
chiang-yuan Jun 8, 2026
05204e1
ci: disable fail-fast in test matrix strategy
chiang-yuan Jun 8, 2026
c0ff4f1
ci: exclude python 3.13 for GNN/compiled model test groups and skip t…
chiang-yuan Jun 8, 2026
61aabbf
Refactor: remove torch_geometric and torch_scatter dependencies, impl…
chiang-yuan Jun 8, 2026
25c927d
move torch_dftd and matscipy to base dep
chiang-yuan Jun 9, 2026
fa2d864
Fix default CI test marker selection
Copilot Jun 9, 2026
08e9304
refactor: apply PyTorch/e3nn compatibility patches at model calculato…
chiang-yuan Jun 9, 2026
ea55fd1
refactor: consolidate asymptotes and shifts benchmarks into submit.py
chiang-yuan Jun 9, 2026
c026b2a
refactor: implement modular model-level reporting and refactor stabil…
chiang-yuan Jun 9, 2026
5167391
refactor: extract model name and calculator resolution to DRY helpers…
chiang-yuan Jun 9, 2026
ca182e0
refactor: simplify calculator resolution and restrict e3nn monkey-pat…
chiang-yuan Jun 9, 2026
c6be86e
test stability benchmark run
chiang-yuan Jun 9, 2026
a1bfdfe
Merge branch 'develop' into refactor/ci-test-environments
chiang-yuan Jun 12, 2026
400dc08
Merge branch 'develop' into refactor/ci-test-environments
chiang-yuan Jun 13, 2026
68c4d90
move stability from tasks to flows
chiang-yuan Jun 13, 2026
5eaa752
handle cache separately for each test group in CI
chiang-yuan Jun 13, 2026
30b49a5
test external calculators in different groups separately in CI
chiang-yuan Jun 13, 2026
2227a12
mark fairchem uma as xfail
chiang-yuan Jun 13, 2026
ba17517
separate orb-models into a separate test group and allow version <0.8.0
chiang-yuan Jun 13, 2026
91b8372
add to() method to custom Data class in collate.py
chiang-yuan Jun 14, 2026
ca06673
fix parameter binding for EOSFlow and EVFlow in submit.py
chiang-yuan Jun 14, 2026
acd2410
pin orb-models to 0.4.0 in pyproject.toml
chiang-yuan Jun 14, 2026
87b9ed6
gracefully handle pytest exit code 5 (no tests collected) in CI
chiang-yuan Jun 14, 2026
e80025c
set torch to 2.2 for orb test group and exclude python 3.13 in CI
chiang-yuan Jun 14, 2026
ab25cd5
monkeypatch typing.io in nequip model and exclude orb test group on p…
chiang-yuan Jun 14, 2026
e691ef9
remove e3nn optimization defaults workaround in nequip.py
chiang-yuan Jun 14, 2026
20a8e72
remove nequip patch and exclude nequip+py3.13 in CI
chiang-yuan Jun 14, 2026
3fdc925
pin orb-models to 0.5.5 and update torch dep to 2.8 in install-linux.sh
chiang-yuan Jun 14, 2026
ff3e9c7
increase test coverage
chiang-yuan Jun 15, 2026
ba8ee45
disable cache
chiang-yuan Jun 15, 2026
2365411
refactor(deps): loosen prefect constraint and comment out ipykernel/i…
chiang-yuan Jun 15, 2026
fa72868
ci: disable caching in setup-uv step
chiang-yuan Jun 15, 2026
c2cf674
test: switch prefect tests skip condition from python 3.11 to 3.12
chiang-yuan Jun 15, 2026
f5a8b81
test: run test_stability.py flows inside prefect_test_harness on pyth…
chiang-yuan Jun 15, 2026
024941b
test: isolate PREFECT_HOME in conftest.py to prevent concurrent datab…
chiang-yuan Jun 15, 2026
b627ba1
ci: add Free Disk Space step and update fairchem-core version to 2.21…
chiang-yuan Jun 15, 2026
6d8282d
test: remove xfail handler for UMA-S-1P1 to enable testing on CPU in CI
chiang-yuan Jun 15, 2026
9af14d3
test: set PREFECT_HOME env variables at top-level of conftest.py to e…
chiang-yuan Jun 15, 2026
090b273
test: classify uma family under fairchem test group
chiang-yuan Jun 15, 2026
1062fb6
refactor(deps): restore prefect version pin constraint <3.7.0
chiang-yuan Jun 15, 2026
f264975
ci: restore -n 5 flag to pytest command to run tests in parallel proc…
chiang-yuan Jun 15, 2026
00f1d2e
refactor(deps): pin fastapi<0.115.0 to fix PrefectRouter AttributeErr…
chiang-yuan Jun 15, 2026
fd88b54
ci: disable pytest-xdist and upgrade prefect dependency to avoid fast…
chiang-yuan Jun 15, 2026
eb9a3bf
fix(stability): disable prefect persistence and refresh cache; update…
chiang-yuan Jun 15, 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
29 changes: 16 additions & 13 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,31 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout PR with full history
- name: Checkout code
uses: actions/checkout@v4
Comment on lines +16 to 17

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/ci.yaml

Repository: atomind-ai/mlip-arena

Length of output: 4853


🏁 Script executed:

# Check for security scanning configuration files
find . -maxdepth 2 -type f \( -name ".pre-commit-config.yaml" -o -name ".github/codeql-analysis.yml" -o -name ".github/workflows/*security*" -o -name "*.trivy*" -o -name ".semgrep*" \) 2>/dev/null | head -20

Repository: atomind-ai/mlip-arena

Length of output: 1964


🏁 Script executed:

# Look for any linting configuration that might check workflow files
find . -maxdepth 2 -type f \( -name "actionlint*" -o -name ".actionlintrc*" -o -name ".yamllint*" \) 2>/dev/null

Repository: atomind-ai/mlip-arena

Length of output: 1964


🌐 Web query:

GitHub Actions best practices pinning commit SHA vs version tags security

💡 Result:

In GitHub Actions, pinning to a full-length commit SHA is the recommended security best practice for ensuring the immutability of your CI/CD pipelines [1][2]. The primary differences and security considerations are: Commit SHA Pinning - Security: Provides maximum security by referencing a specific, immutable version of the action [1][3]. Even if a repository is compromised and a tag is moved or deleted, the pinned SHA will continue to resolve to the exact same code that you have verified [4][5]. - Enforcement: Administrators can enforce SHA pinning at the organization or repository level via GitHub Actions policies to ensure all workflows use immutable references [1][2]. - Workflow Maintenance: Because SHAs do not auto-update, you must manually update them or use automated dependency management tools like Dependabot or Renovate [2][4]. A common practice is to use a comment in the workflow file to keep track of the original version, such as uses: owner/repo@sha123... # v1.0.0; tools like Dependabot use this convention to suggest updates while maintaining the security benefit of the pin [4][5]. Version Tag Pinning - Security: Referencing a tag (e.g., @v1) is mutable and carries significant risk [5][3]. If an attacker gains access to the action's repository, they can overwrite the existing tag to point to malicious code, which would immediately affect your workflows [1][5]. - Convenience: It is more convenient for developers as it allows for automatic updates when tags are moved, but it lacks the security guarantees of an immutable reference [1][6]. - Recommendation: Only pin to tags if you fully trust the creator and the source, such as first-party GitHub-owned actions [5][6]. Even then, SHA pinning remains the superior approach for high-security environments [3]. Summary For production and high-security workflows, you should always pin actions to a full 40-character commit SHA [1][5]. This protects against supply chain attacks where a malicious actor alters a previously trusted tag [2][3]. Automated tools can help manage these updates, allowing you to benefit from both security and maintainability [4][5].

Citations:


Pin third-party actions to commit SHAs and disable persisted checkout credentials.

Lines 17 and 20 use floating action version tags (@v4, @v6), which are mutable and can be exploited to compromise your CI pipeline. The checkout actions (lines 17 and 44) also persist credentials by default, increasing token exposure risk. Pin all actions to specific commit SHAs and disable credential persistence on checkout steps.

Suggested hardening diff
    - name: Checkout code
-      uses: actions/checkout@v4
+      uses: actions/checkout@<pinned-commit-sha>
+      with:
+        persist-credentials: false

    - name: Install uv
-      uses: astral-sh/setup-uv@v6
+      uses: astral-sh/setup-uv@<pinned-commit-sha>

Also applies to checkout at line 44 and other floating-tag actions throughout the workflow.

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 16-17: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 17-17: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml around lines 16 - 17, Replace mutable action tags
with specific commit SHAs and disable credential persistence on checkout steps:
update the "Checkout code" step which currently uses "uses: actions/checkout@v4"
(and the other checkout step that uses the same) to reference the exact commit
SHA for actions/checkout and add "persist-credentials: false" to the step;
likewise replace other floating tags (e.g., actions referenced as `@v6/`@v3 etc.)
with their audited commit SHAs so every "uses:" entry is pinned to a specific
SHA to prevent supply-chain tampering.

Source: Linters/SAST tools

with:
fetch-depth: 0

- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
run: uv python install 3.12

- name: Run pre-commit
run: uvx pre-commit run --all-files --show-diff-on-failure
env:
PRE_COMMIT_USE_UV: 1
- name: Run Ruff (Lint & Format)
# This assumes ruff is in your pyproject.toml or you use uvx
run: |
uvx ruff check .
uvx ruff format --check .

test:
needs: lint
runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.11", "3.12", "3.13"]
test-group: ["default", "nequip", "sevennet", "mace", "fairchem"]

steps:
- name: Checkout PR with full history
Expand All @@ -65,7 +63,7 @@ jobs:
swap-size-gb: 12

- name: Install dependencies
run: bash scripts/install-linux.sh
run: bash scripts/install-linux.sh ${{ matrix.test-group }}

- name: List dependencies
run: pip list
Expand All @@ -80,7 +78,12 @@ jobs:
env:
PREFECT_API_KEY: ${{ secrets.PREFECT_API_KEY }}
PREFECT_API_URL: ${{ secrets.PREFECT_API_URL }}
run: pytest -vra -n 5 --dist=loadscope --cov=mlip_arena --cov-report=xml tests
run: |
if [ "${{ matrix.test-group }}" == "default" ]; then
pytest -vra --cov=mlip_arena --cov-report=xml tests -m "not (nequip or sevennet or mace or fairchem)"
else
pytest -vra --cov=mlip_arena --cov-report=xml tests -m ${{ matrix.test-group }}
fi

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
Expand Down
19 changes: 11 additions & 8 deletions benchmarks/asymptotes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
from mlip_arena.flows.eos_bulk import run_db as EOSFlow
from mlip_arena.flows.ev import run_db as EVFlow

from mlip_arena.models import REGISTRY, MLIPEnum


@flow
def asymptotic_behaviors(calculator: str | BaseCalculator, calculator_kwargs: dict | None = None):
Expand All @@ -32,32 +34,33 @@ def asymptotic_behaviors(calculator: str | BaseCalculator, calculator_kwargs: di

if isinstance(calculator, BaseCalculator):
model_name = calculator.__class__.__name__
else:
elif isinstance(calculator, str) and hasattr(MLIPEnum, calculator):
model_name = calculator
else:
raise ValueError(f"Unsupported model: {calculator}")

from mlip_arena.tasks.utils import get_calculator

calc_instance = get_calculator(calculator, calculator_kwargs)
family = REGISTRY[model_name]["family"] if hasattr(MLIPEnum, model_name) else "custom"

# 1. Diatomics
print(f"Starting homonuclear diatomics benchmark for {model_name}...")
out_dir_diatomics = Path(__file__).parent / "diatomics"

out_dir_diatomics = Path(__file__).parent / "diatomics" / family / model_name
homonuclear_diatomics.with_options(name=f"diatomics-{model_name}", task_runner=parent_task_runner)(
model=calc_instance, run_dir=out_dir_diatomics
calculator=calculator, calculator_kwargs=calculator_kwargs, run_dir=out_dir_diatomics
)

# 2. EOS Bulk
print(f"Starting EOS bulk benchmark for {model_name}...")
out_dir_eos = Path(__file__).parent / "eos_bulk"
EOSFlow.with_options(name=f"eos_bulk-{model_name}", task_runner=parent_task_runner)(
model=calc_instance, run_dir=out_dir_eos, dataset_file="wbm_subset.db"
calculator=calculator, calculator_kwargs=calculator_kwargs, run_dir=out_dir_eos, dataset_file="wbm_subset.db"
)

# 3. E-V
print(f"Starting E-V scan benchmark for {model_name}...")
out_dir_ev = Path(__file__).parent / "ev"
EVFlow.with_options(name=f"ev-{model_name}", task_runner=parent_task_runner)(
model=calc_instance, run_dir=out_dir_ev, dataset_file="wbm_subset.db"
calculator=calculator, calculator_kwargs=calculator_kwargs, run_dir=out_dir_ev, dataset_file="wbm_subset.db"
)


Expand Down
117 changes: 117 additions & 0 deletions benchmarks/stability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from __future__ import annotations

from pathlib import Path

from ase.calculators.calculator import BaseCalculator
from loguru import logger
from prefect import flow
from prefect.context import FlowRunContext

from mlip_arena.flows.stability import compression, heating
from mlip_arena.models import REGISTRY, MLIPEnum
from mlip_arena.tasks.stability.analysis import gather_results
from mlip_arena.tasks.stability.data import get_atoms_from_db


@flow
def random_mixtures(calculator: str | BaseCalculator, calculator_kwargs: dict | None = None):
ctx = FlowRunContext.get()
parent_task_runner = ctx.task_runner if ctx else None

if isinstance(calculator, BaseCalculator):
model_name = calculator.__class__.__name__
model = calculator
family = "custom"
elif isinstance(calculator, str) and hasattr(MLIPEnum, calculator):
model_name = calculator
model = MLIPEnum[calculator]
family = REGISTRY[model_name]["family"]
else:
raise ValueError(f"Unsupported model: {calculator}")

run_dir = Path(__file__).parent / "stability" / family

# 1. Run heating
print(f"Starting stability heating simulation for {model_name}...")
heating.with_options(
name=f"stability-heating-{model_name}",
task_runner=parent_task_runner,
log_prints=True,
)(model, run_dir)

# 2. Run compression
print(f"Starting stability compression simulation for {model_name}...")
compression.with_options(
name=f"stability-compression-{model_name}",
task_runner=parent_task_runner,
log_prints=True,
)(model, run_dir)

# 3. Analysis / gather results
print(f"Analyzing stability results for {model_name}...")
compositions = []
for atoms in get_atoms_from_db("random-mixture.db"):
if len(atoms) == 0:
continue
compositions.append(atoms.get_chemical_formula())

try:
df = gather_results(run_dir, prefix=model_name, run_type="nvt")
df = df[df["formula"].isin(compositions[:120])].copy() # tentatively we only take the first 120 structures
assert len(df) > 0
df.to_parquet(run_dir / f"{model_name}-heating.parquet", index=False)
except Exception as e:
logger.warning(f"Error processing model {model_name} (heating): {e}")

try:
df = gather_results(run_dir, prefix=model_name, run_type="npt")
df = df[df["formula"].isin(compositions[:80])].copy() # tentatively we only take the first 80 structures
assert len(df) > 0
df.to_parquet(run_dir / f"{model_name}-compression.parquet", index=False)
except Exception as e:
logger.warning(f"Error processing model {model_name} (compression): {e}")


if __name__ == "__main__":
from dask.distributed import Client
from dask_jobqueue import SLURMCluster
from prefect_dask import DaskTaskRunner

nodes_per_alloc = 1
gpus_per_alloc = 4

cluster_kwargs = dict(
cores=1,
memory="64 GB",
processes=1,
shebang="#!/bin/bash",
account="matgen",
walltime="04:00:00",
job_mem="0",
job_script_prologue=[
"source ~/.bashrc",
"module load python",
"source activate /pscratch/sd/c/cyrusyc/.conda/mlip-arena",
],
job_directives_skip=["-n", "--cpus-per-task", "-J"],
job_extra_directives=[
"-J arena-stability",
"-q preempt",
"--time-min=00:30:00",
"--comment=12:00:00",
f"-N {nodes_per_alloc}",
"-C gpu",
f"-G {gpus_per_alloc}",
],
)

cluster = SLURMCluster(**cluster_kwargs)
print(cluster.job_script())
cluster.adapt(minimum_jobs=10, maximum_jobs=50)
client = Client(cluster)

for model in MLIPEnum:
random_mixtures.with_options(
task_runner=DaskTaskRunner(address=client.scheduler.address),
log_prints=True,
)(model.name)
42 changes: 0 additions & 42 deletions benchmarks/stability/analysis.py

This file was deleted.

60 changes: 0 additions & 60 deletions benchmarks/stability/run.py

This file was deleted.

18 changes: 14 additions & 4 deletions benchmarks/submit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
from dask.distributed import Client
from dask_jobqueue import SLURMCluster
from prefect_dask import DaskTaskRunner
from shifts import distribution_shifts
from stability import random_mixtures

# ==============================================================================
# 1. JOB CONFIGURATION
# ==============================================================================

# Example A: Registered string model (e.g., "MACE-MP(M)", "CHGNet")
calculator = "NequIP-OAM-L"
calculator = "PET-OAM"

# Example B: Custom ASE Calculator class and arguments (e.g., custom MLIP)
# from ase.calculators.lj import LennardJones
Expand Down Expand Up @@ -77,7 +77,17 @@
# calculator_kwargs=calculator_kwargs # Uncomment for custom ASE Calculator class
)

distribution_shifts.with_options(
# distribution_shifts.with_options(
# task_runner=DaskTaskRunner(address=client.scheduler.address),
# log_prints=True,
# persist_result=False,
# )(
# calculator=calculator,
# # calculator_kwargs=calculator_kwargs # Uncomment for custom ASE Calculator class
# )


random_mixtures.with_options(
task_runner=DaskTaskRunner(address=client.scheduler.address),
log_prints=True,
persist_result=False,
Expand All @@ -86,4 +96,4 @@
# calculator_kwargs=calculator_kwargs # Uncomment for custom ASE Calculator class
)

# TODO: Add Stability and Reactivity
# TODO: Add Reactivity
Loading
Loading