Skip to content

Refactor CI test environments - #87

Merged
chiang-yuan merged 58 commits into
developfrom
refactor/ci-test-environments
Jun 15, 2026
Merged

Refactor CI test environments#87
chiang-yuan merged 58 commits into
developfrom
refactor/ci-test-environments

Conversation

@chiang-yuan

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

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added PET-OAM and PET-MAD molecular potential calculators.
    • Improved stability benchmarking and post-processing (new randomized-mixtures workflow).
  • Chores

    • Deprecated DeepMD entry.
    • Extended Python support to 3.11–3.13 and updated dependency groups.
    • Reorganized benchmark output layout by model family.
    • CI and test matrix updated; installer scripts made group-aware.
    • Benchmark/file-existence checks added for more robust indexing/tests.

@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87a2746e-9870-4065-9c03-2abf291eab65

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR refactors the calculator parameter API across Prefect flows, adds PET model support with device-aware wrappers, consolidates stability benchmark orchestration into a unified flow, updates CI to split tests by model group with conditional dependency installation, and adds file-existence validation for served benchmark artifacts.

Changes

Model parameter refactoring, PET model addition, and stability benchmark consolidation

Layer / File(s) Summary
CI and test infrastructure refactoring
.github/workflows/ci.yaml, pyproject.toml
Migrated CI linting from pre-commit to uv + ruff, expanded test matrix to Python 3.11–3.13 with test-group dimension, added optional dependency groups for nequip and sevennet, introduced pytest marker configuration, and bumped requires-python to >=3.11.
Dependency installation scripts
scripts/install-linux.sh, scripts/install-perlmutter.sh
Changed from fixed PyTorch/CUDA versions to GROUP-driven conditional installation (nequip, sevennet, mace, fairchem, default), setting per-group torch versions and installing corresponding editable extras.
Calculator parameter refactoring in flows
mlip_arena/flows/diatomics.py, mlip_arena/flows/eos_bulk.py, mlip_arena/flows/stability.py
Renamed model parameter to calculator and added calculator_kwargs support across Prefect task/flow signatures, passing both through get_calculator for parameterized instantiation.
PET model implementation and registry
mlip_arena/models/externals/pet.py, mlip_arena/models/externals/ani.py, mlip_arena/models/registry.yaml
Added PET_OAM and PET_MAD calculator wrapper classes with device resolution, refactored ANI2x to use direct torchani factory pattern, registered both PET models in registry, and marked DeepMD deprecated.
Asymptote benchmarks refactored
benchmarks/asymptotes.py
Updated to validate string calculator values against MLIPEnum, derive model family from REGISTRY, and pass calculator/calculator_kwargs directly to flows.
Stability benchmark consolidation
benchmarks/stability.py
Created new random_mixtures Prefect flow orchestrating heating/compression tasks, post-processing with gather_results, and SLURM/Dask cluster harness; integrated logic from removed benchmarks/stability/run.py and benchmarks/stability/analysis.py.
Benchmark submission workflow
benchmarks/submit.py
Switched from distribution_shifts to stability.random_mixtures, set calculator to "PET-OAM", and updated TODO to reflect remaining benchmarks.
Benchmark data validation in serve ranks
serve/ranks/homonuclear-diatomics.py, serve/ranks/combustion.py
Enhanced valid_models filtering to check for existence of corresponding JSON benchmark files on disk.
Test infrastructure updates
tests/test_data_integrity.py, tests/test_external_calculators.py
Updated data integrity test to filter expected models by actual benchmark file existence, and refactored calculator test to parametrize with pytest marks (nequip, sevennet, mace, fairchem) based on model metadata.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • atomind-ai/mlip-arena#83: Overlapping benchmarks/submit.py modifications regarding benchmark workflow selection and distribution_shifts integration
  • atomind-ai/mlip-arena#77: Related changes to mlip_arena/flows/diatomics.py touching diatomic task/flow signatures and calculator resolution
  • atomind-ai/mlip-arena#74: Upstream get_calculator API/signature change that this PR's calculator_kwargs refactoring depends on

Suggested labels

enhancement

Poem

🐰 Refactored flows now dance with grace,
PET models join the benchmark race,
Stability consolidated, tests aligned,
Calculator kwargs, wisely designed.
File checks ensure the data's there—
A tidy, organized affair!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% 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 title 'Refactor CI test environments' accurately reflects the main change in the PR, which is a comprehensive refactor of CI workflows, test dependencies, and environment configuration across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/ci-test-environments

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.

