Source code for chemparseplot.util

import logging

import numpy as np

log = logging.getLogger(__name__)

# --- Configuration ---
POSCON_FILENAME = "pos.con"
_EXPECTED_COORD_COLS = 3

# --- Input Target Coordinates ---
# e.g. from an OVITO selection
target_coords_text = """
19.7267 23.4973 21.4053
21.6919 21.7746 21.7746
19.7274 21.4053 23.4968
21.6918 21.7752 25.2201
21.6915 25.2205 21.7761
18.0999 23.4978 23.4978
19.7265 25.5905 23.4985
21.6915 25.2206 25.2206
22.7758 23.4966 23.4983
20.6077 23.4982 23.4977
19.7268 23.4982 25.5902
23.6566 21.4042 23.4962
23.6566 23.4979 21.4058
23.6561 25.5897 23.4984
23.6566 23.4984 25.5906
25.2834 23.4976 23.4978
"""


# --- Function to parse target coordinates ---
[docs] def parse_target_coords(text_block): """Parses the multiline string of target coordinates. ```{versionadded} 0.0.3 ``` """ coords = [] lines = text_block.strip().split("\n") for i, line in enumerate(lines): try: parts = line.strip().split() if len(parts) == _EXPECTED_COORD_COLS: coords.append([float(p) for p in parts]) elif parts: log.warning( "Skipping target coordinate line %d due to incorrect format: %s", i + 1, line.strip(), ) except ValueError: log.warning( "Skipping target coordinate line %d due to non-numeric value: %s", i + 1, line.strip(), ) return np.array(coords)
[docs] def main(): """Run the coordinate matching helper as a script.""" atoms = None try: # Read the structure using ASE (handles format detection). from ase.io import read log.info("Reading %s using ASE...", POSCON_FILENAME) atoms = read(POSCON_FILENAME) log.info("Successfully read %d atoms.", len(atoms)) atom_coords_from_poscon = atoms.get_positions() except FileNotFoundError: log.error("Error: File not found at %s", POSCON_FILENAME) atom_coords_from_poscon = None except Exception as e: log.error("Error reading %s with ASE: %s", POSCON_FILENAME, e) atom_coords_from_poscon = None log.info("Parsing target coordinates...") target_coords = parse_target_coords(target_coords_text) if atoms is None or atom_coords_from_poscon is None or target_coords.size == 0: log.warning("Aborted due to errors during parsing or file reading.") return log.info("Found %d atoms in %s.", len(atom_coords_from_poscon), POSCON_FILENAME) log.info("Found %d target coordinates to match.", len(target_coords)) results = [] atom_symbols = atoms.get_chemical_symbols() log.info("Matching target coordinates to closest atoms...") for i, target_pos in enumerate(target_coords): distances = np.linalg.norm(atom_coords_from_poscon - target_pos, axis=1) closest_atom_index = np.argmin(distances) # Atom IDs are 0-based in eOn; LAMMPS uses 1-based indexing. closest_atom_id = closest_atom_index min_dist = distances[closest_atom_index] results.append( { "target_index": i + 1, "target_pos": target_pos, "closest_atom_id": closest_atom_id, "closest_atom_symbol": atom_symbols[closest_atom_index], "closest_atom_pos": atom_coords_from_poscon[closest_atom_index], "distance": min_dist, } ) log.info("--- Results ---") for result in results: tp = result["target_pos"] log.info( "Target #%d (%.4f, %.4f, %.4f)", result["target_index"], tp[0], tp[1], tp[2], ) log.info( " -> Closest Atom ID: %s (Symbol: %s)", result["closest_atom_id"], result["closest_atom_symbol"], ) cp = result["closest_atom_pos"] log.info( " Position: (%.4f, %.4f, %.4f)", cp[0], cp[1], cp[2], ) log.info(" Distance: %.6f", result["distance"])
if __name__ == "__main__": main()