---
myst:
html_meta:
"description": "pyeonclient Python API: Matter, min-mode (Dimer/Lanczos/Davidson), MinModeSaddleSearch, NEB, Hessian, Prefactor."
"keywords": "pyeonclient, Dimer, saddle search, NEB, Hessian, Matter, ASE"
---
# pyeonclient (Python client API)
```{versionadded} 2.16
In-process client: first-class algorithm objects on :class:`~pyeonclient.Matter`,
not a working directory and not the `eonclient` binary alone.
```
[pyeonclient](https://pypi.org/project/pyeonclient/) exposes the eOn **C++
client algorithms** to Python with nanobind. Geometries are
{py:class}`~pyeonclient.Matter`. Each method is a live class you construct and
call — the same engines the binary uses.
| Algorithm | Python class / function | Binary analogue |
|-----------|-------------------------|-----------------|
| Local min | `Matter.relax()` | `job = minimization` |
| Min-mode | `Dimer`, `ImprovedDimer`, `Lanczos`, `Davidson` | dimer / lanczos / davidson |
| Saddle search | `MinModeSaddleSearch` | `job = saddle_search` |
| NEB | `NudgedElasticBand` | `job = nudged_elastic_band` |
| Hessian | `Hessian` | `job = hessian` |
| Prefactor | `get_prefactors` | `job = prefactor` |
| Dynamics | `run_dynamics` | `job = dynamics` |
| Monte Carlo | `run_monte_carlo` | `job = monte_carlo` |
| Basin hopping | `run_basin_hopping` | `job = basin_hopping` |
| Process search | `process_search` | `job = process_search` |
| Structure compare | `structures_equal` / `structure_distance` | `job = structure_comparison` |
| TAD | `run_tad` → `TADJob` | `job = tad` |
| Parallel replica | `run_parallel_replica` → `ParallelReplicaJob` | `job = parallel_replica` |
| Safe hyperdynamics | `run_safe_hyperdynamics` → `SafeHyperJob` | `job = safe_hyperdynamics` |
| Replica exchange | `run_replica_exchange` → `ReplicaExchangeJob` | `job = replica_exchange` |
| GP surrogate NEB | `run_gp_surrogate_neb` → `GPSurrogateJob` (gated) | `WITH_GP_SURROGATE` |
| Opaque batch | `make_job` + `Job.run` | any `JobType` via factory |
`make_job(params)` still constructs every `JobType` the C++ factory supports
(process search, dynamics, basin hopping, TAD, …) for workdir-oriented runs.
Prefer the first-class classes when you want Matter in / Matter out.
## Install
```{code-block} bash
pip install pyeonclient
pip install 'pyeonclient[ase]'
pip install 'rgpot>=2.4.2' # multi-ABI engines for Metatomic via RGPOT
```
Typed Dimer/NEB specs come from the shared **[eon-schema](project:../devdocs/eon-schema.md)**
package (`pip install 'pyeonclient[models]'` → `eon-schema>=0.2`). Job-config
pydantic models used in docs live there too (`eon.schema` is a re-export for
eon-akmc).
## Metatomic force backends
```{versionadded} 2.17
```
Named force factories live in ``pyeonclient.backends`` (``make_backend`` /
``list_backends``). Fat Metatomic, RGPOT ``libmetatomic_engine.so``, and ASE
``MetatomicCalculator`` all produce a pyeonclient ``Potential`` for the same
``.pt`` model. See [Metatomic backends](project:rgpot_metatomic.md) for the
API, packaging notes, and a PET-MAD single-point benchmark plot.
## Mutation policy (`inplace`)
Algorithms that change geometry **do not mutate caller-owned Matter by default**.
Pass ``inplace=True`` to write into the input object.
```{code-block} python
out, ok = matter.relax() # copy-then-relax; matter unchanged
out, ok = matter.relax(inplace=True) # mutates matter
saddle, status = pyec.min_mode_saddle_search(
matter, mode, E0, params, pot) # matter unchanged
saddle, status = pyec.min_mode_saddle_search(
matter, mode, E0, params, pot, inplace=True)
```
Same for ``run_dynamics``, ``run_monte_carlo``, ``run_basin_hopping``,
``process_search``, ``run_tad``, ``run_parallel_replica``,
``run_safe_hyperdynamics``, and ``run_replica_exchange``. There are **no**
silent mutators: every algorithm takes ``inplace=False`` by default.
```{code-block} python
out = pyec.TAD(matter, params, pot).run(matter, params, pot) # TADJob; matter unchanged
out = pyec.run_parallel_replica(matter, params, pot) # ParallelReplicaJob
out = pyec.run_safe_hyperdynamics(matter, params, pot)
out = pyec.run_replica_exchange(matter, params, pot)
out = pyec.TAD(matter, params, pot).run(matter, params, pot, inplace=True) # updates matter
```
## Shared setup
Matter-first (preferred for new code). One ``Parameters`` is shared with
``make_backend`` so pot type and job knobs stay consistent:
```{code-block} python
import numpy as np
from pyeonclient import Matter, Parameters
from pyeonclient.backends import make_backend
params = Parameters()
params.opt_converged_force = 0.01
params.opt_max_iterations = 1000
pot = make_backend("lj", params=params) # or "metatomic", "ase_metatomic", …
matter = Matter(pot, params)
matter.resize(n)
matter.positions = ... # (n, 3) float64
matter.cell = np.eye(3) * L
matter.atomic_numbers = ...
matter.masses = ...
# or: from pyeonclient import from_ase; matter = from_ase(atoms, pot, params)
```
INI / PotType path (eonclient workdir parity) still works::
params = load_parameters("config.ini")
pot = make_potential(params.potential, params)
## Minimization
```{code-block} python
out, ok = matter.relax() # like LBFGS(atoms).run(fmax=...)
print(ok, matter.potential_energy, matter.max_force)
```
## Min-mode (Dimer)
```{code-block} python
params.dimer_rotations_max = 10
params.dimer_opt_method = "cg" # cg | lbfgs | sd
direction = np.random.default_rng(0).normal(size=matter.positions.shape)
# Default method is "improved"
d = pyec.Dimer(matter, params, pot)
# d = pyec.Dimer(matter, params, pot, method="classic")
# d = pyec.Dimer(matter, params, pot, method="improved", accelerant="gp") # WITH_GPRD
d.compute(matter, direction)
print(d.eigenvalue, d.eigenvector)
```
Low-level engines remain: ``ClassicDimer``, ``ImprovedDimer``, ``Lanczos``,
``Davidson``. Prefer ``Dimer(method=..., accelerant=...)``.
Parameters: `dimer_improved`, `dimer_rotation_angle`, `dimer_converged_angle`,
`dimer_max_iterations`, `dimer_opt_method`, `dimer_rotations_max` /
`_min`, `dimer_torque_max` / `_min`, `dimer_remove_rotation`.
## Single-ended saddle search
```{code-block} python
params.saddle_minmode_method = "dimer" # dimer | lanczos | davidson
params.saddle_max_iterations = 1000
params.saddle_max_energy = 20.0
# Displace from the minimum, then search
mode = dimer.eigenvector # or a random (n, 3) guess
E_react = matter.potential_energy
ss = pyec.MinModeSaddleSearch(matter, mode, E_react, params, pot)
status = ss.run() # mutates matter → saddle geometry
print(pyec.saddle_status_message(status))
print(ss.eigenvalue, ss.iteration, ss.force_calls)
saddle = matter # now at the saddle
```
`SaddleStatus` enumerates the same codes as the C++ client (`GOOD`,
`BAD_MAX_ITERATIONS`, `DIMER_LOST_MODE`, …).
Parameters: `saddle_method`, `saddle_minmode_method`, `saddle_max_iterations`,
`saddle_max_energy`, `saddle_displace_magnitude`, `saddle_displace_type`,
`saddle_converged_force`, `saddle_remove_rotation`.
## NEB
Use **eOn-native** path initializers (IDPP / SIDPP / linear). Do not pull
ASE ``interpolate("idpp")`` into a pyeonclient band — the same engines live
in ``helpers::neb_paths`` and are already what the endpoint NEB constructor
uses.
Typed knobs live on :class:`~pyeonclient.NebSpec`. Compose the same steps as a
workdir job without a package alias:
```{code-block} python
from pyeonclient import (
NEB,
NebSpec,
Parameters,
PathInit,
append_timing,
from_ase,
pot_registry_total_force_calls,
steady_clock_now,
write_neb_results,
write_potcall_summary,
)
from pyeonclient.backends import make_backend
params = Parameters()
spec = NebSpec(
n_images=10,
path_init=PathInit.idpp,
energy_weighted=True,
ci_mmf=True,
max_iterations=1000,
force_tolerance=0.01,
)
spec.apply_to_parameters(params)
pot = make_backend("metatomic", model_path="pet-mad.pt", params=params)
initial = from_ase(reactant, pot, params)
final = from_ase(product, pot, params)
t0 = steady_clock_now()
f0 = pot_registry_total_force_calls()
neb = NEB(initial, final, params, pot, spec=spec)
status = neb.compute()
if status.name == "GOOD":
neb.find_extrema()
write_neb_results(neb, params, pot_registry_total_force_calls() - f0)
write_potcall_summary("_potcalls.json")
append_timing("results.dat", t0)
```
Explicit path object still available::
path = neb_idpp_path(initial, final, n_intermediate=10, parameters=params)
band = NudgedElasticBand(path, params, pot)
band.compute()
Helpers: ``neb_linear_path``, ``neb_idpp_path``, ``neb_idpp_collective_path``,
``neb_sidpp_path``, ``neb_initial_path``. See {doc}`neb` for energy-weighted
springs and OCINEB (``neb_ci_mmf``, …).
GP-surrogate NEB is still the same **NEB** idea with a GP accelerant on the
band engine (``WITH_GP_SURROGATE``), not a separate product noun — and **not**
prefactor / Parallel Replica.
## Hessian and prefactors
```{code-block} python
atoms = pyec.all_free_atoms(matter) # int64 indices
hess = pyec.Hessian(params, matter)
H = hess.get_hessian(matter, atoms) # (3n, 3n)
freqs = hess.get_freqs(matter, atoms)
pref1, pref2 = pyec.get_prefactors(params, min1, saddle, min2)
```
Parameters: `hessian_atom_list`, `hessian_zero_freq_value`, `prefactor_rate`
(`htst` / `qqhtst`), `prefactor_filter_scheme`, `prefactor_within_radius`, …
## Job factory (full JobType surface)
Every type in `JobType` that the C++ `makeJob` factory implements is available
as a workdir-oriented job:
```{code-block} python
params.job = pyec.JobType.Saddle_Search # or Process_Search, Dynamics, …
job = pyec.make_job(params) # reads cwd files like eonclient
files = job.run()
pyec.write_potcall_summary()
```
Use this for process search, dynamics, basin hopping, TAD, parallel replica,
Monte Carlo, GP surrogate, structure comparison, etc., until those gain
dedicated Matter-first classes. The algorithms above are already first-class.
## ASE bridge
Geometry (always array-copy, fast)::
```{code-block} python
matter = pyec.from_ase(atoms, pot, params)
atoms = pyec.to_ase(matter)
```
**Seamless calculator** — wrap a live ASE Calculator as the Matter PEF
(no file script, no ``-Dwith_ase``)::
```{code-block} python
from ase.calculators.emt import EMT
atoms.calc = EMT()
matter = pyec.from_ase(atoms) # uses atoms.calc
# or
pot = pyec.potential_from_ase(atoms.calc)
matter = pyec.from_ase(atoms, pot, params)
```
Requires `pip install 'pyeonclient[ase]'` and ASE installed at runtime.
Not thread-safe across images (one calculator object).
## Related
- {doc}`dimer`, {doc}`neb`, {doc}`hessian`, {doc}`prefactor`, {doc}`saddle_search`
- {doc}`/devdocs/pyeonclient-pypi` — wheels
- [Cookbook: eOn + PET-MAD NEB](https://atomistic-cookbook.org/examples/eon-pet-neb/eon-pet-neb.html)