Skip to content

Refactor CI test environments / Add PET models - #90

Merged
chiang-yuan merged 1 commit into
mainfrom
develop
Jun 15, 2026
Merged

Refactor CI test environments / Add PET models#90
chiang-yuan merged 1 commit into
mainfrom
develop

Conversation

@chiang-yuan

@chiang-yuan chiang-yuan commented Jun 15, 2026

Copy link
Copy Markdown
Member

Organize model dependency groups in pyproject.toml and split pytest groups.

This PR also includes PET models in the dependency and will close #78 first. We will include benchmark results in a separate PR.

Summary by CodeRabbit

  • New Features

    • Added support for PET (PET-OAM and PET-MAD) calculator models.
    • Enhanced stability workflow with improved result aggregation and analysis capabilities.
  • Bug Fixes

    • Simplified ANI2x model implementation for improved reliability.
  • Chores

    • Updated minimum Python version requirement to 3.11+.
    • Refactored benchmark infrastructure and test matrix for better coverage across Python versions.
    • Reorganized internal stability workflow structure for maintainability.

* Add PET-OAM

* Apply suggestion from review

* Apply one more suggestion

* Add PET-MAD

* Add package to optional dependencies

* adjust deps group

* ruff format

* bump versions; fix tests

* remove 3.10, add 3.13

* fix diatomics output path

* move stability flow under mlip_arena.flows and benchmarks/stability.py

* rollback dependencies

* Refactor CI test environments to split conflicting models

* Refactor CI test dependencies: move model deps to optional group 'all' and revise CI install scripts

* ci: disable fail-fast in test matrix strategy

* ci: exclude python 3.13 for GNN/compiled model test groups and skip torch-scatter/torch-sparse compilation

* Refactor: remove torch_geometric and torch_scatter dependencies, implement local scatter helpers, and mark MACE test workflows

* move torch_dftd and matscipy to base dep

* Fix default CI test marker selection

* refactor: apply PyTorch/e3nn compatibility patches at model calculator level

* refactor: consolidate asymptotes and shifts benchmarks into submit.py

* refactor: implement modular model-level reporting and refactor stability aggregation

* refactor: extract model name and calculator resolution to DRY helpers in stability.py

* refactor: simplify calculator resolution and restrict e3nn monkey-patch to import phase

* test stability benchmark run

* move stability from tasks to flows

* handle cache separately for each test group in CI

* test external calculators in different groups separately in CI

* mark fairchem uma as xfail

* separate orb-models into a separate test group and allow version <0.8.0

* add to() method to custom Data class in collate.py

* fix parameter binding for EOSFlow and EVFlow in submit.py

* pin orb-models to 0.4.0 in pyproject.toml

* gracefully handle pytest exit code 5 (no tests collected) in CI

* set torch to 2.2 for orb test group and exclude python 3.13 in CI

* monkeypatch typing.io in nequip model and exclude orb test group on py3.13

* remove e3nn optimization defaults workaround in nequip.py

* remove nequip patch and exclude nequip+py3.13 in CI

* pin orb-models to 0.5.5 and update torch dep to 2.8 in install-linux.sh

* increase test coverage

* disable cache

* refactor(deps): loosen prefect constraint and comment out ipykernel/ipywidgets

* ci: disable caching in setup-uv step

* test: switch prefect tests skip condition from python 3.11 to 3.12

* test: run test_stability.py flows inside prefect_test_harness on python 3.12

* test: isolate PREFECT_HOME in conftest.py to prevent concurrent database lock race conditions

* ci: add Free Disk Space step and update fairchem-core version to 2.21.0 in pyproject.toml

* test: remove xfail handler for UMA-S-1P1 to enable testing on CPU in CI

* test: set PREFECT_HOME env variables at top-level of conftest.py to ensure isolation before prefect is imported

* test: classify uma family under fairchem test group

* refactor(deps): restore prefect version pin constraint <3.7.0

* ci: restore -n 5 flag to pytest command to run tests in parallel processes

* refactor(deps): pin fastapi<0.115.0 to fix PrefectRouter AttributeError in Prefect < 3.7.0

* ci: disable pytest-xdist and upgrade prefect dependency to avoid fastapi pin

* fix(stability): disable prefect persistence and refresh cache; update prefect/fastapi constraints

---------

Co-authored-by: Filippo Bigi <98903385+frostedoyster@users.noreply.github.com>
Co-authored-by: frostedoyster <bigi.f@libero.it>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds PET-OAM and PET-MAD calculator models to the registry, consolidates stability task utilities from mlip_arena/tasks/stability/ into mlip_arena/flows/stability.py (removing three task modules), removes torch_geometric/torch_scatter dependencies by introducing local replacements, threads calculator_kwargs through all flow APIs, overhauls CI to use uv/ruff with a per-model-family grouped test matrix, and updates test infrastructure with isolated Prefect environments and new utility/data tests.

Changes

