--- myst: html_meta: "description": "Tutorial on using targeted displacement scripts and atom lists for saddle searches in eOn." "keywords": "eOn displacement, saddle search, vacancy diffusion, adsorbate, PTM, OVITO, targeted displacement" --- # 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: 1. **Static atom list** (`displace_atom_list`) — a fixed set of atom indices written directly in `config.ini`. Best when the active atoms are known in advance and don't change between AKMC states. 2. **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 `.con` file 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.info` and reused for all saddle searches launched from that state. - The path in `displace_atom_kmc_state_script` can be **relative** (resolved against the eOn root directory) or **absolute**. - Scripts can use [PEP 723](https://peps.python.org/pep-0723/) inline metadata so they are runnable with `uvx` without 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: ```{code-block} ini :caption: examples/akmc-cu-vacancy/config.ini (excerpt) [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.0` with all other weights at their defaults (0) ensures that *only* listed atoms are used as epicenters. - `displace_all_listed = true` means every atom returned by the script is displaced simultaneously, not just one picked at random. - `displace_radius = 3.0` also includes neighbours within 3 Å of any listed atom. - `displace_atom_kmc_state_script = ptmdisp.py` points to the script (relative to the eOn root directory). ### The PTM Script `ptmdisp.py` is a [PEP 723](https://peps.python.org/pep-0723/) inline script. Its core logic: 1. Reads the `.con` file with ASE. 2. Converts the structure to an OVITO pipeline. 3. Applies the PTM modifier to classify each atom. 4. Selects atoms that are **not** FCC. 5. Prints their indices to stdout. You can run it standalone to inspect the output: ```{code-block} bash 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 1. Identify the adsorbate atoms (C and O) by element. 2. Expand the selection to include all Pt atoms within a cutoff radius of any adsorbate atom. 3. 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): ```{code-block} bash uvx adsorbate_region.py pos.con --adsorbate-elements C O --cutoff 4.0 # Output: 0, 1, 23, 24, 31, ... ``` The script: 1. Reads the `.con` file with `ase.io.read`. 2. Finds atoms matching the specified adsorbate elements. 3. Computes distances from every other atom to the nearest adsorbate atom. 4. Selects all atoms within the cutoff distance. 5. 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 ` to select atoms above a given height. ``` ### Configuration ```{code-block} ini :caption: config.ini (excerpt) [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: ```{code-block} ini :caption: config.ini (excerpt) [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: ```{code-block} ini [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.