Source code for scgo.cluster_adsorbate.sites

"""Convex-hull adsorption site discovery for cluster and slab placement."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Literal

import numpy as np
from ase import Atoms
from scipy.spatial import ConvexHull, QhullError

SiteType = Literal["vertex", "edge", "facet"]


[docs] @dataclass(frozen=True) class SurfaceSiteCandidate: site_type: SiteType anchor: np.ndarray normal: np.ndarray
def _safe_normalize(v: np.ndarray) -> np.ndarray: vn = float(np.linalg.norm(v)) if vn < 1e-12: return np.array([0.0, 0.0, 1.0], dtype=float) return v / vn
[docs] def compute_surface_site_candidates( core: Atoms, ) -> dict[SiteType, list[SurfaceSiteCandidate]]: """Build explicit vertex/edge/facet adsorption sites from a convex hull.""" out: dict[SiteType, list[SurfaceSiteCandidate]] = { "vertex": [], "edge": [], "facet": [], } if len(core) < 4: return out pos = core.get_positions() com = np.mean(pos, axis=0) try: hull = ConvexHull(pos) except (QhullError, ValueError): return out vertices = np.asarray(hull.vertices, dtype=np.intp) for vidx in vertices: anchor = pos[int(vidx)] normal = _safe_normalize(anchor - com) out["vertex"].append( SurfaceSiteCandidate(site_type="vertex", anchor=anchor, normal=normal) ) edge_pairs: set[tuple[int, int]] = set() for simplex in hull.simplices: i, j, k = int(simplex[0]), int(simplex[1]), int(simplex[2]) edge_pairs.add(tuple(sorted((i, j)))) edge_pairs.add(tuple(sorted((j, k)))) edge_pairs.add(tuple(sorted((i, k)))) for i, j in sorted(edge_pairs): midpoint = 0.5 * (pos[i] + pos[j]) normal = _safe_normalize(midpoint - com) out["edge"].append( SurfaceSiteCandidate(site_type="edge", anchor=midpoint, normal=normal) ) for simplex in hull.simplices: tri = pos[np.asarray(simplex, dtype=np.intp)] v1 = tri[1] - tri[0] v2 = tri[2] - tri[0] centroid = np.mean(tri, axis=0) normal = _safe_normalize(np.cross(v1, v2)) if float(np.dot(normal, centroid - com)) < 0.0: normal = -normal out["facet"].append( SurfaceSiteCandidate(site_type="facet", anchor=centroid, normal=normal) ) return out