MLIP Arena — PET models, stability consolidation, dependency removal, CI overhaul

Layer / File(s) Summary
CI/packaging overhaul: uv, ruff, grouped test matrix
pyproject.toml, scripts/install-linux.sh, .github/workflows/ci.yaml
pyproject.toml raises Python floor to >=3.11, updates dependency constraints (prefect, fastapi, matscipy), reworks optional extras, and adds [tool.pytest.ini_options] with per-family markers. install-linux.sh gains GROUP-based branching for different Torch/CUDA versions. CI replaces pre-commit with ruff via uvx, adds a test-group matrix over Python 3.11–3.13, and uses marker-based pytest with exit-code-5 success handling.
Remove torch_geometric/torch_scatter: local Data class and scatter helpers
mlip_arena/data/collate.py, mlip_arena/models/classicals/zbl.py
collate.py replaces the torch_geometric.data.Data import with a local Data class providing from_dict, detach/cpu/to, and __contains__. zbl.py removes torch_scatter and defines local scatter_add/scatter_sum helpers via Tensor.scatter_add_, conditionally sets compile_mode, and updates stress/energy aggregation calls.
New PET calculators, ANI2x simplification, NequIP slice fix, registry updates
mlip_arena/models/externals/pet.py, mlip_arena/models/externals/ani.py, mlip_arena/models/externals/nequip.py, mlip_arena/models/registry.yaml
pet.py adds PET_OAM and PET_MAD as UPETCalculator subclasses. ANI2x is rewritten as a __new__ factory returning torchani.models.ANI2x(...).ase(). nequip.py adds torch.serialization.add_safe_globals([slice]). Registry gains full PET-OAM and PET-MAD entries and marks a prior model deprecated: true.
Thread calculator_kwargs through diatomics, EOS bulk, and benchmark submit
mlip_arena/flows/diatomics.py, mlip_arena/flows/eos_bulk.py, benchmarks/submit.py
homonuclear_diatomic task and homonuclear_diatomics flow accept calculator_kwargs and forward it to get_calculator. EOS bulk run and run_db replace the model parameter with calculator plus optional calculator_kwargs. benchmarks/submit.py threads calculator_kwargs into all three asymptotic_behaviors flow calls.
Consolidate stability utilities into flows/stability.py; remove task modules
mlip_arena/flows/stability.py, mlip_arena/tasks/stability/..., benchmarks/stability/aggregate.py, benchmarks/submit.py
flows/stability.py gains save_to_db, get_atoms_from_db, get_runtime_stats, and gather_results (consolidated from deleted tasks/stability/data.py and analysis.py), and sets persist_result=False/refresh_cache=True on heating/compression submissions. tasks/stability/flow.py, data.py, and analysis.py are deleted. benchmarks/stability/run.py is removed; aggregate.py updates its import path; benchmarks/submit.py adds a parquet-writing post-processing section.
Leaderboard filtering by benchmark file presence
serve/ranks/homonuclear-diatomics.py, serve/ranks/combustion.py, benchmarks/diatomics/nequip/NequIP-OAM-L.json
homonuclear-diatomics.py extends valid_models to require the per-model JSON file exists under benchmarks/diatomics/. combustion.py reformats its comprehension to multi-line. The NequIP-OAM-L.json LFS pointer is updated.
Test infrastructure: isolated Prefect env, new utility and data tests
tests/conftest.py, tests/test_utils.py, tests/test_data_local.py
conftest.py sets isolated PREFECT_HOME and SQLite DB per xdist worker. test_utils.py adds MockCalculator and tests for get_freer_device (CUDA/MPS/CPU), get_calculator (all dispatch paths, dispersion), and _calculator_key_fn. test_data_local.py adds basic and concurrency tests for SafeHDFStore.
Update existing tests: Python 3.12 skip conditions and model family markers
tests/test_elasticity.py, tests/test_eos.py, tests/test_external_calculators.py, tests/test_md.py, tests/test_mof.py, tests/test_neb.py, tests/test_stability.py, tests/test_data_integrity.py
All skipif conditions updated from != (3, 11) to != (3, 12). MACE-dependent tests gain pytest.mark.mace. test_external_calculators builds model_params with per-family markers and narrows exception skip list. test_data_integrity filters expected models by benchmark file presence. test_stability wraps flows in prefect_test_harness().

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • atomind-ai/mlip-arena#77: Modifies the same homonuclear_diatomic/homonuclear_diatomics task and flow API in mlip_arena/flows/diatomics.py, directly related to the calculator_kwargs propagation changes here.
  • atomind-ai/mlip-arena#87: Modifies the same .github/workflows/ci.yaml and scripts/install-linux.sh files for uv/ruff linting and test-group-based pytest matrix execution.
  • atomind-ai/mlip-arena#88: Refactors benchmarks/submit.py and mlip_arena/flows/stability.py heating/compression workflow logic, directly overlapping with the stability consolidation changes here.