@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: 6

🧹 Nitpick comments (5)
mlip_arena/models/externals/pet.py (2)

19-20: ⚡ Quick win

Remove redundant calculate method override.

The calculate method is a pure delegation to the parent class with no additional behavior. Unless there's a specific reason to override (e.g., future customization or documentation), this method can be removed.

♻️ Proposed simplification
-    def calculate(self, atoms=None, properties=["energy", "forces", "stress"], system_changes=all_changes):
-        super().calculate(atoms, properties, system_changes)
🤖 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/pet.py` around lines 19 - 20, The calculate
method in pet.py is a redundant override that only calls super().calculate with
the same signature (def calculate(self, atoms=None, properties=["energy",
"forces", "stress"], system_changes=all_changes): super().calculate(atoms,
properties, system_changes)); remove this method entirely so the class inherits
the parent implementation directly (no other changes needed unless you intend to
add custom behavior later).

32-33: ⚡ Quick win

Remove redundant calculate method override.

Same as the PET_OAM class above, this method override adds no value and can be removed.

♻️ Proposed simplification
-    def calculate(self, atoms=None, properties=["energy", "forces", "stress"], system_changes=all_changes):
-        super().calculate(atoms, properties, system_changes)
🤖 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/pet.py` around lines 32 - 33, The calculate
override in class PET (the def calculate(self, atoms=None, properties=["energy",
"forces", "stress"], system_changes=all_changes) method) is redundant and should
be removed; delete this method so the class inherits the base implementation
(same simplification as PET_OAM) and ensure no other code relies on this
explicit override signature or default mutable default arguments—if callers
depend on different defaults, adjust call sites instead of keeping the redundant
method.
benchmarks/submit.py (1)

80-87: 💤 Low value

Remove or document the commented-out code.

The distribution_shifts execution block is commented out. If this workflow is permanently disabled, remove the dead code. Otherwise, add a comment explaining why it's temporarily disabled.

🤖 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 `@benchmarks/submit.py` around lines 80 - 87, The commented-out
distribution_shifts invocation (the block using
distribution_shifts.with_options, DaskTaskRunner, and the
calculator/calculator_kwargs parameters) is dead code; either remove it if the
workflow is permanently disabled or add a brief inline comment above the block
explaining why it is temporarily disabled and when/how it will be re-enabled
(e.g., note dependency or environment reason), and ensure you keep references to
DaskTaskRunner, persist_result, calculator, and calculator_kwargs in the comment
so future readers know what the block does.
tests/test_external_calculators.py (1)

12-28: 💤 Low value

Consider using a mapping for cleaner mark assignment.

The current if/elif chain for assigning pytest marks could be simplified with a mapping dictionary, improving maintainability as more models/marks are added.

♻️ Optional refactor using a mapping
+mark_map = {
+    "SevenNet": [pytest.mark.sevennet],
+    "NequIP-OAM-L": [pytest.mark.nequip],
+}
+family_mark_map = {
+    "fairchem": [pytest.mark.fairchem],
+}
+
 model_params = []
 for model in MLIPEnum:
-    marks = []
-    if model.name == "SevenNet":
-        marks.append(pytest.mark.sevennet)
-    elif model.name == "NequIP-OAM-L":
-        marks.append(pytest.mark.nequip)
-    elif "MACE" in model.name:
-        marks.append(pytest.mark.mace)
-    elif model.value.get("family") == "fairchem":
-        marks.append(pytest.mark.fairchem)
+    marks = mark_map.get(model.name, []).copy()
+    if "MACE" in model.name:
+        marks.append(pytest.mark.mace)
+    family = model.value.get("family")
+    if family in family_mark_map:
+        marks.extend(family_mark_map[family])
 
     if marks:
         model_params.append(pytest.param(model, marks=marks, id=model.name))
🤖 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 12 - 28, Replace the if/elif
chain that assigns pytest marks for each MLIPEnum entry with a lookup mapping:
define a dict mapping either model.name strings or predicates to pytest marks
(e.g., {"SevenNet": pytest.mark.sevennet, "NequIP-OAM-L": pytest.mark.nequip,
"MACE": pytest.mark.mace, "fairchem": pytest.mark.fairchem}) and then for each
model in MLIPEnum build marks by checking the mapping keys (use substring check
for "MACE" and family lookup for "fairchem") and append matching marks to marks
list before creating pytest.param; update the logic around model_params, marks,
and pytest.param to use the mapping so adding new model-to-mark rules only
requires updating the dict.
benchmarks/stability.py (1)

