Skip to content

Deprecate top-level cutoff, always deduce it from bonds::rcut - #104

Merged
yury-lysogorskiy merged 3 commits into
masterfrom
claude/ace-cutoff-parameters-di6xyq
Aug 27, 2026
Merged

Deprecate top-level cutoff, always deduce it from bonds::rcut#104
yury-lysogorskiy merged 3 commits into
masterfrom
claude/ace-cutoff-parameters-di6xyq

Conversation

@yury-lysogorskiy

@yury-lysogorskiy yury-lysogorskiy commented Aug 27, 2026

Copy link
Copy Markdown
Member

Pull Request Template

Description of Changes

The top-level cutoff of the input YAML has never reached the fit. It is read in bin/pacemaker:161 and required to be present, but it is never passed to GeneralACEFit, so the cutoff is None branch always applies and the neighbour lists are always built with max(bonds::rcut). The last consumer of the value was the --query-data path removed in 29ba519 (Nov 2023); checking every commit that ever touched bin/pacemaker, the GeneralACEFit(...) call has never carried a cutoff argument.

That leaves a mandatory parameter whose value is silently discarded: a user can set it inconsistently with bonds::rcut and never find out, while omitting it aborts the run with a ValueError.

Rather than start honouring the value — which would make an under-sized cutoff silently truncate the training neighbour lists, and an over-sized one waste memory on neighbours that contribute exactly zero — this PR drops the parameter. Deducing the cutoff from max(bonds::rcut) makes the fitting neighbour lists agree with ACEBBasisSet.cutoffmax, which the ASE and LAMMPS calculators use, so the two cannot drift apart.

  • bin/pacemakercutoff is no longer required; a deprecation warning is logged if it is still present, so existing input files keep working unchanged.
  • generalfit.py — extract get_maximal_rcut() and log the resolved cutoff, so the value actually used is always visible in log.txt. The cutoff argument of GeneralACEFit is kept as a programmatic escape hatch and documented.
  • input_template.yaml / pacemaker -t — the dialog set both cutoff and bonds::rcut from a single answer; it now asks for rcut only, and the generated file has no top-level cutoff.
  • examples and CLI tests — drop the redundant cutoff line. All eight files had cutoff == max(bonds::rcut) exactly, so the deduced value is unchanged in every case.
  • docs — describe the deduction in inputfile.md, quickstart.md and faq.md. The inputfile.md example was itself inconsistent (cutoff: 10.0 against rcut 5 / 5.5).
  • tests — new tests/test_cutoff_deduction.py covering get_maximal_rcut, including that it agrees with ACEBBasisSet.cutoffmax for per-bond cutoffs.

Runtime behaviour is unchanged for every existing input file: the deduced cutoff is exactly what was already being used.

Two CI fixes carried along

CI on this repository was red before this branch, and in a way that hid all test results, so two unrelated fixes are included to make the change verifiable. Both are self-contained and can be split out if preferred.