Suggested labels

enhancement

🐇 A rabbit hops in with a bundle of news,
PET-OAM and PET-MAD, two models to choose!
Scatter helpers local, no torch_geo in sight,
Stability flows consolidated just right.
CI now runs ruff with a matrix so grand,
Calculator kwargs passed by every hand! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.21% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Refactor CI test environments / Add PET models' accurately reflects the two main categories of changes: CI/test environment refactoring and addition of new PET models.
Linked Issues check ✅ Passed The PR fully implements issue #78 requirements: adds PET-OAM and PET-MAD models with energy/forces/stress support, automatic device selection, discoverable/selectable in registry, and treats them as optional dependencies via packaging.
Out of Scope Changes check ✅ Passed While the PR scope extends beyond issue #78 to include significant CI refactoring and architectural changes (removing torch_geometric, consolidating stability workflows, updating test infrastructure), these changes align with the PR objectives and commit messages indicating a larger modernization effort.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.4.16)
benchmarks/diatomics/nequip/NequIP-OAM-L.json

File contains syntax errors that prevent linting: Line 1: String values must be double quoted.; Line 1: String values must be double quoted.; Line 1: End of file expected; Line 3: End of file expected


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.76271% with 96 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
mlip_arena/flows/stability.py 19.10% 72 Missing ⚠️
mlip_arena/data/collate.py 66.66% 8 Missing ⚠️
mlip_arena/flows/diatomics.py 0.00% 8 Missing ⚠️
mlip_arena/flows/eos_bulk.py 0.00% 8 Missing ⚠️
Files with missing lines Coverage Δ
mlip_arena/models/classicals/zbl.py 85.22% <100.00%> (+3.28%) ⬆️
mlip_arena/models/externals/ani.py 100.00% <100.00%> (+62.50%) ⬆️
mlip_arena/models/externals/nequip.py 100.00% <100.00%> (+75.00%) ⬆️
mlip_arena/models/externals/pet.py 100.00% <100.00%> (ø)
mlip_arena/data/collate.py 76.28% <66.66%> (-3.45%) ⬇️
mlip_arena/flows/diatomics.py 0.00% <0.00%> (ø)
mlip_arena/flows/eos_bulk.py 0.00% <0.00%> (ø)
mlip_arena/flows/stability.py 48.97% <19.10%> (-46.34%) ⬇️

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_external_calculators.py (1)

38-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle ImportError from MLIPEnum.load() in the skip path.

MLIPEnum.load() can raise ImportError for missing optional model packages, but this branch now falls into the generic Exception handler and fails the test instead of skipping. That makes this test brittle in dependency-grouped environments.

Proposed fix
     except (
+        ImportError,
         GatedRepoError,
         HTTPError,
         HTTPStatusError,
         FileNotFoundError,
     ) as e:
         pytest.skip(str(e))
🤖 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 `@tests/test_external_calculators.py` around lines 38 - 53, The ImportError
exception raised by MLIPEnum.load() for missing optional model packages is not
being caught in the skip path and instead falls through to the generic Exception
handler, causing the test to fail or xfail instead of being skipped. Add
ImportError to the except clause that currently catches GatedRepoError,
HTTPError, HTTPStatusError, and FileNotFoundError so that ImportError is also
handled in the skip path with pytest.skip().
🧹 Nitpick comments (1)
mlip_arena/models/classicals/zbl.py (1)

18-32: 💤 Low value

Consider documenting the dim default difference from torch_scatter.

The implementation is correct. Note that the default dim=0 differs from torch_scatter.scatter_add's default of dim=-1. Since all current call sites explicitly pass dim=0, this isn't an issue now, but could surprise future callers expecting torch_scatter-compatible defaults.

A brief docstring noting this local function's contract would help maintainability.

🤖 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 `@mlip_arena/models/classicals/zbl.py` around lines 18 - 32, Add a docstring to
the scatter_add function to document its behavior and clarify that the default
parameter dim=0 differs from torch_scatter.scatter_add (which defaults to
dim=-1). The docstring should explain what the function does, document the
parameters (src, index, dim, dim_size), and note this behavioral difference to
help future maintainers and callers understand the local implementation's
contract.
🤖 Prompt for all review comments with 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.

Inline comments:
In @.github/workflows/ci.yaml:
- Around line 16-17: Replace the mutable version tag `@v4` in the actions/checkout
step with a full commit SHA to ensure supply-chain security and consistency.
Additionally, add persist-credentials: false to the checkout step to prevent
GitHub tokens from being passed to subsequent workflow steps. After fixing the
checkout step, audit all other action uses entries in the workflow file
(including any `@v6`, `@v5`, `@v1.0` references) and replace their mutable version
tags with specific commit SHAs as well.

