Skip to content

Merge pull request #177 from isayevlab/feat/conda-no-aimnet #198

Merge pull request #177 from isayevlab/feat/conda-no-aimnet

Merge pull request #177 from isayevlab/feat/conda-no-aimnet #198

Workflow file for this run

name: Tests
on:
push:
branches: [main]
pull_request:
concurrency:
group: tests-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: pytest (py${{ matrix.python }}, ani=${{ matrix.ani }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.11", "3.12"]
ani: [false]
include:
- python: "3.12"
ani: true
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: ${{ matrix.python }}
- name: Install
run: |
python -m pip install --upgrade pip
pip install -e ".[ase,dev]" # dev provides pytest/pytest-cov
- name: Install ani extra
if: matrix.ani
run: pip install -e ".[ani]"
- name: Verify every test module imports
# --continue-on-collection-errors on the fast run hides a module that
# fails to import, so assert collection cleanliness separately.
run: pytest tests/ --collect-only -q
- name: Run fast tests
run: pytest tests/ -q -m "not slow" --continue-on-collection-errors
# Here rather than in `lint`, because this job is the only one that
# installs the dependencies. mypy resolves a third-party import to `Any`
# when the package is absent, so a dep-free run cannot see a wrong tensor,
# array or ASE argument at all -- it reported 40 errors where a run with
# the extras present reports 66, and the 26 it missed are precisely the
# ones a type checker is worth having here for.
#
# Only on the `ani` leg: three near-identical reports are noise, and this
# is the leg with the most complete type information. Running it under
# 3.12 does not weaken the floor -- `python_version = "3.11"` in
# pyproject.toml is what decides the semantics checked, not the
# interpreter.
- name: mypy (non-blocking until type debt is cleared)
if: matrix.ani
run: mypy src/Auto3D/ || true
no-aimnet:
# The conda-forge package will ship Auto3D's dependency set minus aimnet
# plus torchani and ase -- aimnet cannot be packaged for conda-forge (its
# dependency nvalchemi-toolkit-ops is pip-only). conda-recipe/meta.yaml is
# updated to that shape as part of the 3.1.1 release. This job emulates
# that environment and is the enforcement mechanism for the no-aimnet
# path: without it, the path rots the first time someone adds a
# module-level `import aimnet`.
#
# `pip uninstall -y aimnet` below leaves aimnet's own transitive
# dependencies installed, so this emulation cannot catch code that leans
# on a package that only arrives via aimnet's dependency tree -- the
# genuine conda build is the true gate for that.
name: no-aimnet (conda package environment)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Install, then remove aimnet
run: |
python -m pip install --upgrade pip
pip install -e ".[ani,ase,dev]"
pip uninstall -y aimnet
- name: Bare import works without aimnet
run: python -c "import Auto3D"
- name: Whole-suite collection stays clean without aimnet
run: pytest tests/ --collect-only -q
- name: No-aimnet behavior tests
run: pytest tests/test_no_aimnet.py -q
- name: ANI2xt still constructs (bundled weights, no download)
run: pytest "tests/test_model_factory.py" -q -k "ani2xt"
slow:
name: slow tests (NNP integration)
runs-on: ubuntu-latest
# These tests are NNP inference on CPU, so their wall time tracks whatever
# share of the runner we get. Three consecutive runs of the same commit
# took 6m21s, 17m57s and 6m52s: the middle one exceeded the old 20-minute
# ceiling and was cancelled *after* all 58 tests had passed, reporting a
# red slow tier for code that was fine. A job that fails on runner luck
# teaches people to ignore it, which is worse than a job that is slow.
# 45 gives roughly 2.5x headroom over the worst observed run while still
# catching a genuine hang.
timeout-minutes: 45
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- name: Cache downloaded model weights
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
# AIMNet2 weights download to ~/.cache/aimnet; torchani/torch.hub
# weights (ANI2x) land in ~/.cache/torch. Cache both so the slow
# suite does not re-download on every run.
path: |
~/.cache/aimnet
~/.cache/torch
key: nnp-model-cache-v1
- name: Install
run: |
python -m pip install --upgrade pip
# ase for thermo, ani for the torchani-gated slow tests, dev for pytest
pip install -e ".[ase,ani,dev]"
- name: Warm the AIMNet2 model cache (hard failure if unreachable)
# An unavailable gate must never be reportable as a passing gate. If the
# registry or network is down, fail here loudly rather than letting the
# suite skip or silently pass (audit: the remediation gate depends on
# this model being present).
run: |
python - <<'PY'
import sys
import torch
from Auto3D.engines.model_factory import create_model
try:
create_model("AIMNET", torch.device("cpu"))
except Exception as exc:
print(f"::error::AIMNet2 model unavailable, gate cannot run: {exc}")
sys.exit(1)
print("AIMNet2 model cache warm")
PY
- name: Run slow NNP + pipeline tests
# Previously limited to three files because the heavy end-to-end modules
# were flaky under combined ordering. test_auto3D.py now runs every
# pipeline test through the job_dir/isolated_input fixtures and
# test_thermo.py stages its opt_geometry inputs in tmp_path, so those
# two no longer write job directories or output SDFs into
# tests/files/. The remaining three (test_SPE.py, test_isomer_engine.py,
# test_tauto.py) still do not use the fixtures, so for them the
# protection is unchanged: main()'s microsecond-resolution job naming,
# strictly serial CI execution, and the autouse GPU-teardown fixture
# applied via the `slow` marker -- assessed at 70-80% confidence, not
# guaranteed (audit M31).
run: >
pytest tests/ -m slow -q
# Executes one notebook for real. Here rather than in the docs workflow
# because every one of the 20 needs a neural network potential, and this
# is the only job with the model cache and the `ani` extra.
#
# One, and the selection was measured on a CPU box with CUDA hidden --
# which is what a runner is -- rather than on a machine with a GPU. The
# dividing line is not notebook size, it is whether the notebook runs an
# optimization:
#
# single_point_energy (calc_spe, 4 records) 5.9 s
# geometry_optimization (opt_geometry, 4 records) > 600 s
#
# The same optimization notebook takes 74 s on a GPU, which is how it
# first looked affordable. Every other notebook either optimizes or runs
# `main()` end to end, so all of them land on the wrong side of that line;
# those paths are covered by the slow test tier above instead.
#
# What this closes: this notebook calling a function Auto3D no longer has,
# or passing an argument it no longer accepts, now fails here. What it
# does not: the other 19 are still parse-checked only, so the guard in
# tests/test_notebook_properties.py remains the only thing covering all
# 20 -- and it catches a wrong SD property name, not a wrong call.
- name: Execute the single-point notebook
run: |
pip install nbmake
pytest --nbmake --nbmake-timeout=600 example/single_point_energy.ipynb
lint:
name: ruff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
# ruff needs no dependencies installed; mypy does, so it runs in the
# `test` job instead. See the note on that step.
# Pinned exactly, and matching the `dev` extra in pyproject.toml, because
# `ruff format --check` is a gate: ruff promotes formatting-style changes
# into its stable style across minor releases, with settings unchanged.
# An unpinned install turns any such release into a red build on whichever
# unrelated pull request happens to be open that day -- the same
# fails-on-luck failure mode the slow job's timeout comment describes.
#
# Bumping it is a deliberate act: raise both pins together, and if the new
# version reformats anything, that reformat is its own commit and its own
# `.git-blame-ignore-revs` entry.
- name: Install tooling
run: pip install ruff==0.15.9
- name: ruff check
run: ruff check src/Auto3D/ tests/
# CONTRIBUTING.md has told contributors to run `ruff format src/ tests/`
# since long before anything checked. Nothing did, so the tree drifted
# until the documented command produced a 169-file diff -- at which point
# nobody could follow the instruction on an ordinary pull request, and the
# drift compounded. The gate is the half that makes the instruction true.
#
# Unlike the mypy step, this one does NOT end in `|| true`. It starts from
# zero and the fix is one command, so there is no debt to grandfather.
- name: ruff format
run: ruff format --check src/ tests/