60-60: 💤 Low value

Clarify or document the hardcoded structure limits.

The slice limits [:120] for heating and [:80] for compression are marked "tentatively," suggesting these are experimental values. If these are intended as permanent limits, document the rationale; otherwise, consider making them configurable.

Also applies to: 68-68

🤖 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 `@benchmarks/stability.py` at line 60, The hardcoded slice limits on
compositions (e.g., df = df[df["formula"].isin(compositions[:120])].copy() and
the similar compositions[:80] for the other case) are experimental; either
document the rationale for using 120 and 80 in a comment or make these values
configurable (e.g., add constants or function parameters like HEATING_LIMIT and
COMPRESSION_LIMIT or pass max_structures into the enclosing function) and use
those named variables instead of literal slices so the limits are explicit and
easily changed. Ensure you update any docstring or top-level comments to mention
the defaults and why those numbers were chosen if you keep them hardcoded.
🤖 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 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.

In `@scripts/install-linux.sh`:
- Around line 14-15: The DGL find-links URL (the command "uv pip install dgl -f
https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html" and its duplicates)
is returning 403 in CI; update the install step to use a reachable source and
add a fallback: try the existing data.dgl.ai find-links first, and if it fails
then fall back to "pip install dgl" (PyPI) or another known-working wheel URL
for the current TORCH/CUDA combo; apply the same change to the other occurrences
(the duplicate lines at the other blocks), and keep the TORCH and CUDA variable
interpolation intact so the script still targets the correct wheel when
available.

In `@serve/ranks/combustion.py`:
- Around line 11-16: The list comprehension building valid_models calls
metadata.get("family").lower() which will raise if family is missing; update the
comprehension to guard that family exists before calling lower (e.g. require
metadata.get("family") truthy first and then use metadata["family"].lower() or
metadata.get("family").lower()) so models with no family are skipped and the
DATA_DIR / ... .exists() check only runs when family is present; refer to
valid_models, MODELS, metadata, and DATA_DIR when making the change.

In `@serve/ranks/homonuclear-diatomics.py`:
- Around line 11-16: The list comprehension building valid_models uses
metadata.get("family") directly which can be None and cause Path / None
TypeError; update the comprehension (valid_models, MODELS, metadata) to first
ensure a valid family string exists (e.g., check metadata.get("family") is
truthy or is instance of str) before constructing the path and checking
.exists(), so short-circuit when family is missing and skip that model.

In `@tests/test_data_integrity.py`:
- Around line 47-56: MODELS[model].get("family") can be None causing Path(...)
to raise; after assigning family in the block where fpath is built (the branch
on rank_module_name), add a guard that if family is falsy/None you skip this
model (e.g., continue the loop) or set fpath to None and handle it downstream;
update the code around the family assignment and the fpath construction so that
functions/variables referenced (MODELS, family, rank_module_name, fpath) do not
attempt Path(...) when family is missing.
- Around line 44-58: The fallback logic currently adds models when fpath is None
(unknown rank_module_name), which wrongly includes all models; update the loop
that builds filtered_expected_models so it only appends a model when a concrete
path was computed and that path exists—i.e. check fpath is not None AND
fpath.exists()—so unknown rank_module_name cases are skipped; modify the
condition around the append to use that check and leave the rest of the fpath
construction in the block using MODELS, expected_models, and rank_module_name.

---

Nitpick comments:
In `@benchmarks/stability.py`:
- Line 60: The hardcoded slice limits on compositions (e.g., df =
df[df["formula"].isin(compositions[:120])].copy() and the similar
compositions[:80] for the other case) are experimental; either document the
rationale for using 120 and 80 in a comment or make these values configurable
(e.g., add constants or function parameters like HEATING_LIMIT and
COMPRESSION_LIMIT or pass max_structures into the enclosing function) and use
those named variables instead of literal slices so the limits are explicit and
easily changed. Ensure you update any docstring or top-level comments to mention
the defaults and why those numbers were chosen if you keep them hardcoded.

In `@benchmarks/submit.py`:
- Around line 80-87: The commented-out distribution_shifts invocation (the block
using distribution_shifts.with_options, DaskTaskRunner, and the
calculator/calculator_kwargs parameters) is dead code; either remove it if the
workflow is permanently disabled or add a brief inline comment above the block
explaining why it is temporarily disabled and when/how it will be re-enabled
(e.g., note dependency or environment reason), and ensure you keep references to
DaskTaskRunner, persist_result, calculator, and calculator_kwargs in the comment
so future readers know what the block does.

In `@mlip_arena/models/externals/pet.py`:
- Around line 19-20: The calculate method in pet.py is a redundant override that
only calls super().calculate with the same signature (def calculate(self,
atoms=None, properties=["energy", "forces", "stress"],
system_changes=all_changes): super().calculate(atoms, properties,
system_changes)); remove this method entirely so the class inherits the parent
implementation directly (no other changes needed unless you intend to add custom
behavior later).
- Around line 32-33: The calculate override in class PET (the def
calculate(self, atoms=None, properties=["energy", "forces", "stress"],
system_changes=all_changes) method) is redundant and should be removed; delete
this method so the class inherits the base implementation (same simplification
as PET_OAM) and ensure no other code relies on this explicit override signature
or default mutable default arguments—if callers depend on different defaults,
adjust call sites instead of keeping the redundant method.

In `@tests/test_external_calculators.py`:
- Around line 12-28: Replace the if/elif chain that assigns pytest marks for
each MLIPEnum entry with a lookup mapping: define a dict mapping either
model.name strings or predicates to pytest marks (e.g., {"SevenNet":
pytest.mark.sevennet, "NequIP-OAM-L": pytest.mark.nequip, "MACE":
pytest.mark.mace, "fairchem": pytest.mark.fairchem}) and then for each model in
MLIPEnum build marks by checking the mapping keys (use substring check for
"MACE" and family lookup for "fairchem") and append matching marks to marks list
before creating pytest.param; update the logic around model_params, marks, and
pytest.param to use the mapping so adding new model-to-mark rules only requires
updating the dict.
🪄 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: c1632ccf-573d-4b7f-b6fc-2de71d3e9b2b

📥 Commits

Reviewing files that changed from the base of the PR and between 9c3f697 and 3b85011.

📒 Files selected for processing (19)
  • .github/workflows/ci.yaml
  • benchmarks/asymptotes.py
  • benchmarks/stability.py
  • benchmarks/stability/analysis.py
  • benchmarks/stability/run.py
  • benchmarks/submit.py
  • mlip_arena/flows/diatomics.py
  • mlip_arena/flows/eos_bulk.py
  • mlip_arena/flows/stability.py
  • mlip_arena/models/externals/ani.py
  • mlip_arena/models/externals/pet.py
  • mlip_arena/models/registry.yaml
  • pyproject.toml
  • scripts/install-linux.sh
  • scripts/install-perlmutter.sh
  • serve/ranks/combustion.py
  • serve/ranks/homonuclear-diatomics.py
  • tests/test_data_integrity.py
  • tests/test_external_calculators.py
💤 Files with no reviewable changes (3)
  • benchmarks/stability/analysis.py
  • scripts/install-perlmutter.sh
  • 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 | 🔴 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

Comment thread scripts/install-linux.sh Outdated
Comment on lines +14 to +15
uv pip install torch-scatter torch-sparse -f https://data.pyg.org/whl/torch-${TORCH}.0+${CUDA}.html
uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html

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

Use a reachable DGL install source for the Torch/CUDA combos in CI.

These lines are currently blocking CI: sevennet/mace jobs fail with HTTP 403 on the data.dgl.ai find-links URL (torch-2.8/cu128). This is a release blocker for grouped test execution.

Also applies to: 21-22, 28-29, 35-36

🤖 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` around lines 14 - 15, The DGL find-links URL (the
command "uv pip install dgl -f
https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html" and its duplicates)
is returning 403 in CI; update the install step to use a reachable source and
add a fallback: try the existing data.dgl.ai find-links first, and if it fails
then fall back to "pip install dgl" (PyPI) or another known-working wheel URL
for the current TORCH/CUDA combo; apply the same change to the other occurrences
(the duplicate lines at the other blocks), and keep the TORCH and CUDA variable
interpolation intact so the script still targets the correct wheel when
available.

Source: Pipeline failures

Comment thread serve/ranks/combustion.py
Comment on lines +11 to +16
valid_models = [
model
for model, metadata in MODELS.items()
if Path(__file__).stem in metadata.get("gpu-tasks", [])
and (DATA_DIR / metadata.get("family").lower() / f"{model}_H256O128.json").exists()
]

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

Guard against missing family metadata.

Line 15 uses metadata.get("family").lower() in path construction. If family is missing from the model's metadata, this will raise an AttributeError (NoneType has no attribute 'lower').

🛡️ Proposed fix to add defensive check
 valid_models = [
     model
     for model, metadata in MODELS.items()
     if Path(__file__).stem in metadata.get("gpu-tasks", [])
+    and metadata.get("family") is not None
     and (DATA_DIR / metadata.get("family").lower() / f"{model}_H256O128.json").exists()
 ]
📝 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
valid_models = [
model
for model, metadata in MODELS.items()
if Path(__file__).stem in metadata.get("gpu-tasks", [])
and (DATA_DIR / metadata.get("family").lower() / f"{model}_H256O128.json").exists()
]
valid_models = [
model
for model, metadata in MODELS.items()
if Path(__file__).stem in metadata.get("gpu-tasks", [])
and metadata.get("family") is not None
and (DATA_DIR / metadata.get("family").lower() / f"{model}_H256O128.json").exists()
]
🤖 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 `@serve/ranks/combustion.py` around lines 11 - 16, The list comprehension
building valid_models calls metadata.get("family").lower() which will raise if
family is missing; update the comprehension to guard that family exists before
calling lower (e.g. require metadata.get("family") truthy first and then use
metadata["family"].lower() or metadata.get("family").lower()) so models with no
family are skipped and the DATA_DIR / ... .exists() check only runs when family
is present; refer to valid_models, MODELS, metadata, and DATA_DIR when making
the change.

Comment on lines +11 to +16
valid_models = [
model
for model, metadata in MODELS.items()
if Path(__file__).stem in metadata.get("gpu-tasks", [])
and (DATA_DIR / metadata.get("family") / f"{model}.json").exists()
]

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

Guard against missing family metadata.

Line 15 uses metadata.get("family") in path construction. If family is missing from the model's metadata, this will raise a TypeError when constructing the path (Path / None).

🛡️ Proposed fix to add defensive check
 valid_models = [
     model
     for model, metadata in MODELS.items()
     if Path(__file__).stem in metadata.get("gpu-tasks", [])
+    and metadata.get("family") is not None
     and (DATA_DIR / metadata.get("family") / f"{model}.json").exists()
 ]
🤖 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 `@serve/ranks/homonuclear-diatomics.py` around lines 11 - 16, The list
comprehension building valid_models uses metadata.get("family") directly which
can be None and cause Path / None TypeError; update the comprehension
(valid_models, MODELS, metadata) to first ensure a valid family string exists
(e.g., check metadata.get("family") is truthy or is instance of str) before
constructing the path and checking .exists(), so short-circuit when family is
missing and skip that model.

Comment on lines +44 to +58
# Filter expected models to those that have benchmark data files in the repository
filtered_expected_models = []
for model in expected_models:
family = MODELS[model].get("family")
if rank_module_name == "homonuclear-diatomics":
fpath = Path("benchmarks/diatomics") / family / f"{model}.json"
elif rank_module_name == "combustion":
fpath = Path("benchmarks/combustion") / family.lower() / f"{model}_H256O128.json"
elif rank_module_name == "stability":
fpath = Path("benchmarks/stability") / family.lower() / f"{model}-heating.parquet"
else:
fpath = None

if fpath is None or fpath.exists():
filtered_expected_models.append(model)

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

Fix fallback logic for unknown rank modules.

Line 57's condition if fpath is None or fpath.exists() includes models when fpath is None (i.e., when rank_module_name doesn't match any known pattern). This means for any new rank module not in the if/elif chain, all expected models are included regardless of whether their benchmark files exist, potentially masking missing data.

🔧 Proposed fix to handle unknown rank modules explicitly
         if fpath is None or fpath.exists():
             filtered_expected_models.append(model)
+        elif fpath is None:
+            pytest.fail(f"Unknown rank_module_name '{rank_module_name}' - add file path pattern")

Alternative: Only include models when fpath exists:

-        if fpath is None or fpath.exists():
+        if fpath is not None and fpath.exists():
             filtered_expected_models.append(model)
🤖 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_data_integrity.py` around lines 44 - 58, The fallback logic
currently adds models when fpath is None (unknown rank_module_name), which
wrongly includes all models; update the loop that builds
filtered_expected_models so it only appends a model when a concrete path was
computed and that path exists—i.e. check fpath is not None AND fpath.exists()—so
unknown rank_module_name cases are skipped; modify the condition around the
append to use that check and leave the rest of the fpath construction in the
block using MODELS, expected_models, and rank_module_name.

Comment on lines +47 to +56
family = MODELS[model].get("family")
if rank_module_name == "homonuclear-diatomics":
fpath = Path("benchmarks/diatomics") / family / f"{model}.json"
elif rank_module_name == "combustion":
fpath = Path("benchmarks/combustion") / family.lower() / f"{model}_H256O128.json"
elif rank_module_name == "stability":
fpath = Path("benchmarks/stability") / family.lower() / f"{model}-heating.parquet"
else:
fpath = None

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

Guard against missing family metadata.

Line 47 uses MODELS[model].get("family"), which could return None. Subsequent path construction on lines 49, 51, and 53 would raise a TypeError if family is None.

🛡️ Proposed fix to skip models with missing family
     for model in expected_models:
         family = MODELS[model].get("family")
+        if family is None:
+            continue
         if rank_module_name == "homonuclear-diatomics":
📝 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
family = MODELS[model].get("family")
if rank_module_name == "homonuclear-diatomics":
fpath = Path("benchmarks/diatomics") / family / f"{model}.json"
elif rank_module_name == "combustion":
fpath = Path("benchmarks/combustion") / family.lower() / f"{model}_H256O128.json"
elif rank_module_name == "stability":
fpath = Path("benchmarks/stability") / family.lower() / f"{model}-heating.parquet"
else:
fpath = None
family = MODELS[model].get("family")
if family is None:
continue
if rank_module_name == "homonuclear-diatomics":
fpath = Path("benchmarks/diatomics") / family / f"{model}.json"
elif rank_module_name == "combustion":
fpath = Path("benchmarks/combustion") / family.lower() / f"{model}_H256O128.json"
elif rank_module_name == "stability":
fpath = Path("benchmarks/stability") / family.lower() / f"{model}-heating.parquet"
else:
fpath = None
🤖 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_data_integrity.py` around lines 47 - 56,
MODELS[model].get("family") can be None causing Path(...) to raise; after
assigning family in the block where fpath is built (the branch on
rank_module_name), add a guard that if family is falsy/None you skip this model
(e.g., continue the loop) or set fpath to None and handle it downstream; update
the code around the family assignment and the fpath construction so that
functions/variables referenced (MODELS, family, rank_module_name, fpath) do not
attempt Path(...) when family is missing.

@chiang-yuan
chiang-yuan changed the base branch from main to develop June 8, 2026 01:40

@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: 1

♻️ Duplicate comments (1)
scripts/install-linux.sh (1)

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

Add a fallback for DGL wheel installs to prevent CI hard-failures.

Line 8/15/22/29/36 still use the data.dgl.ai index directly; earlier CI runs already showed 403 for some Torch/CUDA combos, so grouped test jobs can fail before tests start.

Proposed minimal fix
+install_dgl() {
+    uv pip install dgl -f "https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html" \
+      || uv pip install dgl
+}
+
 if [ "$GROUP" == "nequip" ]; then
@@
-    uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html
+    install_dgl
@@
-    uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html
+    install_dgl
@@
-    uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html
+    install_dgl
@@
-    uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html
+    install_dgl
@@
-    uv pip install dgl -f https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html
+    install_dgl

Also applies to: 15-15, 22-22, 29-29, 36-36

🤖 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 8, Replace the direct DGL wheel installs
that use the data.dgl.ai index (the lines calling "pip install dgl -f
https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html") with a resilient
two-step command so CI won't hard-fail on 403s: attempt the indexed install
first and, if it fails, fall back to a normal PyPI install (e.g. "pip install
dgl -f ... || pip install dgl"); update every occurrence of that exact command
(the ones at lines with the same pattern) in scripts/install-linux.sh
accordingly.

Source: Pipeline failures

🧹 Nitpick comments (1)
pyproject.toml (1)

32-32: ⚡ Quick win

Consolidate duplicated pymatgen requirement.

Line 32 (pymatgen) and Line 41 (pymatgen>=2025.1.9) are redundant; keep only the constrained entry to avoid ambiguity in dependency intent.

Also applies to: 41-41

🤖 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 `@pyproject.toml` at line 32, Remove the duplicate unconstrained dependency
entry "pymatgen" and retain only the constrained entry "pymatgen>=2025.1.9" in
pyproject.toml so the package intent is unambiguous; locate the two entries (the
plain "pymatgen" and the "pymatgen>=2025.1.9" lines) and delete the
unconstrained one.
🤖 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 `@pyproject.toml`:
- Around line 54-58: The test extra currently forces "torch>=2.8.0" in
pyproject.toml which can override the CI's nequip-installed torch==2.5.0 and
break prebuilt wheels; remove or relax that constraint—either delete the
"torch>=2.8.0" entry from the test extras so tests use the environment-provided
torch, or replace it with a non-conflicting range (e.g., "torch>=1.13,<2.6") if
you must declare a version; update the test extras list in pyproject.toml where
the string "torch>=2.8.0" appears.

---

Duplicate comments:
In `@scripts/install-linux.sh`:
- Line 8: Replace the direct DGL wheel installs that use the data.dgl.ai index
(the lines calling "pip install dgl -f
https://data.dgl.ai/wheels/torch-${TORCH}/${CUDA}/repo.html") with a resilient
two-step command so CI won't hard-fail on 403s: attempt the indexed install
first and, if it fails, fall back to a normal PyPI install (e.g. "pip install
dgl -f ... || pip install dgl"); update every occurrence of that exact command
(the ones at lines with the same pattern) in scripts/install-linux.sh
accordingly.

---

Nitpick comments:
In `@pyproject.toml`:
- Line 32: Remove the duplicate unconstrained dependency entry "pymatgen" and
retain only the constrained entry "pymatgen>=2025.1.9" in pyproject.toml so the
package intent is unambiguous; locate the two entries (the plain "pymatgen" and
the "pymatgen>=2025.1.9" lines) and delete the unconstrained one.
🪄 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: be4e78f3-c8ea-4b8c-bb11-9636b4897467

📥 Commits

Reviewing files that changed from the base of the PR and between 3b85011 and 03e7e83.

📒 Files selected for processing (3)
  • mlip_arena/models/classicals/zbl.py
  • pyproject.toml
  • scripts/install-linux.sh

Comment thread pyproject.toml Outdated
Comment on lines +54 to +58
"torch>=2.8.0",
"pytest",
"pytest-cov",
"streamlit>=1.55.0",
]

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

test extra’s Torch constraint conflicts with nequip CI environment.

Line 54 (torch>=2.8.0) can override the nequip branch’s torch==2.5.0 install (from scripts/install-linux.sh), which can invalidate the preinstalled torch-scatter/torch-sparse wheels built for 2.5.

Proposed fix
 test = [
-    "torch>=2.8.0",
     "pytest",
     "pytest-cov",
     "streamlit>=1.55.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
"torch>=2.8.0",
"pytest",
"pytest-cov",
"streamlit>=1.55.0",
]
"pytest",
"pytest-cov",
"streamlit>=1.55.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 `@pyproject.toml` around lines 54 - 58, The test extra currently forces
"torch>=2.8.0" in pyproject.toml which can override the CI's nequip-installed
torch==2.5.0 and break prebuilt wheels; remove or relax that constraint—either
delete the "torch>=2.8.0" entry from the test extras so tests use the
environment-provided torch, or replace it with a non-conflicting range (e.g.,
"torch>=1.13,<2.6") if you must declare a version; update the test extras list
in pyproject.toml where the string "torch>=2.8.0" appears.

@codecov

codecov Bot commented Jun 8, 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 9 files with indirect coverage changes

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

…ement local scatter helpers, and mark MACE test workflows
@chiang-yuan chiang-yuan closed this Jun 8, 2026
@chiang-yuan chiang-yuan reopened this Jun 8, 2026
@chiang-yuan
chiang-yuan force-pushed the refactor/ci-test-environments branch from cf8fcc2 to ca06673 Compare June 14, 2026 00:19
@chiang-yuan
chiang-yuan force-pushed the refactor/ci-test-environments branch from 17d6a44 to ba8ee45 Compare June 15, 2026 04:30
@chiang-yuan
chiang-yuan merged commit e793ceb into develop 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.

3 participants