In `@mlip_arena/flows/diatomics.py`:
- Around line 26-28: The `calculator_kwargs` parameter in both the
`homonuclear_diatomic` function (at lines 26-28) and the corresponding function
at lines 259-261 should be optional with a default value of None. Change the
parameter signature from `calculator_kwargs: dict | None` to `calculator_kwargs:
dict | None = None` in both function definitions to make the parameter truly
optional and maintain consistency with the optional-kwargs contract used
elsewhere in the codebase.

In `@mlip_arena/flows/eos_bulk.py`:
- Line 83: The line 83 in the eos_bulk.py file silently ignores
calculator_kwargs when calculator is already a BaseCalculator instance, which
can lead to misleading behavior. Add validation to check if calculator is a
BaseCalculator instance and calculator_kwargs is provided; if both conditions
are true, raise an error to fail fast and alert the user that they cannot pass
calculator_kwargs with an already-instantiated BaseCalculator. If calculator is
a BaseCalculator instance without calculator_kwargs, use it as-is; otherwise,
call get_calculator with the provided kwargs to instantiate a new calculator.

In `@mlip_arena/flows/stability.py`:
- Around line 196-205: The download logic in the stability.py file is
conditioned only on whether the database file exists locally, completely
ignoring the force_download parameter. Modify the condition that guards the
hf_hub_download call to also check the force_download flag, so that the download
executes when either the file does not exist OR when force_download is True.
This ensures that callers can force a fresh download of the database by passing
force_download=True, even if a local copy already exists.
- Line 295: The code accessing target_steps uses off-by-one indexing with
traj[1], which causes an IndexError when the trajectory has only a single frame
(only index 0 exists), resulting in the file being silently dropped during
processing. Change the indexing from traj[1] to traj[0] in the line that reads
target_steps = traj[1].info["target_steps"] to correctly access the first frame
of the trajectory instead of attempting to access a second frame that may not
exist.

In `@mlip_arena/models/externals/ani.py`:
- Around line 8-11: The __new__ method accepts **kwargs but does not forward
them to the model.ase() call on the return statement. Update the return
statement to pass **kwargs to the ase() method so that callers can configure
parameters like overwrite and stress_kind as intended.

In `@mlip_arena/models/registry.yaml`:
- Around line 441-446: The PET-MAD checkpoint name in the registry.yaml file
declares pet-mad-s-v1.0.2.ckpt while the version hardcoded in
mlip_arena/models/externals/pet.py at line 30 shows version="1.5.0", creating a
provenance mismatch. Update the checkpoint value in the PET-MAD registry entry
to align with the actual version string from the pet.py file, ensuring the
declared checkpoint artifact metadata matches the runtime model version being
loaded.
- Line 391: The DeepMD entry in the model registry has a deprecated flag set to
true, but it lacks an explanatory comment documenting the reason for
deprecation, unlike other deprecated entries such as ORB which include
descriptive comments. Add a comment next to the deprecated: true flag in the
DeepMD entry explaining the reason for its deprecation to maintain consistency
with other deprecated model entries and improve maintainability.

In `@scripts/install-linux.sh`:
- Line 7: The extras specifiers in the `uv pip install` commands are not quoted,
which allows the shell to interpret the square brackets `[]` as glob character
classes. Quote each extras argument (the part containing `.[test,extra,nequip]`
and similar patterns) by wrapping it in single or double quotes so that the
brackets are passed literally to `uv pip` rather than being expanded as shell
glob patterns. This applies to all occurrences of the `uv pip install` command
where extras are specified with unquoted bracket notation.

In `@tests/conftest.py`:
- Around line 8-12: Replace the deterministic temp_dir path construction
(currently using tempfile.gettempdir() with worker_id) with tempfile.mkdtemp()
to generate a unique directory for each test run, ensuring test isolation and
preventing stale Prefect state leakage from previous runs. Update the temp_dir
assignment to use mkdtemp(), which will automatically create a unique temporary
directory and eliminate the reuse of old prefect.db content across test runs.

---

Outside diff comments:
In `@tests/test_external_calculators.py`:
- Around line 38-53: The ImportError exception raised by MLIPEnum.load() for
missing optional model packages is not being caught in the skip path and instead
falls through to the generic Exception handler, causing the test to fail or
xfail instead of being skipped. Add ImportError to the except clause that
currently catches GatedRepoError, HTTPError, HTTPStatusError, and
FileNotFoundError so that ImportError is also handled in the skip path with
pytest.skip().

---

Nitpick comments:
In `@mlip_arena/models/classicals/zbl.py`:
- Around line 18-32: Add a docstring to the scatter_add function to document its
behavior and clarify that the default parameter dim=0 differs from
torch_scatter.scatter_add (which defaults to dim=-1). The docstring should
explain what the function does, document the parameters (src, index, dim,
dim_size), and note this behavioral difference to help future maintainers and
callers understand the local implementation's contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9f8f35f7-7479-4d83-94a9-cfca7c3ce19e

📥 Commits

Reviewing files that changed from the base of the PR and between f40153e and e793ceb.

