Targeted Displacement for Saddle Searches#
By default, eOn picks a random atom as the epicenter for each saddle search displacement. This works well for small systems or when all atoms are equally likely to participate in reactions. For larger, more structured systems — surfaces with defects, supported catalysts, or bulk crystals with vacancies — random selection wastes effort: most saddle searches start far from the action and fail to find relevant transitions.
eOn provides two mechanisms for targeting displacements at the atoms that matter:
Static atom list (
displace_atom_list) — a fixed set of atom indices written directly inconfig.ini. Best when the active atoms are known in advance and don’t change between AKMC states.Dynamic script (
displace_atom_kmc_state_script) — a Python script that is executed once per new AKMC state. The script receives the current geometry, analyses it, and returns the indices of atoms that should be displaced. Best when the active region moves (e.g. a migrating vacancy).
Script Interface#
A displacement script must satisfy the following contract:
It receives the path to a
.confile as its sole positional argument.It must print a comma-separated list of 0-based atom indices to stdout (e.g.
3, 7, 12, 45).All other output (logging, warnings, progress bars) must go to stderr. Anything on stdout that is not a valid index list will cause a parse error.
The script is run once per new AKMC state; the result is cached in
state.infoand reused for all saddle searches launched from that state.The path in
displace_atom_kmc_state_scriptcan be relative (resolved against the eOn root directory) or absolute.Scripts can use PEP 723 inline metadata so they are runnable with
uvxwithout a separate virtual environment.
Example 1: Vacancy Diffusion in Cu#
The examples/akmc-cu-vacancy/ directory contains a complete AKMC setup for a
Cu crystal with a single vacancy, using OVITO’s Polyhedral Template Matching
(PTM) to identify defect atoms.
The Idea#
In a nearly perfect FCC crystal, the only atoms likely to participate in vacancy migration are those whose local environment deviates from bulk FCC — i.e. the atoms adjacent to the vacancy. PTM classifies each atom’s local neighbourhood into crystal structure types (FCC, HCP, BCC, icosahedral, or “other”). Atoms that are not FCC are the natural candidates for displacement.
Configuration#
The relevant config.ini block:
[Saddle Search]
method = min_mode
min_mode_method = dimer
max_energy = 300
displace_listed_atom_weight = 1.0
displace_radius = 3.0
displace_magnitude = 0.01
displace_atom_kmc_state_script = ptmdisp.py
displace_all_listed = true
Key settings:
displace_listed_atom_weight = 1.0with all other weights at their defaults (0) ensures that only listed atoms are used as epicenters.displace_all_listed = truemeans every atom returned by the script is displaced simultaneously, not just one picked at random.displace_radius = 3.0also includes neighbours within 3 Å of any listed atom.displace_atom_kmc_state_script = ptmdisp.pypoints to the script (relative to the eOn root directory).
The PTM Script#
ptmdisp.py is a PEP 723 inline script.
Its core logic:
Reads the
.confile with ASE.Converts the structure to an OVITO pipeline.
Applies the PTM modifier to classify each atom.
Selects atoms that are not FCC.
Prints their indices to stdout.
You can run it standalone to inspect the output:
uvx ptmdisp.py pos.con
# Output: 42, 43, 51, 67, ...
uvx ptmdisp.py pos.con --verbose
# Prints logging info to stderr, indices to stdout
Data Flow#
New AKMC state
│
▼
eOn server runs: ptmdisp.py reactant.con
│
▼
Script prints indices to stdout → parsed into displace_atom_list
│
▼
Indices cached in state.info
│
▼
DisplacementManager uses list for all saddle searches in this state
Example 2: Adsorbate on a Catalyst Surface#
Consider a CO molecule adsorbed on a Pt(111) surface. During an AKMC simulation the adsorbate can diffuse, rotate, or desorb — but the relevant atoms are always the adsorbate itself plus a handful of nearby surface atoms. Displacing random bulk Pt atoms far from the CO would be wasteful.
Strategy#
Identify the adsorbate atoms (C and O) by element.
Expand the selection to include all Pt atoms within a cutoff radius of any adsorbate atom.
Return the combined set of indices.
The Script#
The file examples/akmc-cu-vacancy/adsorbate_region.py implements this
approach using only ASE (no OVITO dependency):
uvx adsorbate_region.py pos.con --adsorbate-elements C O --cutoff 4.0
# Output: 0, 1, 23, 24, 31, ...
The script:
Reads the
.confile withase.io.read.Finds atoms matching the specified adsorbate elements.
Computes distances from every other atom to the nearest adsorbate atom.
Selects all atoms within the cutoff distance.
Merges the adsorbate indices with the nearby-atom indices and prints the result.
Tip
You can also select adsorbate atoms by z-coordinate instead of element, which
is useful when the adsorbate and surface share the same element. Pass
--z-above <threshold> to select atoms above a given height.
Configuration#
[Saddle Search]
displace_listed_atom_weight = 1.0
displace_radius = 3.0
displace_magnitude = 0.01
displace_atom_kmc_state_script = adsorbate_region.py --adsorbate-elements C O --cutoff 4.0
displace_all_listed = true
Static List Alternative#
For systems where the active atoms are known ahead of time and do not change
between states, a static displace_atom_list is simpler — no script needed:
[Saddle Search]
displace_atom_list = 0, 1, 2
displace_listed_atom_weight = 1.0
displace_all_listed = true
displace_radius = 3.0
displace_magnitude = 0.01
This displaces atoms 0, 1, and 2 (plus their neighbours within 3 Å) on every saddle search. It is suitable for small molecules, known defect sites, or any situation where the active region is fixed.
Client-Side listed_atoms#
The displacement can also be handled entirely by the C++ client, without the server writing a displacement file. Set:
[Saddle Search]
client_displace_type = listed_atoms
displace_atom_list = 0, 1, 2
In this mode the client reads displace_atom_list directly from the INI config
and uses those atoms as epicenter candidates. This is useful for simple cases
where server-side scripting is unnecessary.