External Potential¶
conda-forge availability
Included in the conda-forge package. Always compiled in; no build flags required.
The external potential (ext_pot) interface allows eOn to use any energy and
force calculator by communicating through files and a system call. Because it
requires no compile-time dependencies, ext_pot is always available in every eOn
build, including the conda-forge package.
Tip
If you installed eOn from conda-forge and want to use an MLIP (DeePMD, MACE,
etc.) that is not available through the Metatomic
interface, ext_pot or LAMMPS is the recommended path.
The ASE potentials require additional compile-time flags
that are not enabled in the conda-forge build.
Protocol¶
When the client needs an energy/force evaluation it:
Creates a private exchange directory named
extpot_<pid>_<n>beside the working directory, where<pid>is the client’s process id and<n>counts the potentials it built.Writes the file
from_eon_to_extpotin that directory.Calls the executable (or script) specified by
ext_pot_pathviasystem(), with the exchange directory as the command’s working directory.Reads the file
from_extpot_to_eonthat the script must create there.
The two filenames do not change, so a wrapper that opens them by relative name, as every example below does, needs no edit. The exchange directory is what keeps two clients started in one directory from reading each other’s numbers; the client removes it once the exchange files are consumed, and leaves it in place when a call fails so its contents can be inspected.
Important
ext_pot_path is resolved against the directory eOn runs in when it names an
existing file, so ./ext_pot and an absolute path both work. A command line
with arguments (python wrapper.py) or a name looked up on PATH is passed
to the shell unchanged and therefore has to be written so it resolves from
the exchange directory: give the script an absolute path. On Windows,
cmd.exe does not honor a shebang: a resolved script whose first line names
python, or whose name ends in .py, is invoked as python <script>. A
wrapper that needs to reach files in the directory eOn runs in finds that
directory in the environment variable EON_EXTPOT_RUN_DIR.
Input file (from_eon_to_extpot)¶
The first three lines are the 3x3 box matrix (one row per line, tab-separated).
Each subsequent line contains one atom: atomic_number x y z
(tab-separated, double precision).
box_00 box_01 box_02
box_10 box_11 box_12
box_20 box_21 box_22
6 1.234 5.678 9.012
6 ...
Output file (from_extpot_to_eon)¶
The first line is the total energy (scalar).
Each subsequent line contains the force on one atom: fx fy fz
(space-separated). The atom order must match the input.
-42.12345
0.123 -0.456 0.789
...
Configuration¶
[Potential]
potential = ext_pot
ext_pot_path = /full/path/to/your_wrapper
Important
When running compound jobs (aKMC, process search, etc.) eOn copies
configuration into per-job scratch directories. Use absolute paths for
ext_pot_path and for any model files referenced inside your wrapper.
Examples¶
DeePMD (PyTorch) wrapper¶
The following Python script wraps a DeePMD-kit v3 PyTorch model and can be used
directly as the ext_pot_path target. Save it as, e.g.,
/home/user/scripts/deepmd_extpot and make it executable (chmod +x).
#!/usr/bin/env python
"""eOn ext_pot wrapper for DeePMD-kit (PyTorch backend)."""
import numpy as np
from deepmd.infer import DeepPot
# --- user settings ---
MODEL = "/absolute/path/to/model.pth"
# ----------------------
dp = DeepPot(MODEL)
# Read input
lines = open("from_eon_to_extpot").readlines()
box = np.array([[float(v) for v in l.split()] for l in lines[:3]])
atoms = []
coords = []
for l in lines[3:]:
tok = l.split()
atoms.append(int(tok[0]))
coords.append([float(tok[1]), float(tok[2]), float(tok[3])])
atoms = np.array(atoms)
coords = np.array(coords)
# Evaluate
energy, forces, _ = dp.eval(coords.reshape(1, -1, 3),
box.reshape(1, 9),
atoms)
# Write output
with open("from_extpot_to_eon", "w") as f:
f.write(f"{energy[0]:.15f}\n")
for fx, fy, fz in forces[0]:
f.write(f"{fx:.15f} {fy:.15f} {fz:.15f}\n")
Generic ASE calculator wrapper¶
Any ASE-compatible calculator can be wrapped in a similar fashion:
#!/usr/bin/env python
"""eOn ext_pot wrapper using an ASE calculator."""
import numpy as np
from ase import Atoms
# --- user settings ---
from mace_mp import MACECalculator
calc = MACECalculator(model="/path/to/model.pt", device="cuda")
# ----------------------
lines = open("from_eon_to_extpot").readlines()
box = np.array([[float(v) for v in l.split()] for l in lines[:3]])
numbers = []
positions = []
for l in lines[3:]:
tok = l.split()
numbers.append(int(tok[0]))
positions.append([float(tok[1]), float(tok[2]), float(tok[3])])
system = Atoms(numbers=numbers, positions=positions, cell=box, pbc=True)
system.calc = calc
energy = system.get_potential_energy()
forces = system.get_forces()
with open("from_extpot_to_eon", "w") as f:
f.write(f"{energy:.15f}\n")
for fx, fy, fz in forces:
f.write(f"{fx:.15f} {fy:.15f} {fz:.15f}\n")
Performance considerations¶
Each force call spawns a new process and re-initializes the calculator. For GPU-accelerated MLIPs this overhead is typically small compared to the inference cost, but for very fast potentials the startup cost can dominate. In that scenario consider:
The built-in ASE interface (requires building from source with
-Dwith_python=True -Dwith_ase=True), which embeds the interpreter and avoids per-call process overhead.The Metatomic interface for models that support the metatensor format (compiled into the
conda-forgebuild).The serve mode to expose any eOn potential over RPC to external callers.