📒 Files selected for processing (34)
  • .github/workflows/ci.yaml
  • benchmarks/diatomics/nequip/NequIP-OAM-L.json
  • benchmarks/stability/aggregate.py
  • benchmarks/stability/run.py
  • benchmarks/submit.py
  • mlip_arena/data/collate.py
  • mlip_arena/flows/diatomics.py
  • mlip_arena/flows/eos_bulk.py
  • mlip_arena/flows/stability.py
  • mlip_arena/models/classicals/zbl.py
  • mlip_arena/models/externals/ani.py
  • mlip_arena/models/externals/nequip.py
  • mlip_arena/models/externals/pet.py
  • mlip_arena/models/registry.yaml
  • mlip_arena/tasks/stability/__init__.py
  • mlip_arena/tasks/stability/analysis.py
  • mlip_arena/tasks/stability/data.py
  • mlip_arena/tasks/stability/flow.py
  • pyproject.toml
  • scripts/install-linux.sh
  • scripts/install-perlmutter.sh
  • serve/ranks/combustion.py
  • serve/ranks/homonuclear-diatomics.py
  • tests/conftest.py
  • tests/test_data_integrity.py
  • tests/test_data_local.py
  • tests/test_elasticity.py
  • tests/test_eos.py
  • tests/test_external_calculators.py
  • tests/test_md.py
  • tests/test_mof.py
  • tests/test_neb.py
  • tests/test_stability.py
  • tests/test_utils.py
💤 Files with no reviewable changes (6)
  • scripts/install-perlmutter.sh
  • mlip_arena/tasks/stability/flow.py
  • mlip_arena/tasks/stability/init.py
  • mlip_arena/tasks/stability/data.py
  • mlip_arena/tasks/stability/analysis.py
  • benchmarks/stability/run.py

Comment thread .github/workflows/ci.yaml
Comment on lines +16 to 17
- name: Checkout code
uses: actions/checkout@v4

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify workflow action pinning and checkout credential persistence settings.
# Expected:
# 1) Every "uses:" line should end with a full commit SHA.
# 2) Each actions/checkout step should explicitly set persist-credentials: false.

echo "== uses entries =="
rg -n '^\s*uses:\s*' .github/workflows/ci.yaml

echo
echo "== checkout blocks (with nearby context) =="
rg -n -C3 'actions/checkout@' .github/workflows/ci.yaml

echo
echo "== persist-credentials settings =="
rg -n 'persist-credentials' .github/workflows/ci.yaml

Repository: atomind-ai/mlip-arena

Length of output: 773


Pin workflow actions to commit SHAs and add persist-credentials: false to checkout steps.

Multiple actions use mutable version tags (@v4, @v6, @v5, @v1.0) instead of immutable commit SHAs, creating supply-chain risk. Additionally, checkout steps lack persist-credentials: false, leaving GitHub tokens available to all subsequent steps. Audit and pin all uses: entries to full commit SHAs and set persist-credentials: false for checkout steps.

🧰 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 the mutable version
tag `@v4` in the actions/checkout step with a full commit SHA to ensure
supply-chain security and consistency. Additionally, add persist-credentials:
false to the checkout step to prevent GitHub tokens from being passed to
subsequent workflow steps. After fixing the checkout step, audit all other
action uses entries in the workflow file (including any `@v6`, `@v5`, `@v1.0`
references) and replace their mutable version tags with specific commit SHAs as
well.

Source: Linters/SAST tools

Comment on lines +26 to +28
def homonuclear_diatomic(
symbol: str, calculator: str | MLIPEnum | BaseCalculator, calculator_kwargs: dict | None, out_dir: Path
):

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 | 🟠 Major | ⚡ Quick win

Make calculator_kwargs optional in both public signatures.

Line 27 and Line 260 currently require calculator_kwargs, which introduces a breaking API change and contradicts the optional-kwargs contract used elsewhere in this PR.

Proposed fix
 def homonuclear_diatomic(
-    symbol: str, calculator: str | MLIPEnum | BaseCalculator, calculator_kwargs: dict | None, out_dir: Path
+    symbol: str,
+    calculator: str | MLIPEnum | BaseCalculator,
+    out_dir: Path,
+    calculator_kwargs: dict | None = None,
 ):
@@
 def homonuclear_diatomics(
-    calculator: BaseCalculator | str, calculator_kwargs: dict | None, run_dir: Path | None = None
+    calculator: BaseCalculator | str,
+    run_dir: Path | None = None,
+    calculator_kwargs: dict | None = None,
 ):

Also applies to: 259-261

🤖 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 `@mlip_arena/flows/diatomics.py` around lines 26 - 28, The `calculator_kwargs`
parameter in both the `homonuclear_diatomic` function (at lines 26-28) and the
corresponding function at lines 259-261 should be optional with a default value
of None. Change the parameter signature from `calculator_kwargs: dict | None` to
`calculator_kwargs: dict | None = None` in both function definitions to make the
parameter truly optional and maintain consistency with the optional-kwargs
contract used elsewhere in the codebase.

