--- myst: html_meta: "description": "Guide to using the external potential (ext_pot) interface in eOn for wrapping arbitrary calculators via file-based communication." "keywords": "eOn external potential, ext_pot, MLIP, DeePMD, wrapper script, file interface" --- # External Potential ```{admonition} conda-forge availability :class: tip 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](project:metatomic_pot.md) interface, `ext_pot` or [LAMMPS](project:lammps_pot.md) is the recommended path. The [ASE](project:ase_pot.md) 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: 1. Writes the file `from_eon_to_extpot` in the working directory. 2. Calls the executable (or script) specified by `ext_pot_path` via `system()`. 3. Reads the file `from_extpot_to_eon` that the script must create. ### 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 ```{code-block} ini [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`). ```{code-block} python :caption: deepmd_extpot #!/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: ```{code-block} python :caption: ase_extpot #!/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](project:ase_pot.md) (requires building from source with `-Dwith_python=True -Dwith_ase=True`), which embeds the interpreter and avoids per-call process overhead. - The [Metatomic](project:metatomic_pot.md) interface for models that support the metatensor format (compiled into the `conda-forge` build). - The [serve mode](project:serve_mode.md) to expose any eOn potential over RPC to external callers.