Skip to content

v0.2.5

Latest

Choose a tag to compare

@isayev isayev released this 23 Oct 22:11
· 26 commits to main since this release
5b2e833

0.2.5 is a pre-1.0 release. Despite the patch-level version number it contains breaking changes, so if you pin aimnet~=0.2.0 (or >=0.2,<0.3), read the breaking-changes list below before upgrading.

Breaking changes

  • Higher minimum dependency versions: torch>=2.10 (was 2.8), warp-lang>=1.13,<2 (was 1.11), and nvalchemi-toolkit-ops>=0.4.1 (was 0.3.1). Selecting PME Coulomb in an environment that still has nvalchemi-toolkit-ops 0.4.0 installed raises RuntimeError.
  • Model loading and the artifact trust boundary: TorchScript is used only for .jpt files; a bare registry name takes precedence over a same-named implicit local path; registry SHA-256 digests must be lowercase; v2 .pt artifacts are deserialized with weights and basic data only; inconsistent v2 metadata and incomplete weights are rejected; and model YAML may import only an exact, role-specific default set of classes and functions (no longer the whole torch.nn.* namespace). Direct local and Hugging Face artifacts can extend that set or explicitly opt into unsafe loading for trusted custom code. Artifact model_yaml may no longer use the ptfile constructor keyword or positional args, so a third-party artifact exported with positional args must be re-exported. See the model-format migration notes.
  • Mode-2 neighbor matrices use global atom indices: batched (B, N, M) matrices carry b * N + j with the sentinel B * N, the last atom slot of every system must be a dummy (numbers[:, -1] == 0), and full-3D periodic inputs need aligned (B, N, M, 3) shifts. Matrices with per-system local indices are rejected; convert them with aimnet.nbops.convert_mode2_local_to_global.
  • peratom_loss_fn normalization: per-atom losses now average over real atoms in every neighbor mode. On padded batches the force loss is about 3-5x larger than before; divide the forces weight by about 3 to keep the old effective weighting.
  • Compilation options: passing compile_kwargs={"fullgraph": False} is rejected (compiled inference always uses a full graph), and compile_model=True on a legacy TorchScript .jpt model raises instead of being ignored.
  • Derivatives of models with an embedded tabulated DFT-D3: Hessians, Hessian-vector products, and stress now raise NotImplementedError for a model that carries aimnet.modules.DFTD3 inside its own module tree. Previously they returned values that silently omitted the dispersion curvature and most of the dispersion virial. Registry models are not affected.
  • Removed APIs: LRCoulomb.coul_ewald and LRCoulomb.coul_pme (use LRCoulomb.forward with method="ewald" or method="pme"), aimnet.modules.core.DSequential, aimnet.constants.get_dftd3_param, aimnet.models.utils.has_dftd3_in_config and its aimnet.models re-export, aimnet.train.utils.make_seed, aimnet.ops.lazy_calc_dij_lr, and the empty aimnet.interfaces package (import aimnet.interfaces now fails).
  • Reused calculators return different, correct energies: in 0.1.0, 0.1.1, and 0.2.0 the adaptive neighbor list could silently drop neighbors after the calculator evaluated a sparser system, so a calculator reused across molecules or cells could return wrong energies and forces with no warning (#147). Results from such workflows change in this release; see the neighbor-list entry under Fixed.

Security

  • Verified official model-registry downloads and cached artifacts against their SHA-256 digests before loading. Registry names and aliases now take precedence over same-named implicit local paths, preventing unverified local files from shadowing registry models. Official distributions currently bundle no model artifacts and continue to download them on demand.
  • Restricted v2 .pt artifact deserialization to weights and basic data. Legacy .jpt files are loaded only through the TorchScript loader and must come from a trusted source.
  • Validated Python class and function references in model YAML against a default trusted set. Direct local and Hugging Face artifacts can extend or replace that set, or explicitly select unsafe loading for trusted custom code; registry models always use the fixed default set.
  • Enriched and structurally validated complete Hugging Face metadata before weight access, including unambiguous SRCoulomb parameter recovery. Registry-backed HF fallback now treats digest-verified registry YAML and metadata as authoritative and rejects conflicting family metadata.
  • Forbade the ptfile constructor kwarg anywhere in artifact model_yaml. DispParam.__init__ runs torch.load(ptfile, weights_only=True) on a YAML-supplied path, and the import-path walker never inspected constructor kwargs, so a default-trusted artifact could carry an arbitrary-path read/probe/DoS primitive. The exporter always strips ptfile before an artifact is produced, so no legitimate artifact is affected; training-config loading is untouched.
  • Cross-checked D3TS presence between model_yaml and the has_embedded_d3ts metadata flag during artifact validation. Previously the two were validated independently, so a mislabeled artifact could silently double-count or entirely lose dispersion depending on which direction it was mislabeled.
  • Validated D3TS damping parameters (a1, a2, s8, s6) supplied via artifact model_yaml: they must be finite, non-negative real numbers. These are plain constructor floats outside the state dict, so nothing previously stopped a NaN/Inf value or a1=a2=0 (an undamped 1/d**6 collapse) from loading silently.
  • Forbade positional constructor arguments (args) anywhere in artifact model_yaml. build_module forwards a sibling args list into func(*args, **kwargs), but both constructor-argument guards match keyword names only, and the parameters they protect are not keyword-only: ptfile is DispParam's third positional parameter and D3TS takes its damping parameters first. A positional respelling therefore evaded both under the default trust policy — args: [null, null, <path>] reopened the torch.load arbitrary-path read/probe/DoS primitive (including a filesystem existence oracle via differing exception types) and args: [-1.0, .nan, .inf] reopened the undamped 1/d**6 collapse. Artifact model_yaml is keyword-only in practice: no in-repo YAML and none of the shipped or registry-distributed artifacts use args, so first-party and registry artifacts are unaffected, and rejecting the spelling outright makes the keyword guards sound rather than advisory. A third-party artifact that was exported with positional args before this change must be re-exported. Training-time construction never routes through this validator, so training configs may still use args; aimnet export does route the model YAML through it, so a training YAML that spells a module positionally fails at export with this message rather than at load.
  • Upgraded locked dependencies to clear every open Dependabot alert that has a released fix. All of these alerts were against uv.lock, so they affect the development and CI environments, not what pip install aimnet resolves. The locked torch moves from 2.9.1 to 2.14.1 (with triton 3.8.0), which clears the torch.lstm_cell memory-corruption advisory (fixed in 2.10) and the torch.jit.script advisory (fixed in 2.13). Pillow moves to 13.0.0, which fixes every open Pillow advisory. Development and transitive dependencies were moved to patched versions for certifi, GitPython, Mako, cryptography, idna, msgpack, mkdocs-material, pymdown-extensions, setuptools, tornado, urllib3, and virtualenv. The one remaining alert is a low-severity paramiko issue with no released patch, reached only as a test dependency through fabric and pysisyphus.

Added

  • Added CUDA-graph inference replay for supported stock closed-shell GELU AIMNet2 models, with dense/sparse engines, optional Triton dense AEV kernels, per-layout numerical checks, bounded cache admission, mutation/storage guards, and regular-path fallback. Graph replay is opt-in: the cuda_graph constructor default is False, and cuda_graph=None defers the choice to the AIMNET_CUDA_GRAPH environment variable. compile_model=False remains the default; pass cuda_graph=False to benchmark torch.compile alone. Requested replay that cannot apply (CPU, deterministic=True, mol_idx/cell/nbmat input, stress/Hessian requests, unsupported models, ROCm) warns once per calculator and reason, and AIMNet2Calculator.clear_cuda_graphs() releases the captured graphs and their memory pool. Graph capture requires nvalchemi-toolkit-ops>=0.5.0 and warp-lang>=1.16; with older versions installed the calculator warns and uses the regular path (the package floor stays at nvalchemi-toolkit-ops>=0.4.1). The optional Triton inference kernels (inference_kernels=True) require compute capability 8.0 or newer and otherwise use the equivalent PyTorch expressions. This does not accelerate training or add custom-model/legacy-artifact compatibility.
  • Added Hessians for every long-range Coulomb method and batched Hessians. eval(..., hessian=True) now works with DSF, Ewald, and PME, which raised in 0.2.0; all three are relaxed-charge (they include the charge-response coupling), and Ewald and PME come from the calculator's total-energy autograd, so they are directly comparable for vibrational analysis while DSF still differs by its shifted-force truncation near the cutoff. A 3D batch (B, N, 3) or a flat input with a multi-molecule mol_idx is evaluated per structure and returns stacked (B, N, 3, N, 3) Hessians, or a list when the systems differ in size. The dense Hessian is returned in the force dtype (typically float32). DSF also supports force and stress training now, through the same differentiable torch path; in 0.2.0 it raised for train=True with forces or stress.
  • Added AIMNet2Calculator.hessian_vector_product, a matrix-free Hessian-vector product H @ v for one structure that avoids building the dense (3N, 3N) Hessian. It is exact reverse-mode autograd for every backend and returns float64 for Ewald and PME and the model dtype otherwise. Its eps argument is deprecated and unused; passing it emits a DeprecationWarning.
  • Added compiled AIMNet2 training for energy and forces in neighbor modes 0, 1, and 2, and for stress in explicit-topology modes 1 and 2. Each process captures one symbolic derivative graph whose batch, atom, and neighbor dimensions remain dynamic; there is no application-level graph cache, and the original model remains the sole owner of parameters and checkpoint state.
  • Added DDP support for compiled training. The derivative runner is wrapped before distributed setup so every forward passes through DDP, each rank owns its own compiled graph and may receive different local shapes, and unused derivative-only parameters are handled without bypassing gradient reduction.
  • Added eager stress training through the same strain-derivative implementation used by compiled training and validation. Stress training requires a cell and aligned shift tensors for every explicit neighbor matrix; topology-free mode 0 stress is rejected. Labels are full (B, 3, 3) tensors in eV/Å^3 with the ASE sign convention. The built-in loader yields dense batches, so stress training needs a custom dataset; aimnet train checks the first batch and stops early with an explanation.
  • Added aimnet.calculators.vibrations, a numpy-only harmonic vibrational analysis of the calculator's dense Hessian. analyze_hessian symmetrizes and mass-weights the (N, 3, N, 3) or (3N, 3N) Hessian, projects out rigid translations and rotations with an SVD-orthonormalized basis (three for a single atom, five for a linear molecule, six otherwise), diagonalizes only the vibrational subspace, and returns wavenumbers in cm^-1 with imaginary modes as negative values, vibrational quanta in eV, and unit-norm Cartesian normal modes; vibrational_analysis(calc, data) wraps the hessian=True evaluation for a single structure and warns when the model embeds a dispersion module whose curvature the Hessian currently omits. Unlike ase.vibrations.VibrationsData, which does not project rigid-body motions, or the former sorted(freqs)[6:] recipe, which mishandled linear molecules and could hide an imaginary mode, the result is orientation-independent and correct for linear and near-linear geometries. Masses come from the bundled aimnet.constants table, so no ASE install is required; the geometry-optimization tutorial's thermochemistry step now uses it.
  • Added an opt-in static geometry cache, AIMNet2Calculator(..., cache_static=True), for repeated evaluations of the same resident CUDA geometry. It reuses calculator-built neighbor matrices and the external DFT-D3 energy and force terms; charge and multiplicity are not part of the cache key, so charge scans and similar workflows on one geometry reuse the geometry-dependent work while model outputs are recomputed. It applies to non-periodic single-system CUDA inputs only and is bypassed for Hessians, stress, training mode, and caller-supplied mol_idx or nbmat.
  • Added a manually invoked GPU validation tool for the torch / warp-lang / nvalchemi-toolkit-ops coupling, which CPU CI cannot cover. make gpu-validate builds a resolver-coherent environment for each PyTorch version (2.10 through 2.14 by default), runs the GPU test suite, and diffs full-model energies and forces against a same-run baseline. See docs/gpu_validation.md.
  • Added aimnet download <model...|--all> to prefetch registry model weights into the local cache for offline/HPC use.
  • Added deterministic=True calculator option: routes external DFT-D3 and DSF Coulomb through their differentiable pure-torch paths, making repeated identical evaluations bitwise reproducible on the same machine/build (issue #93). This holds only on the dense CUDA layout (3D input with at most nb_threshold atoms per system, no cell, no Hessian) and for the same batch composition; on the packed neighbor-list layout, used above nb_threshold and for periodic systems, the calculator sorts the neighbor rows it builds (not a caller-supplied nbmat) into a canonical order, so energies are bitwise reproducible on CUDA when torch.use_deterministic_algorithms(True) is also enabled (a one-time warning fires otherwise), while forces are not yet covered (issue #140). CPU always uses the packed layout and has been bitwise repeatable in testing, though that is not yet guaranteed. Ewald/PME kernels are not covered and warn once.
  • Added weekly and manually dispatched strict fleet CI covering every official registry digest, strict-policy artifact load, and exact role-specific YAML defaults.
  • Added aimnet info reporting package/torch/warp versions, CUDA availability, registered kernel ops, and the model cache location.
  • Added a weights pytest marker on modules that need model weights, enabling a verified fully-offline test run (-m "not weights ...") for packaging environments.
  • Added unit tests for the public SizeGroupedDataset.save_h5, AIMNet2Calculator.set_lr_cutoff, and aimnet.train.loss.mse_loss_fn APIs, which are kept.
  • Added targeted unit tests for previously untested code: RegMultiMetric/regression_stats, the aimnet export helper functions (load_sae, bake_sae_into_model, mask_not_implemented_species), SizeGroupedDataset.cv_split/concatenate, train.utils config/parameter helpers, and LRCoulomb constructor validation.

Changed

  • Updated the supported Python and dependency range. Added Python 3.14 support: CI runs the core suite and the TorchSim tests on 3.14, and the ase, hf, and train extras pass the default suite there too. The sella and pysis extras are not tested on 3.14, because the jaxlib version locked for them (0.5.3) has no Python 3.14 wheels; their CI lanes stay on Python 3.11. Legacy .jpt models load on Python 3.14, but PyTorch marks TorchScript as unsupported there and emits a FutureWarning. The supported PyTorch range is 2.10 through 2.14 (raised from 2.8, see Breaking changes): CI runs the core suite against each of those versions on CPU, and the GPU path is validated across the same range with make gpu-validate. The lockfile used for development and CI now resolves torch 2.14.1, triton 3.8.0, warp-lang 1.18.0, nvalchemi-toolkit-ops 0.5.0, numpy 2.4.6 (Python 3.11) and 2.5.4 (Python 3.12+), ase 3.29.0, torch-sim-atomistic 0.6.2, pytorch-ignite 0.5.5, and huggingface-hub 2.3.0. MkDocs is capped below 2.0 in the docs dependency group, because mkdocs-material does not support MkDocs 2.
  • BREAKING: Raised the minimum supported PyTorch version to 2.10. The Torch 2.9 singleton-only sum and broadcast branches were removed. Compiled mode-1 reductions now use the ordinary scatter/gather calculation with unreachable padding rows that prevent singleton-buffer corruption on PyTorch 2.12 without changing any returned value.
  • torch.compile is now a net speedup instead of a slowdown. The compiled model went from 42 graphs with 41 breaks to 4 graphs with 3 breaks by removing Tensor.item() host syncs and other dynamo-unfriendly patterns from the compiled path; on an L40S a compiled energy-and-forces call is about 1.6-1.9x faster than eager, where it was previously slower. Eager results are bit-for-bit unchanged. The CPU backend keeps one protective graph break to avoid an inductor fusion bug in PyTorch releases before 2.11.
  • Compiled inference now always requests a full graph. Passing compile_kwargs={"fullgraph": False} is rejected so graph breaks cannot silently weaken the requested compilation path.
  • Hessian and Hessian-vector-product requests continue to use the original eager AIMNet2 module when compiled inference is enabled; _compiled_forward is only a callable referencing that same module and never replaces the model or duplicates its parameters.
  • BREAKING: peratom_loss_fn now computes mean squared error over real atoms (numbers != 0) in every neighbor mode. This makes padded mode 0 use the same per-atom normalization as modes 1 and 2. Mode-1 atom counts now ignore the final dummy atom and produce one count per molecule, and a dummy with its own molecule-index bucket is folded into the last molecule, so eager and compiled reductions agree. Because the default loader pads nearly every batch, the force loss is about 3-5x larger than before on padded batches (the old formula divided by 3N) and per-atom charge losses 1-1.6x; divide the forces weight by about 3 to reproduce the old effective weighting. The loss is now computed as a masked sum without a device-to-host sync.
  • Training steps with a non-finite loss or gradient are now skipped on every rank (no optimizer step), counted in engine.state.skipped_steps (also logged to wandb as train/skipped_steps, with the skipped step's NaN left out of the loss curve), and logged; TerminateOnNan keeps its end-of-epoch behavior. The check is one fused device-side reduction with a single host sync per step.
  • Requesting compile_model=True for a legacy TorchScript .jpt model now raises immediately. Legacy models remain usable without compilation rather than silently accepting an unused compile option.
  • BREAKING: Added canonical global packed mode 2 for batched sparse neighbor matrices. (B, N, M) neighbor matrices now carry global atom indices b * N + j with the sentinel B * N, every system must reserve its final atom slot as a dummy (numbers[:, -1] == 0), and full-3D periodic inputs must supply aligned integral lattice shifts of shape (B, N, M, 3). Legacy 3D matrices with per-system local indices are rejected on every entry path; convert them with aimnet.nbops.convert_mode2_local_to_global. The converter does not add the dummy row, so a caller whose padded capacity was exactly filled must first extend coord, numbers, and every neighbor matrix by one padded slot. Inputs are validated before any kernel runs (index bounds, per-system ownership, packed sentinel tails, shift alignment, finite coordinates, non-singular cells): once per plain evaluation, with a Hessian request on a batch validating the batch before the split and each subsystem once more, and a standalone model validating every call; the check traces under torch.compile without graph breaks, and a violation surfaces on CUDA and in compiled code as a device-side assertion (RuntimeError), on eager CPU as ValueError. Ewald estimates its splitting parameter and k-space cutoff per system and PME one parameter set per batch, both from the real atoms only, so a mode-2 energy matches the flat evaluation to rounding whatever the padding width, and a system made of dummy rows only contributes zero energy without influencing the others, provided it carries a non-singular cell (copy a real one into padded slots; a zero cell has no reciprocal lattice). A batch in which every system is dummy rows only is rejected, matching the flat path. A single (3, 3) or (1, 3, 3) cell shared by a batch is broadcast to every system, like a (3,) pbc; the pure-torch ConvSV fallback masks the features it gathers for excluded neighbor slots instead of relying on a pre-masked geometry tensor. Because mode-2 callers supply the neighbor lists, nbmat_coulomb must be built at the Ewald/PME real-space cutoff. The declared cutoff is checked against that estimate for whichever list the Coulomb sum resolved to, and a warning is raised when it is shorter or when no cutoff was declared at all; reusing the model's short-range list instead costs of order 1 eV and 0.1 eV/A in forces for a condensed-phase cell. The calculator accepts the additional optional keys nbmat_coulomb, nbmat_dftd3, shifts_coulomb, shifts_dftd3, cutoff_lr, cutoff_coulomb, and cutoff_dftd3. See the calculator docs section "Batched sparse neighbor matrices (mode 2)" for the contract and migration steps.
  • Migrated Ewald and PME Coulomb to the nvalchemi-toolkit-ops energy+autograd API. Inference forces and stress, force and stress training, dense Hessians, and Hessian-vector products all flow through the calculator's total-energy autograd. Ewald energies are bit-identical and forces and stress agree to kernel rounding (at most 2e-06 eV/A). This requires nvalchemi-toolkit-ops 0.4.1: on 0.4.0 an upstream composed-graph charge-gradient bug corrupts PME train-mode forces (max error 4.5 eV/A on a test crystal), so selecting PME on 0.4.0 raises RuntimeError, because the dependency floor alone would not protect an already-installed environment. Neither method uses the upstream direct-output flags that nvalchemi-toolkit-ops 0.4 deprecated, so no upstream DeprecationWarning is emitted.
  • BREAKING: Model loading now routes TorchScript only for .jpt files, gives bare registry names precedence over same-named implicit local paths, rejects inconsistent v2 metadata and incomplete weights, requires lowercase registry SHA-256 digests, and uses exact role-specific default YAML imports instead of the broad torch.nn.* namespace. See the model-format migration notes for remedies.
  • Split artifact checks into envelope, structural, canonical-distribution, and effective-calculator validation. Structurally valid direct and complete custom HF artifacts may explicitly disable external Coulomb or dispersion, while registry, registry-backed HF, and exported artifacts retain canonical action-flag requirements.
  • Made aimnet export validate canonical artifacts before atomically replacing the destination, preserve existing output on failure, record embedded D3TS metadata, and accept explicit trusted custom constructor paths through --model-import-path.
  • Marked legacy .jpt runtime metadata as embedded-LR while retaining format version 1 and embedded-module defaults. Consolidated v2 construction, state loading, and source-routing predicates without expanding the stable top-level models API.
  • A closed-shell model (one charge channel) now warns once per calculator instance when it receives mult != 1. The ASE, PySisyphus, and TorchSim wrappers always pass mult, so an open-shell request previously returned a closed-shell result with no signal.
  • The automatic switch from simple to dsf Coulomb for periodic input is now scoped to that evaluation and restored afterward, even on error. Previously one periodic call permanently moved a gas-phase calculator off its trained full Coulomb. An explicit set_lrcoulomb_method() still persists.
  • Species validation is cached on the identity of the numbers tensor, removing a per-step device-to-host sync from MD and optimization loops; charge validation still runs on every call. The packed (mode 1) message-passing path no longer syncs to read the molecule count.
  • Model families are declared as data in model_registry.yaml (per-family charge support and post-hoc D3(BJ) parameters, and an explicit family: for every model) instead of being parsed from model names and hard-coded in the calculator.
  • Hardened make test: the parallel run now hides CUDA and caps per-worker threads (CUDA_VISIBLE_DEVICES="" OMP_NUM_THREADS=1); previously xdist workers either all initialized the first GPU (OOM on CUDA boxes) or oversubscribed the CPU with per-worker torch thread pools. A new make test-gpu target runs the GPU-marked tests serially on CUDA.
  • Raised the nvalchemi-toolkit-ops floor to 0.4.1 (see the Ewald and PME migration above) and warp-lang to >=1.13,<2. Moving from nvalchemi-toolkit-ops 0.3.1 to 0.4 left energies, charges, and Hessians bit-identical; explicit force and virial outputs shift within float32 accumulation noise (max 6.7e-05 eV/A on a periodic system).
  • Replaced deprecated PyTorch calls: enable_tf32 uses torch.set_float32_matmul_precision, the trainer passes weights_only=True to torch.load explicitly, and the distance path uses torch.linalg.vector_norm (values and gradients identical).
  • Marked the expensive tail of the test suite (~60 test nodes ≥2 s each: torch.compile, model-format roundtrips, dense-Hessian/HVP comparisons, multi-model ASE runs) with the slow marker; the default CPU test run now completes in under two minutes. Run pytest -m slow for the marked tail. The cheapest representative of each critical path (dense Hessian, HVP correctness, legacy .jpt loading, torch.compile smoke) stays in the default set.
  • Silenced the spurious "Warp CUDA error 100" stderr line emitted at import on CPU-only hosts.
  • Made optional-dependency install hints installer-neutral (conda equivalents where they exist; pysisyphus named directly since it is pip-only).

Fixed

  • Restored batched calculator input with a single shared charge or mult (scalar or one-element list) in neighbor mode 1, eager and compiled; the calculator broadcasts it per system and rejects any other length mismatch with a ValueError.
  • Validation with trainer.compile=True no longer retains the compiled derivative graph under no_grad.
  • Refused Hessians, Hessian-vector products, and stress for a model that carries a tabulated DFT-D3 module inside its own module tree, instead of returning a number that silently omits dispersion. That module's energy reaches autograd through a first-order-only autograd.Function: its backward multiplies a saved, graph-free force tensor, so the second derivative is structurally zero, and it returns no gradient for cell, so the periodic image term never contributes. Measured against finite differences, the dispersion Hessian block was absent entirely and about 95% of the dispersion virial was lost, worth 1.68 GPa of pressure on a test crystal, with nothing raised. Energy and forces were always correct and are unaffected. No registry model is affected: dispersion is attached externally, the default artifact policy rejects aimnet.modules.DFTD3 as an untrusted import path, and the exporter strips it. Load a model so the calculator owns dispersion externally, or use D3TS, which differentiates correctly.
  • Detected an affected model by class rather than by the key its dispersion module sits under. The previous test asked whether anything was registered at dftd3/d3bj, which warned for D3TS under that key, where there is no defect, and stayed silent for a DFTD3 under any other key, where there is. The new has_embedded_tabulated_dftd3 predicate tests the class; has_externalizable_dftd3 keeps its key semantics, which are what neighbor-list sizing needs. The narrower warning added to vibrational_analysis is removed, superseded by the calculator refusing the operation with a remedy.
  • Fixed the adaptive neighbor list silently dropping neighbors, which made a reused calculator return different energies for the same geometry depending on what it had evaluated before. AdaptiveNeighborList relied on NeighborOverflowError to grow its buffer, but in matrix mode nvalchemiops never raises it: it reports the true count in num_neighbors while returning only max_neighbors columns, so the retry branch was unreachable and the trim to actual_max was a no-op on an already-truncated list. The shrink heuristic then fed an undersized buffer forward with no way back up, so any evaluation on a sparser system permanently capped every later one (measured: caffeine shifts by 7.8e-03 eV after one single-atom evaluation on the same calculator, and a periodic cell by +79.7 eV / 19.7 eV/A after one sparser evaluation; repeated calls on a truncated list were not even stable). The buffer now grows and rebuilds whenever the reported count exceeds the allocated width. The cluster-tile kernels that CUDA selects for fully periodic float32 input raise instead of truncating; that retry now also grows straight to the reported count with headroom instead of by 1.5x of the current width per attempt (12 rebuilds down to 1 on a 13,824-atom cell). Affects every AdaptiveNeighborList user -- the main, LR, Coulomb and dispersion lists -- on any path that reaches make_nbmat (PBC, CPU, caller-supplied mol_idx, N > nb_threshold, or nb_threshold=0). A freshly constructed calculator was correct only for systems within the initial density estimate of about 0.2 atoms/A^3 -- a 64-atom, 6.4 A periodic cell at a 5 A cutoff was truncated on its very first call -- and those first-call cases are fixed by the same branch; results that were already correct are unchanged.
  • Treated the module tree, not metadata, as ground truth when detecting embedded long-range modules, so an artifact whose metadata flag contradicts its own contents still gets a long-range neighbor list (issue #118, third pass). The previous fix consulted the module tree only when metadata is None; a shipped solvation artifact carries a metadata dict declaring has_embedded_lr=False and has_embedded_d3ts=False while holding an outputs.d3bj submodule, so the detection helper was correct and never reached — lr stayed False and every system above nb_threshold raised KeyError: ['_dftd3', '_lr'] (measured: 119 atoms works, 125 fails). _has_embedded_dispersion carried the same gate, so correcting only has_embedded_lr left cutoff_lr at inf and turned the KeyError into a [125, 8.4e17] allocation overflow; both now inspect the module tree unconditionally. A wrong flag can only ever cause a missing long-range list, never a spurious one. Energies below nb_threshold are unchanged bit-for-bit. Note the existing model_yaml/has_embedded_d3ts artifact cross-check does not catch this case, which carries a D3BJ rather than a D3TS module.
  • Excluded the local weight cache (aimnet/calculators/assets/) from wheels and sdists explicitly, instead of relying on hatchling's .gitignore handling.
  • Fixed embedded-LR models failing with KeyError: nbmat_lr on every flattened evaluation (molecules above nb_threshold on CUDA, any size on CPU, PBC, or Hessians): when metadata declared has_embedded_lr but neither dispersion nor Coulomb could be identified, the calculator built no long-range neighbor list at all despite resolving an all-pairs cutoff_lr for exactly that case. The shared LR list is now built with that cutoff (issue #118).
  • Detected embedded long-range modules from the module tree for pre-metadata artifacts: a model carrying a D3TS, DFTD3/D3BJ, or LRCoulomb submodule but no metadata dict previously had lr=False, so no LR neighbor list was built and flattened evaluations failed with KeyError: nbmat_lr (issue #118, reopened case).
  • Trusted the aimnet.modules.lr.D3TS submodule spelling of the D3TS class in the default artifact allowlist, alongside the existing aimnet.modules.D3TS barrel spelling. The loader machinery that detects D3TS by class name matches the "D3TS" substring regardless of spelling, so an artifact using this spelling was recognized as D3TS by the export layer but rejected by the exact-match allowlist.
  • Fixed SizeGroupedDataset.merge raising KeyError on every realistic dataset. The method iterated for k in other:, but SizeGroupedDataset defines __getitem__ without __iter__, so the loop fell back to the legacy integer sequence protocol: it probes other[0], other[1], ... and stops only on IndexError. Since the mapping is keyed by group size (atom count), the first size missing from a gap-free run starting at 0 raises KeyError instead -- for the bundled test set, whose sizes start at 7, that is the very first probe. It now iterates other.keys(). DataGroup.merge, DataGroup.rename_key, DataGroup.to_dict and SizeGroupedDataset.rename_datakey are unchanged and remain supported; all five are now covered by tests.
  • Fixed DataGroup.cv_split corrupting cross-validation folds: building each fold's train split mutated the shared parts in place via cat(), so later folds contained duplicated samples and validation splits larger than the dataset. Folds are now built without mutating the shared parts.
  • Fixed a crash when torch has CUDA but warp-lang does not (possible with conda-forge variant packages): the AEV kernel gate now checks warp CUDA availability and falls back to the pure-torch path with a one-time warning.
  • Fixed ConvSV raising on float64 or mixed-dtype CUDA input: the Warp kernel is now used only when both operands are float32, and other inputs fall back to the einsum path (float64 on GPU matches CPU to 2e-13).
  • Fixed the Hugging Face registry fallback for a config.json without model_yaml or member_names. The old lookup compared an underscore-normalized slug against dash-separated registry keys, so it never matched and the documented fallback always raised; it now warns and falls back to the registry as documented.

Removed

  • Removed unused aimnet.train.utils.make_seed and aimnet.ops.lazy_calc_dij_lr.
  • Removed unused LRCoulomb.coul_ewald and LRCoulomb.coul_pme convenience wrappers; use LRCoulomb.forward with method="ewald"/"pme".
  • Removed unused aimnet.modules.core.DSequential and aimnet.constants.get_dftd3_param.
  • Removed unused aimnet.models.utils.has_dftd3_in_config (and its aimnet.models re-export).
  • Removed the empty aimnet.interfaces package. It held only a docstring and nothing referenced it; the calculator adapters live in aimnet.calculators. import aimnet.interfaces now raises ImportError.

Documentation

  • Documented CUDA-graph eligibility, defaults and torch.compile precedence, startup/cache-memory costs, differentiation and mutation contracts, dependency limits, and fair backend comparisons.
  • Documented the rxn dipole origin convention: center_coord=False is origin-safe because the family is net-neutral-only; any future charged-system family must ship center_coord=True.
  • Documented offline/HPC installation (cache pre-seeding via aimnet download, AIMNET_CACHE_DIR, WARP_CACHE_PATH) and the model weight hosting immutability policy.
  • Added the AIMNet2-2025 paper citation (DOI and BibTeX) to the README and the documentation home page, and linked the paper from the model documentation and guides.
  • Documented the limits of deterministic=True: the bitwise guarantee covers the dense CUDA layout, the packed layout needs torch.use_deterministic_algorithms(True) for energies, packed forces are not yet covered, and a caller-supplied nbmat is not reordered.
  • Documented a known limitation of aimnet2 and aimnet2_2025: two hydrogens on the same heavy atom that are compressed below an H–H distance of about 0.75 Å can collapse toward 0 Å into a spurious low-energy region. The model pages recommend rejecting starting geometries with any H–H distance below 0.8 Å and checking optimized geometries for H–H distances below 0.6 Å until the next model release (#139).
  • Documented that Hessians are outside the deterministic=True bitwise guarantee: the vmap-based dense Hessian varies by about 1e-5 eV/Ų between runs on CUDA, which is negligible for vibrational frequencies (#143).