Skip to content

Commit d7019ab

Browse files
committed
Address review comments on PR metatensor#167
- Restructure as flat metatomic_torchsim package (no namespace shims) - Rename _calculator.py to _model.py (matches MetatomicModel class) - Convert all md docs to rst under docs/src/engines/ - Remove separate docs/ directory, wire into main doc toctree - Add version bounds: metatomic-torch >=0.1.11,<0.2, torch-sim >=0.5,<0.6 - Remove towncrier/tbump config (premature for unreleased package) - Update URLs to point to torch-sim engine docs - Inline test helpers into fixtures, convert _make_ni_atoms_2 to fixture - Update imports: metatomic.torchsim -> metatomic_torchsim - Remove **kwargs from AtomisticModel.save() (no purpose) - Update README: reference upet instead of pet-mad
1 parent 2d7b87e commit d7019ab

25 files changed

Lines changed: 397 additions & 634 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.. _torchsim-architecture:
2+
3+
Architecture
4+
============
5+
6+
This page explains how ``MetatomicModel`` bridges TorchSim and
7+
metatomic.
8+
9+
SimState vs list of System
10+
--------------------------
11+
12+
TorchSim represents a simulation as a single batched ``SimState``
13+
containing all atoms from all systems, with a ``system_idx`` tensor
14+
tracking ownership. Metatomic expects a ``list[System]`` where each
15+
``System`` holds one periodic structure.
16+
17+
``MetatomicModel.forward`` converts between these representations:
18+
19+
1. Split the batched positions and atomic numbers by ``system_idx``
20+
2. Create one ``System`` per sub-structure with its own cell
21+
3. Call the model on the list of systems
22+
4. Concatenate results back into batched tensors
23+
24+
Forces via autograd
25+
-------------------
26+
27+
Metatomic models typically output only total energies. Forces are
28+
computed as the negative gradient of the energy with respect to atomic
29+
positions::
30+
31+
F_i = -dE/dr_i
32+
33+
Before calling the model, each system's positions are detached and set
34+
to ``requires_grad_(True)``. After the forward pass,
35+
``torch.autograd.grad`` computes the derivatives.
36+
37+
Stress via the strain trick
38+
---------------------------
39+
40+
Stress is computed using the Knuth strain trick. An identity strain
41+
tensor (3x3, ``requires_grad=True``) is applied to both positions and
42+
cell vectors::
43+
44+
r' = r @ strain
45+
h' = h @ strain
46+
47+
The stress per system is then::
48+
49+
sigma = (1/V) * dE/d(strain)
50+
51+
where V is the cell volume. This gives the full 3x3 stress tensor
52+
without finite differences.
53+
54+
Neighbor lists
55+
--------------
56+
57+
Models specify what neighbor lists they need via
58+
``model.requested_neighbor_lists()``, which returns a list of
59+
``NeighborListOptions`` (cutoff radius, full vs half list).
60+
61+
The wrapper computes these using:
62+
63+
- **vesin**: Default backend for both CPU and GPU. Handles half and
64+
full neighbor lists. Systems on non-CPU/CUDA devices are temporarily
65+
moved to CPU for the computation.
66+
- **nvalchemiops**: Used automatically on CUDA for full neighbor lists
67+
when installed. Keeps everything on GPU, avoiding host-device
68+
transfers.
69+
70+
The decision happens per-call in ``_compute_requested_neighbors``: if
71+
all systems are on CUDA and nvalchemiops is available, full-list
72+
requests go through nvalchemi while half-list requests still use vesin.
73+
74+
Why a separate package
75+
----------------------
76+
77+
metatomic-torchsim has its own versioning, release schedule, and
78+
dependency set (``torch-sim-atomistic``). Keeping it separate from
79+
metatomic-torch avoids forcing a torch-sim dependency on users who only
80+
need the ASE calculator or other integrations.
81+
82+
The package is pure Python with no compiled extensions, making it
83+
lightweight to install.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
.. _torchsim-batched:
2+
3+
Batched simulations
4+
===================
5+
6+
TorchSim supports batching multiple systems into a single ``SimState``
7+
for efficient parallel evaluation on GPU. ``MetatomicModel`` handles
8+
this transparently.
9+
10+
Creating a batched state
11+
------------------------
12+
13+
Pass a list of ASE ``Atoms`` objects to ``atoms_to_state``:
14+
15+
.. code-block:: python
16+
17+
import ase.build
18+
import torch_sim as ts
19+
from metatomic_torchsim import MetatomicModel
20+
21+
model = MetatomicModel("model.pt", device="cpu")
22+
23+
atoms_list = [
24+
ase.build.bulk("Cu", "fcc", a=3.6, cubic=True),
25+
ase.build.bulk("Ni", "fcc", a=3.52, cubic=True),
26+
ase.build.bulk("Al", "fcc", a=4.05, cubic=True),
27+
]
28+
29+
sim_state = ts.io.atoms_to_state(atoms_list, model.device, model.dtype)
30+
31+
Evaluating the batch
32+
--------------------
33+
34+
A single forward call evaluates all systems:
35+
36+
.. code-block:: python
37+
38+
results = model(sim_state)
39+
40+
The output shapes reflect the batch:
41+
42+
- ``results["energy"]`` has shape ``[3]`` (one energy per system)
43+
- ``results["forces"]`` has shape ``[n_total_atoms, 3]`` (all atoms
44+
concatenated)
45+
- ``results["stress"]`` has shape ``[3, 3, 3]`` (one 3x3 tensor per
46+
system)
47+
48+
How system_idx works
49+
--------------------
50+
51+
``SimState`` tracks which atom belongs to which system via the
52+
``system_idx`` tensor. For three 4-atom systems, ``system_idx`` looks
53+
like::
54+
55+
[0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
56+
57+
``MetatomicModel.forward`` uses this to split the batched positions and
58+
types into per-system ``System`` objects before calling the underlying
59+
model.
60+
61+
Batch consistency
62+
-----------------
63+
64+
Energies computed in a batch match those computed individually. This is
65+
guaranteed because each system gets its own neighbor list and
66+
independent evaluation. The existing test
67+
``test_energy_consistency_single_vs_batch`` validates this property.
68+
69+
Performance considerations
70+
--------------------------
71+
72+
Batching is most beneficial on GPU, where the neighbor list computation
73+
and model forward pass can run in parallel across systems. On CPU, the
74+
speedup comes from reduced Python overhead (one call instead of N).
75+
76+
For very large systems or many small ones, adjust the batch size to fit
77+
in GPU memory. TorchSim does not impose a maximum batch size, but each
78+
system gets its own neighbor list, so memory scales with the sum of
79+
per-system sizes.
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.. _torchsim-getting-started:
2+
3+
Getting started
4+
===============
5+
6+
This tutorial walks through running a short NVE molecular dynamics
7+
simulation with a metatomic model and TorchSim.
8+
9+
Prerequisites
10+
-------------
11+
12+
Install the package and its dependencies:
13+
14+
.. code-block:: bash
15+
16+
pip install metatomic-torchsim
17+
18+
You also need a saved metatomic model file (``.pt``). If you have a
19+
metatrain checkpoint (``.ckpt``), install metatrain as well:
20+
21+
.. code-block:: bash
22+
23+
pip install metatrain
24+
25+
Load the model
26+
--------------
27+
28+
.. code-block:: python
29+
30+
from metatomic_torchsim import MetatomicModel
31+
32+
model = MetatomicModel("path/to/model.pt", device="cpu")
33+
34+
The wrapper detects the model's dtype and supported devices
35+
automatically. Pass ``device="cuda"`` to run on GPU.
36+
37+
Build a simulation state
38+
------------------------
39+
40+
TorchSim works with ``SimState`` objects. Convert ASE ``Atoms`` using
41+
``torch_sim.io.atoms_to_state``:
42+
43+
.. code-block:: python
44+
45+
import ase.build
46+
import torch_sim as ts
47+
48+
atoms = ase.build.bulk("Si", "diamond", a=5.43, cubic=True)
49+
sim_state = ts.io.atoms_to_state([atoms], model.device, model.dtype)
50+
51+
Evaluate the model
52+
------------------
53+
54+
Call the model on the simulation state to get energies, forces, and
55+
stresses:
56+
57+
.. code-block:: python
58+
59+
results = model(sim_state)
60+
61+
print("Energy:", results["energy"]) # shape [1]
62+
print("Forces:", results["forces"]) # shape [n_atoms, 3]
63+
print("Stress:", results["stress"]) # shape [1, 3, 3]
64+
65+
Run NVE dynamics
66+
----------------
67+
68+
Use TorchSim's Velocity Verlet integrator:
69+
70+
.. code-block:: python
71+
72+
from torch_sim.integrators import VelocityVerletIntegrator
73+
74+
integrator = VelocityVerletIntegrator(
75+
model=model,
76+
state=sim_state,
77+
dt=1.0, # femtoseconds
78+
)
79+
80+
for step in range(100):
81+
sim_state = integrator.step(sim_state)
82+
if step % 10 == 0:
83+
energy = model(sim_state)["energy"].item()
84+
print(f"Step {step:3d} E = {energy:.4f} eV")
85+
86+
The total energy should remain approximately constant in an NVE
87+
simulation, which serves as a basic sanity check for your model.
88+
89+
Next steps
90+
----------
91+
92+
- :ref:`torchsim-model-loading` covers all supported input formats
93+
- :ref:`torchsim-batched` explains running multiple systems at once
94+
- :ref:`torchsim-architecture` describes the internals
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
.. _torchsim-model-loading:
2+
3+
Loading models
4+
==============
5+
6+
``MetatomicModel`` accepts several input formats. Each section below
7+
shows one loading pattern.
8+
9+
From a saved ``.pt`` file
10+
-------------------------
11+
12+
The most common case. Pass the path to a TorchScript-exported metatomic
13+
model:
14+
15+
.. code-block:: python
16+
17+
from metatomic_torchsim import MetatomicModel
18+
19+
model = MetatomicModel("path/to/model.pt", device="cpu")
20+
21+
The file must exist and contain a valid ``AtomisticModel``. A
22+
``ValueError`` is raised if the path does not exist.
23+
24+
From a metatrain checkpoint
25+
---------------------------
26+
27+
Pass a ``.ckpt`` path to load a metatrain checkpoint directly. This
28+
requires the ``metatrain`` package:
29+
30+
.. code-block:: python
31+
32+
model = MetatomicModel("path/to/checkpoint.ckpt")
33+
34+
The checkpoint is exported to an ``AtomisticModel`` internally.
35+
36+
PET-MAD shortcut
37+
----------------
38+
39+
The string ``"pet-mad"`` downloads and loads the PET-MAD universal
40+
model:
41+
42+
.. code-block:: python
43+
44+
model = MetatomicModel("pet-mad")
45+
46+
This also requires ``metatrain`` to be installed. The model weights are
47+
fetched from HuggingFace on first use.
48+
49+
From a Python AtomisticModel
50+
-----------------------------
51+
52+
If you already have an ``AtomisticModel`` instance (for example, built
53+
programmatically):
54+
55+
.. code-block:: python
56+
57+
from metatomic.torch import AtomisticModel
58+
59+
atomistic_model = build_my_model() # returns AtomisticModel
60+
model = MetatomicModel(atomistic_model, device="cuda")
61+
62+
From a TorchScript RecursiveScriptModule
63+
-----------------------------------------
64+
65+
If you have a scripted model loaded via ``torch.jit.load``:
66+
67+
.. code-block:: python
68+
69+
import torch
70+
71+
scripted = torch.jit.load("model.pt")
72+
model = MetatomicModel(scripted, device="cpu")
73+
74+
The script module must have ``original_name == "AtomisticModel"``.
75+
Otherwise a ``TypeError`` is raised.
76+
77+
Selecting a device
78+
------------------
79+
80+
By default, ``MetatomicModel`` picks the best device from the model's
81+
``supported_devices``. Override with the ``device`` parameter:
82+
83+
.. code-block:: python
84+
85+
model = MetatomicModel("model.pt", device="cuda:0")
86+
87+
Extensions directory
88+
--------------------
89+
90+
Some models require compiled TorchScript extensions. Point to their
91+
location with ``extensions_directory``:
92+
93+
.. code-block:: python
94+
95+
model = MetatomicModel(
96+
"model.pt",
97+
extensions_directory="path/to/extensions/",
98+
)

docs/src/engines/torch-sim.rst

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ How to use the code
3838
3939
import ase.build
4040
import torch_sim as ts
41-
from metatomic.torchsim import MetatomicModel
41+
from metatomic_torchsim import MetatomicModel
4242
4343
model = MetatomicModel("model.pt", device="cpu")
4444
@@ -50,5 +50,11 @@ How to use the code
5050
print(results["forces"]) # shape [n_atoms, 3]
5151
print(results["stress"]) # shape [1, 3, 3]
5252
53-
For more details, see the `metatomic-torchsim documentation
54-
<https://docs.metatensor.org/metatomic/latest/torchsim/>`_.
53+
.. toctree::
54+
:maxdepth: 2
55+
:caption: torch-sim integration
56+
57+
torch-sim-getting-started
58+
torch-sim-model-loading
59+
torch-sim-batched
60+
torch-sim-architecture

python/metatomic_torch/metatomic/torch/model.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,9 +549,7 @@ def export(self, file: str, collect_extensions: Optional[str] = None):
549549
)
550550
return self.save(file, collect_extensions)
551551

552-
def save(
553-
self, file: Union[str, Path], collect_extensions: Optional[str] = None, **kwargs
554-
):
552+
def save(self, file: Union[str, Path], collect_extensions: Optional[str] = None):
555553
"""Save this model to a file that can then be loaded by simulation engine.
556554
557555
The model will be saved with `requires_grad=False` for all parameters.

0 commit comments

Comments
 (0)