pyeonclient (Python client API)¶
Added in version 2.16: In-process client: first-class algorithm objects on :class:~pyeonclient.Matter,
not a working directory and not the eonclient binary alone.
pyeonclient exposes the eOn C++
client algorithms to Python with nanobind. Geometries are
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 |
|
|
Min-mode |
|
dimer / lanczos / davidson |
Saddle search |
|
|
NEB |
|
|
Hessian |
|
|
Prefactor |
|
|
Dynamics |
|
|
Monte Carlo |
|
|
Basin hopping |
|
|
Process search |
|
|
Structure compare |
|
|
TAD |
|
|
Parallel replica |
|
|
Safe hyperdynamics |
|
|
Replica exchange |
|
|
GP surrogate NEB |
|
|
Opaque batch |
|
any |
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¶
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
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¶
Added in version 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 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.
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.
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
Minimization¶
out, ok = matter.relax() # like LBFGS(atoms).run(fmax=...)
print(ok, matter.potential_energy, matter.max_force)
Min-mode (Dimer)¶
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¶
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:
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 Nudged Elastic Band 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¶
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:
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)::
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)::
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).