pd.DataFrame: A DataFrame containing the raw EOS results.
"""
calculator = model if isinstance(model, BaseCalculator) else get_calculator(model)
calculator = calculator if isinstance(calculator, BaseCalculator) else get_calculator(calculator, calculator_kwargs)

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 | 🟡 Minor | ⚡ Quick win

Avoid silently ignoring calculator_kwargs for calculator instances.

Line 83 drops calculator_kwargs when calculator is already a BaseCalculator instance. With the new parameter exposed, this should fail fast to prevent misleading runs.

Proposed fix
-    calculator = calculator if isinstance(calculator, BaseCalculator) else get_calculator(calculator, calculator_kwargs)
+    if isinstance(calculator, BaseCalculator):
+        if calculator_kwargs:
+            raise ValueError("`calculator_kwargs` cannot be used with a calculator instance.")
+    else:
+        calculator = get_calculator(calculator, calculator_kwargs)
🤖 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 `@mlip_arena/flows/eos_bulk.py` at line 83, The line 83 in the eos_bulk.py file
silently ignores calculator_kwargs when calculator is already a BaseCalculator
instance, which can lead to misleading behavior. Add validation to check if
calculator is a BaseCalculator instance and calculator_kwargs is provided; if
both conditions are true, raise an error to fail fast and alert the user that
they cannot pass calculator_kwargs with an already-instantiated BaseCalculator.
If calculator is a BaseCalculator instance without calculator_kwargs, use it
as-is; otherwise, call get_calculator with the provided kwargs to instantiate a
new calculator.

Comment on lines +196 to +205
if not db_path.exists():
db_path = hf_hub_download(
repo_id=repo_id,
repo_type=repo_type,
subfolder=subfolder,
# local_dir=db_path.parent,
filename=db_path.name,
token=hf_token,
force_download=force_download,
)

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 | 🟡 Minor | ⚡ Quick win

force_download is currently ignored when the local DB file exists.

On Line 196, the download branch only runs when the file is missing, so callers passing force_download=True still read stale local data.

Proposed fix
-    if not db_path.exists():
+    if force_download or not db_path.exists():
         db_path = hf_hub_download(
             repo_id=repo_id,
             repo_type=repo_type,
             subfolder=subfolder,
             # local_dir=db_path.parent,
             filename=db_path.name,
             token=hf_token,
             force_download=force_download,
         )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not db_path.exists():
db_path = hf_hub_download(
repo_id=repo_id,
repo_type=repo_type,
subfolder=subfolder,
# local_dir=db_path.parent,
filename=db_path.name,
token=hf_token,
force_download=force_download,
)
if force_download or not db_path.exists():
db_path = hf_hub_download(
repo_id=repo_id,
repo_type=repo_type,
subfolder=subfolder,
# local_dir=db_path.parent,
filename=db_path.name,
token=hf_token,
force_download=force_download,
)
🤖 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 `@mlip_arena/flows/stability.py` around lines 196 - 205, The download logic in
the stability.py file is conditioned only on whether the database file exists
locally, completely ignoring the force_download parameter. Modify the condition
that guards the hf_hub_download call to also check the force_download flag, so
that the download executes when either the file does not exist OR when
force_download is True. This ensures that callers can force a fresh download of
the database by passing force_download=True, even if a local copy already
exists.

total_time_seconds += block_time.total_seconds()
total_steps += steps[indices][-1] - steps[indices][0]

target_steps = traj[1].info["target_steps"]

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 | 🟡 Minor | ⚡ Quick win

target_steps indexing is off-by-one and can crash for short trajectories.

Line 295 reads traj[1]; a valid single-frame trajectory raises IndexError, and this silently drops the file in gather_results.

Proposed fix
-    target_steps = traj[1].info["target_steps"]
+    target_steps = traj[0].info.get("target_steps", steps[-1] if len(steps) else 0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
target_steps = traj[1].info["target_steps"]
target_steps = traj[0].info.get("target_steps", steps[-1] if len(steps) else 0)
🤖 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 `@mlip_arena/flows/stability.py` at line 295, The code accessing target_steps
uses off-by-one indexing with traj[1], which causes an IndexError when the
trajectory has only a single frame (only index 0 exists), resulting in the file
being silently dropped during processing. Change the indexing from traj[1] to
traj[0] in the line that reads target_steps = traj[1].info["target_steps"] to
correctly access the first frame of the trajectory instead of attempting to
access a second frame that may not exist.

Comment on lines +8 to +11
def __new__(cls, device: str | None = None, **kwargs):
device = device or str(get_freer_device())
model = torchani.models.ANI2x().to(device)
return model.ase()

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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For torchani==2.2.4, what keyword arguments are supported by ANI2x().ase(), and are they intended to be passed through from wrapper constructors?

💡 Result:

In torchani 2.2.4 (consistent with the 2.2.x series), the ase method on an ANI model instance (like ANI2x) serves as a convenience wrapper to instantiate the torchani.ase.Calculator class [1][2]. The keyword arguments supported by the ase method are passed directly to the constructor of the underlying torchani.ase.Calculator class [1][3]. While specific signatures can evolve across versions, common keyword arguments include: 1. overwrite (bool): Determines whether to replace original positions in the ASE Atoms object with wrapped positions after periodic boundary condition handling (defaults to False) [1][3]. 2. stress_kind (str): Defines the strategy for stress calculation; valid options typically include 'scaling', 'fdotr', and 'numerical' (defaulting to 'scaling') [1][3]. Additionally, users may pass other arguments (such as dtype) that the implementation may handle during the calculator setup process [4]. Because the ase method acts as a factory for the calculator, these arguments are explicitly intended to be passed through to configure the resulting ASE calculator instance [1][3]. Always check the documentation specific to your local version by inspecting the docstring of the method directly if in doubt, as internal argument handling can vary slightly between minor versions [1][2].

Citations:


🏁 Script executed:

# First, let's locate and examine the actual file
fd -t f "ani.py" | head -20

Repository: atomind-ai/mlip-arena

Length of output: 100


🏁 Script executed:

# Also check the structure to understand the file better
cat -n mlip_arena/models/externals/ani.py | head -50

Repository: atomind-ai/mlip-arena

Length of output: 450


🏁 Script executed:

# Get the full context of the file
cat mlip_arena/models/externals/ani.py

Repository: atomind-ai/mlip-arena

Length of output: 373


🏁 Script executed:

# Check for similar wrapper patterns in the externals directory
ls -la mlip_arena/models/externals/

Repository: atomind-ai/mlip-arena

Length of output: 1181


🏁 Script executed:

# Look at other model files to understand the pattern
head -30 mlip_arena/models/externals/*.py

Repository: atomind-ai/mlip-arena

Length of output: 13121


Forward kwargs to model.ase().

The constructor accepts **kwargs but silently discards them at line 11. The ase() method explicitly supports keyword arguments like overwrite and stress_kind that are intended to be configurable by callers. Update the return statement to forward these arguments.

Proposed fix
 class ANI2x:
     def __new__(cls, device: str | None = None, **kwargs):
         device = device or str(get_freer_device())
         model = torchani.models.ANI2x().to(device)
-        return model.ase()
+        return model.ase(**kwargs)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __new__(cls, device: str | None = None, **kwargs):
device = device or str(get_freer_device())
model = torchani.models.ANI2x().to(device)
return model.ase()
def __new__(cls, device: str | None = None, **kwargs):
device = device or str(get_freer_device())
model = torchani.models.ANI2x().to(device)
return model.ase(**kwargs)
🤖 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 `@mlip_arena/models/externals/ani.py` around lines 8 - 11, The __new__ method
accepts **kwargs but does not forward them to the model.ase() call on the return
statement. Update the return statement to pass **kwargs to the ase() method so
that callers can configure parameters like overwrite and stress_kind as
intended.

nvt: true
npt: true
license:
deprecated: true

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'DeepMD:|deprecated:\s*true' mlip_arena/models/registry.yaml
rg -n -C5 'meta\.get\("deprecated",\s*False\)|Skipping deprecated models|members\[model_name\]' mlip_arena/models/__init__.py

Repository: atomind-ai/mlip-arena

Length of output: 1880


Add explanatory comment for DeepMD deprecation.

The deprecated: true flag at line 391 is intentional and correctly excludes DeepMD from model registration via the deprecation check in __init__.py (lines 63–69). However, unlike other deprecated entries (e.g., ORB: "Superseded by ORBv2"), DeepMD's deprecation lacks an explanatory comment. Add a comment documenting the reason for deprecation for consistency and maintainability.

🤖 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 `@mlip_arena/models/registry.yaml` at line 391, The DeepMD entry in the model
registry has a deprecated flag set to true, but it lacks an explanatory comment
documenting the reason for deprecation, unlike other deprecated entries such as
ORB which include descriptive comments. Add a comment next to the deprecated:
true flag in the DeepMD entry explaining the reason for its deprecation to
maintain consistency with other deprecated model entries and improve
maintainability.

Comment on lines +441 to +446
PET-MAD:
module: externals
class: PET_MAD
family: pet
package: upet==0.1.1
checkpoint: pet-mad-s-v1.0.2.ckpt

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 | 🟠 Major | ⚡ Quick win

Align PET-MAD registry artifact metadata with runtime model version.

Line [446] declares checkpoint pet-mad-s-v1.0.2.ckpt, while mlip_arena/models/externals/pet.py Line [30] hardcodes version="1.5.0". This creates a provenance mismatch between reported artifact metadata and the actually loaded model version.

🤖 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 `@mlip_arena/models/registry.yaml` around lines 441 - 446, The PET-MAD
checkpoint name in the registry.yaml file declares pet-mad-s-v1.0.2.ckpt while
the version hardcoded in mlip_arena/models/externals/pet.py at line 30 shows
version="1.5.0", creating a provenance mismatch. Update the checkpoint value in
the PET-MAD registry entry to align with the actual version string from the
pet.py file, ensuring the declared checkpoint artifact metadata matches the
runtime model version being loaded.

Comment thread scripts/install-linux.sh
TORCH=2.5
CUDA=cu124
uv pip install torch==${TORCH}.0
uv pip install -e .[test,extra,nequip] --no-cache

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 | 🟡 Minor | ⚡ Quick win

Quote extras specifiers to prevent shell glob expansion.

Lines 7/12/17/22/27/34 pass .[...] unquoted; [] is a glob character class in Bash. Quote each extras argument so it is always passed literally to uv pip.

Proposed fix
-    uv pip install -e .[test,extra,nequip] --no-cache
+    uv pip install -e ".[test,extra,nequip]" --no-cache
...
-    uv pip install -e .[test,extra,sevennet] --no-cache
+    uv pip install -e ".[test,extra,sevennet]" --no-cache
...
-    uv pip install -e .[test,extra,mace] --no-cache
+    uv pip install -e ".[test,extra,mace]" --no-cache
...
-    uv pip install -e .[test,extra,fairchem] --no-cache
+    uv pip install -e ".[test,extra,fairchem]" --no-cache
...
-    uv pip install -e .[test,extra,orb] --no-cache
+    uv pip install -e ".[test,extra,orb]" --no-cache
...
-    uv pip install -e .[matgl] --no-cache
-    uv pip install -e .[test,extra,all] --no-cache
+    uv pip install -e ".[matgl]" --no-cache
+    uv pip install -e ".[test,extra,all]" --no-cache

Also applies to: 12-12, 17-17, 22-22, 27-27, 34-34

🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 7-7: Ranges can only match single chars (mentioned due to duplicates).

(SC2102)

🤖 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 `@scripts/install-linux.sh` at line 7, The extras specifiers in the `uv pip
install` commands are not quoted, which allows the shell to interpret the square
brackets `[]` as glob character classes. Quote each extras argument (the part
containing `.[test,extra,nequip]` and similar patterns) by wrapping it in single
or double quotes so that the brackets are passed literally to `uv pip` rather
than being expanded as shell glob patterns. This applies to all occurrences of
the `uv pip install` command where extras are specified with unquoted bracket
notation.

Source: Linters/SAST tools

Comment thread tests/conftest.py
Comment on lines +8 to +12
temp_dir = os.path.join(tempfile.gettempdir(), f"prefect_home_{worker_id}")
os.makedirs(temp_dir, exist_ok=True)

os.environ["PREFECT_HOME"] = temp_dir
os.environ["PREFECT_API_DATABASE_CONNECTION_URL"] = f"sqlite+aiosqlite:///{temp_dir}/prefect.db"

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 | 🟡 Minor | ⚡ Quick win

Use a unique temp directory per test run to avoid stale Prefect state leakage.

Line 8 builds a deterministic path per worker, so repeated runs can reuse old prefect.db content. Prefer tempfile.mkdtemp(...) (or a session-scoped fixture creating unique dirs) to isolate runs as well as workers.

Proposed fix
-worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master")
-temp_dir = os.path.join(tempfile.gettempdir(), f"prefect_home_{worker_id}")
-os.makedirs(temp_dir, exist_ok=True)
+worker_id = os.environ.get("PYTEST_XDIST_WORKER", "master")
+temp_dir = tempfile.mkdtemp(prefix=f"prefect_home_{worker_id}_")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
temp_dir = os.path.join(tempfile.gettempdir(), f"prefect_home_{worker_id}")
os.makedirs(temp_dir, exist_ok=True)
os.environ["PREFECT_HOME"] = temp_dir
os.environ["PREFECT_API_DATABASE_CONNECTION_URL"] = f"sqlite+aiosqlite:///{temp_dir}/prefect.db"
temp_dir = tempfile.mkdtemp(prefix=f"prefect_home_{worker_id}_")
os.environ["PREFECT_HOME"] = temp_dir
os.environ["PREFECT_API_DATABASE_CONNECTION_URL"] = f"sqlite+aiosqlite:///{temp_dir}/prefect.db"
🤖 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 `@tests/conftest.py` around lines 8 - 12, Replace the deterministic temp_dir
path construction (currently using tempfile.gettempdir() with worker_id) with
tempfile.mkdtemp() to generate a unique directory for each test run, ensuring
test isolation and preventing stale Prefect state leakage from previous runs.
Update the temp_dir assignment to use mkdtemp(), which will automatically create
a unique temporary directory and eliminate the reuse of old prefect.db content
across test runs.

@chiang-yuan
chiang-yuan merged commit ba67574 into main Jun 15, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant