"""Interfaces for orienting adsorbates on adsorption sites."""fromcollections.abcimportIterablefromtypingimportTYPE_CHECKINGfromtypingimportNamedTuplefromtypingimportProtocolfromase.atomsimportAtomsimportnumpyasnpfromccu.adsorption.sitesimportAdsorptionSitefromccu.structure.axisfinderimportget_axesfromccu.structure.comparatorimportComparatorfromccu.structure.geometryimportMolecularOrientationfromccu.structure.geometryimportalignfromccu.structure.symmetryimportReflectionfromccu.structure.symmetryimportRotationfromccu.structure.symmetryimportTransformationifTYPE_CHECKING:fromnumpy.typingimportNDArray
[docs]classAdsorptionCenter(NamedTuple):"""A tuple representing a point in space used to center an adsorbate. Attributes: position: A length 3, 1D :class:`numpy.ndarray` of floats representing the position of the adsorption center in space. description: A description of the adsorption center (e.g., `"on C1"`). """position:"NDArray[np.floating]"description:str
[docs]classCenterFactory(Protocol):r"""A Callable that generates :class:`AdsorptionCenters <ccu.adsorption.orientation.AdsorptionCenter>`. The :class:`numpy.ndarrays <numpy.ndarray>` returned when calling implementers of this protocol should identify different points used to center an adsorbate. """def__call__(self,adsorbate:Atoms)->list[AdsorptionCenter]:"""Protocol adherents should implement this function."""
[docs]defcom_centerer(adsorbate:Atoms)->list[AdsorptionCenter]:"""A :class:`CenterFactory` for centering adsorbates with their COM. Args: adsorbate: An :class:`~ase.Atoms` object. Returns: A list containing a single :class:`AdsorptionCenters <AdsorptionCenter>` corresponding to the centre-of-mass of `adsorbate`. """return[AdsorptionCenter(adsorbate.get_center_of_mass(),"COM")]
[docs]defspecial_centerer(adsorbate:Atoms)->list[AdsorptionCenter]:"""A :class:`CenterFactory` that returns special centers. Special centers must be identified by the key `"special_centers"` in `adsorbate.info`. Args: adsorbate: An :class:`~ase.Atoms` object. Returns: A list of special centers--relative to the adsorbate center of mass. """if"special_centers"inadsorbate.infoand(indices:=adsorbate.info["special_centers"]):centers:list[AdsorptionCenter]=[]forindexinindices:description=f"{adsorbate[index].symbol}{index}"center=AdsorptionCenter(adsorbate[index].position,description)centers.append(center)returncentersreturn[AdsorptionCenter(adsorbate[0].position,"0")]
[docs]defatomic_centerer(adsorbate:Atoms)->list[AdsorptionCenter]:"""A :class:`CenterFactory` that returns atomic centers. Args: adsorbate: An :class:`~ase.Atoms` object. Returns: A list of :class:`AdsorptionCenters <AdsorptionCenter>` representing the atomic positions of `adsorbate` with descriptions in the form `SYMBOL_INDEX`, where `SYMBOL` and `INDEX` are the atom's chemical symbol and index, respectively. """return[AdsorptionCenter(a.position,f"{a.symbol}{a.index}")forainadsorbate]
[docs]classOrientationFactory(Protocol):r"""A Callable that generates :class:`MolecularOrientations <ccu.structure.geometry.MolecularOrientation>`."""def__call__(self,site:AdsorptionSite,adsorbate:Atoms,center:AdsorptionCenter)->list[MolecularOrientation]:"""Protocol adherents should implement this function."""
[docs]classTransformer(OrientationFactory):r"""A :class:`~ccu.structure.symmetry.Transformation`\ -based :class:`OrientationFactory`. Instances of this class generate :class:`MolecularOrientations <ccu.structure.geometry.MolecularOrientation>` by transforming each :class:`~ccu.adsorption.sites.SiteAlignment` :class:`~ccu.structure.symmetry.Transformation`. Attributes: transformations: A list of :class:`~ccu.structure.symmetry.Transformation` instances. check_symmetry: Whether or not to exlude symmetric images. Note: In order for :class:`MolecularOrientations <ccu.structure.geometry.MolecularOrientation>` representing the original :attr:`SiteAlignment.directions <ccu.adsorption.sites.SiteAlignment.direction>` to be returned by the :class:`Transformer`, at least one :class:`~ccu.structure.symmetry.Transformation` in :attr:`!Transformer.transformations` should be equivalent to the identity transformation. """def__init__(self,transformations:Iterable[Transformation]|None=None,check_symmetry:bool=False,)->None:"""Instantiate a :class:`Transformer`. Args: transformations: A list of :class:`~ccu.structure.symmetry.Transformation` instances. Defaults to a list containing a single :class:`~ccu.structure.symmetry.Transformation` that returns a copy of the input :class:~ase.Atoms` object. check_symmetry: Whether or not to exlude symmetric images. """self.transformations=list(transformationsor[lambdaa:a.copy()])self.check_symmetry=check_symmetrydef__call__(self,site:AdsorptionSite,adsorbate:Atoms,center:AdsorptionCenter)->list[MolecularOrientation]:"""Generate orientations for each transformation and site alignment."""orientations:list[MolecularOrientation]=[]structures:list[Atoms]=[]fori,alignmentinenumerate(site.alignments):site_aligned=align(adsorbate,(alignment.direction,site.norm),center.position,)forj,transforminenumerate(self.transformations):transformed=transform(site_aligned)ifself.check_symmetryandany(Comparator.check_similarity(s,transformed)forsinstructures):continuedescription=f"{alignment.descriptionori}{j}"ax1,ax2,_=get_axes(transformed)orientation=MolecularOrientation((ax1,ax2),description)orientations.append(orientation)structures.append(transformed)returnorientations
[docs]classOctahedralFactory(Transformer):"""A :class:`Transformer` composed of the O:sub:`h` symmetry group."""def__init__(self,transformations:Iterable[Transformation]|None=None,check_symmetry:bool=False,)->None:"""Instantiate a :class:`OctahedralFactory`. Args: transformations: A list of :class:`~ccu.structure.symmetry.Transformation` instances. **This argument will be ignored.** check_symmetry: Whether or not to exlude symmetric images. """transformations=[*(Rotation(90*i)foriinrange(4)),*(Reflection(norm=n)forninnp.identity(3)),]self.check_symmetry=check_symmetrysuper().__init__(transformations,check_symmetry)