3bfa4df — install lib/maxvolpy in the workflow. Both jobs aborted during pytest collection with ModuleNotFoundError: No module named 'maxvolpy', so no test ran at all — pre-existing and identical on run 31264271622 (17 Aug, #103). maxvolpy is installed by the custom InstallMaxVolPyLocalPackage(install) hook in setup.py, which pip install . never invokes because it builds a wheel. The workflow now builds it explicitly; the commit message records why --no-build-isolation and scipy are each required and why numpy is deliberately not installed there.

d63466fUnitCellFilter import in test_PyACECalculator.py. ASE moved it to ase.filters in 3.23 and has since dropped the ase.constraints alias. ase is unpinned, so the 3.10 job resolved a recent ASE without it while the 3.9 job — which recent ASE no longer supports — still got one that has it; that is why only one job failed. The test now tries the new location and falls back to the old, so both ASE generations work.

These two also unblock #100, #101, #102 and #103, which hit the same walls.

Checklist

  • Code is well-documented.
  • All tests have been run and passed. — run 33104565094: build (3.9) and build (3.10) both green, each covering the full pytest suite and the pacemaker CLI integration tests, which exercise the modified input.yaml files end to end.
  • Relevant documentation has been updated if necessary.

License Agreement

By submitting this pull request, I agree that:

  • The code submitted in this pull request will be distributed under the Academic Software License (free for academic non-commercial use, not free for commercial use), see LICENSE.md for more details.
  • The copyright for the code, including the submitted code, remains with Ruhr University Bochum. Ruhr University Bochum retains the right to transfer or modify the copyright.

🤖 Generated with Claude Code

https://claude.ai/code/session_017QbfG2UVKxkeuiz9GtDLo4

The top-level `cutoff` of the input YAML has never reached the fit: it was
read in `bin/pacemaker` and required to be present, but was never passed to
`GeneralACEFit`, so the `cutoff is None` branch always applied and the
neighbour lists were always built with max(bonds::rcut). The last consumer of
the value was the `--query-data` path removed in 29ba519.

This left a mandatory parameter whose value was silently discarded: users
could set it inconsistently with `bonds::rcut` and never learn about it, while
omitting it aborted the run with a ValueError.

Rather than start honouring the value - which would make an under-sized
`cutoff` silently truncate the training neighbour lists, and an over-sized one
waste memory for identically zero contributions - the parameter is dropped.
Deducing the cutoff from max(bonds::rcut) makes the fitting neighbour lists
agree with `ACEBBasisSet.cutoffmax`, which the ASE and LAMMPS calculators use,
so the two cannot drift apart.

- bin/pacemaker: `cutoff` is no longer required; a deprecation warning is
  logged if it is still present, so existing input files keep working.
- generalfit.py: extract `get_maximal_rcut()` and log the resolved cutoff, so
  the value actually used is always visible in log.txt. The `cutoff` argument
  of `GeneralACEFit` is kept as a programmatic escape hatch and documented.
- input_template.yaml / `pacemaker -t`: the dialog set both `cutoff` and
  `bonds::rcut` from one answer; it now asks for `rcut` only and the generated
  file has no top-level `cutoff`.
- examples and CLI tests: drop the redundant `cutoff` line. All eight files had
  `cutoff == max(bonds::rcut)` exactly, so the deduced value is unchanged.
- docs: describe the deduction in inputfile.md, quickstart.md and faq.md. The
  inputfile.md example was itself inconsistent (`cutoff: 10.0` against
  rcut 5/5.5).
- tests: cover `get_maximal_rcut`, including that it agrees with
  `ACEBBasisSet.cutoffmax` for per-bond cutoffs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QbfG2UVKxkeuiz9GtDLo4

Copy link
Copy Markdown
Member Author

CI is red on both build (3.9) and build (3.10), but the failure is not from this PR and I have not tried to fix it here.

Both jobs abort during collection, before a single test runs:

ImportError while importing test module 'tests/test_activeexploration.py'
  pyace/activelearning.py:9: from maxvolpy.maxvol import maxvol
E ModuleNotFoundError: No module named 'maxvolpy'
...
!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!

This is pre-existing. The Python package workflow has three runs in total and all three failed; run 31264271622 on 17 Aug, from the unrelated PR #103, fails with byte-for-byte the same two collection errors. Nothing in this PR touches activelearning, activeexploration or packaging. I did not re-run the jobs — the failure is a deterministic ModuleNotFoundError reproduced ten days apart on unrelated code, so it is not a flake. No open PR addresses it, so there was nothing to port into this branch.

Root cause, for whoever picks it up: maxvolpy is installed by the custom InstallMaxVolPyLocalPackage(install) command in setup.py:18-33, which shells out to cd lib/maxvolpy; python setup.py install. The workflow installs with pip install ., which builds a wheel and never invokes the legacy install command, so that hook does not run and maxvolpy is never installed. (python setup.py install is also gone in setuptools >= 80, so the hook would likely fail even if it were reached.)

The smallest CI-side fix is an explicit step in .github/workflows/test.yml, after Install python-ace:

    - name: Install maxvolpy
      run: |
        pip install Cython
        pip install ./lib/maxvolpy

I have deliberately left this out of this PR rather than widen it — happy to open it as a separate one if that is useful.

What this means for reviewing this PR: CI currently gives no signal on it either way. Because pytest exits during collection, the new tests/test_cutoff_deduction.py never executes, and neither does anything else. The checks in this branch will stay red until the maxvolpy install is fixed on master, independently of the changes here.


Generated by Claude Code

Both CI jobs aborted during pytest collection with

    pyace/activelearning.py:9: from maxvolpy.maxvol import maxvol
    E ModuleNotFoundError: No module named 'maxvolpy'
    Interrupted: 2 errors during collection

so no test ran at all. This is pre-existing and unrelated to any particular
change - run 31264271622 (17 Aug, PR #103) fails identically.

`maxvolpy` is installed by the custom InstallMaxVolPyLocalPackage(install)
command in setup.py, which shells out to `cd lib/maxvolpy; python setup.py
install`. The workflow installs with `pip install .`, which builds a wheel and
never invokes the legacy `install` command, so that hook never runs.
(`python setup.py install` is also gone in setuptools >= 80.)

Build it explicitly instead. `--no-build-isolation` is required because
lib/maxvolpy/setup.py imports Cython and numpy at module level while declaring
no pyproject.toml build-system, so an isolated build environment fails with
ModuleNotFoundError: No module named 'Cython'. Cython and scipy are installed
first: the extension cimports scipy/linalg/cython_blas.pxd. numpy is
deliberately not installed here, to avoid resolving past setup.py's
`numpy<=1.26.4` pin; it is already present from the python-ace install.

Verified locally in a clean venv: with build isolation the build fails on the
missing Cython, without scipy it fails cythonizing _maxvol.pyx, and with the
command used here it builds a wheel and `from maxvolpy.maxvol import maxvol`
succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QbfG2UVKxkeuiz9GtDLo4

Copy link
Copy Markdown
Member Author

Update after 3bfa4df (the maxvolpy CI fix): collection now succeeds and the suite runs for the first time.

job result
build (3.9) ✅ success — full suite + CLI integration tests
build (3.10) ❌ 289 passed, 1 failed in 187s

The maxvolpy problem from my earlier comment is resolved, and the changes in this PR are exercised: tests/test_cutoff_deduction.py passes on both jobs.

The single remaining failure is unrelated to this PR and I have not fixed it here:

FAILED tests/test_PyACECalculator.py::test_relaxation
  ImportError: cannot import name 'UnitCellFilter' from 'ase.constraints'

tests/test_PyACECalculator.py:191 imports UnitCellFilter from ase.constraints; ASE moved it to ase.filters in 3.23. ase is unpinned in requirements.txt, so 3.10 resolves a version where the old location is gone, while 3.9 — which recent ASE no longer supports — still gets a version that has it. That version split is exactly why 3.9 is green and 3.10 is not, and it is also why this was invisible before: collection used to abort before the test ran.

Proposed patch, if you would like it in a separate PR:

try:
    from ase.filters import UnitCellFilter
except ImportError:  # ASE < 3.23
    from ase.constraints import UnitCellFilter

I have kept it out of this branch so the cutoff change stays reviewable on its own; the same fix would also unblock #100, #101, #102 and #103, which all hit the maxvolpy wall and would hit this one next. Happy to open it separately, or to fold it in here if you would rather see this PR fully green — just say which.


Generated by Claude Code

…nstraints

test_relaxation failed on the Python 3.10 job with

    ImportError: cannot import name 'UnitCellFilter' from 'ase.constraints'

ASE moved UnitCellFilter to ase.filters in 3.23 and has since removed the
alias from ase.constraints. `ase` is unpinned in requirements.txt, so the 3.10
job resolves a recent ASE without it, while the 3.9 job - which recent ASE no
longer supports - still gets a version that has it. That is why only one of
the two jobs failed.

Try the new location first and fall back to the old one, so both ASE
generations keep working. Verified against ase 3.29: ase.filters.UnitCellFilter
resolves and importing it from ase.constraints raises exactly the error above.

This failure is unrelated to the cutoff change in this branch; it only became
visible once the maxvolpy fix in 3bfa4df let the suite be collected at all.
It is the sole remaining failure - the run before this commit reported
289 passed, 1 failed on 3.10 and a fully green 3.9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QbfG2UVKxkeuiz9GtDLo4
@yury-lysogorskiy
yury-lysogorskiy merged commit a743a1f into master Aug 27, 2026
2 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