Source code for ccu.adsorption.adsorbates

r"""This module provides common adsorbate structures.

Adsorbates can be retrieved using the :func:`get_adsorbate` function
or from any one of the subset dictionaries (:data:`CO2RR_ADSORBATES`,
:data:`NRR_ADSORBATES`, :data:`ORR_ADSORBATES`, :data:`HER_ADSORBATES`, or
:data:`ALL_ADSORBATES`).

|CO2RR| Intermediates from Nitopi et al. [1]_

NRR/UOR Intermediates Wan et al. [2]_ and Li et al. [3]_

Bond lengths, angles and positions from NIST_.

Example:
    >>> from ccu.adsorption.adsorbates import get_adsorbate
    >>> co2 = get_adsorbate("CO2")
    >>> co2
    Atoms(symbols='CO2', pbc=False)
    >>> co2.info["structure"]
    'CO2'
    >>> nho = get_adsorbate("NHO")
    >>> nho
    Atoms(symbols='HNO', pbc=False)
    >>> nho.info["structure"]
    'NHO'

References:

    .. [1] |ref1|_
    .. |ref1| replace:: Chem. Rev. **2019**, 119, 12, 7610-7672.
    .. _ref1: https://pubs.acs.org/doi/10.1021/acs.chemrev.8b00705
    .. [2] |ref2|_
    .. |ref2| replace:: ACS Catal. **2023**, 13, 3, 1926-1933.
    .. _ref2: https://pubs.acs.org/doi/abs/10.1021/acscatal.2c05315
    .. [3] |ref3|_
    .. |ref3| replace:: Angew. Chem. Int. Ed. **2021**, 60, 51, 26656.
    .. _ref3: https://onlinelibrary.wiley.com/doi/abs/10.1002/anie.202107886
    .. _NIST: https://cccbdb.nist.gov
"""

import logging

import ase
from ase.atoms import Atoms
from ase.build import molecule

logger = logging.getLogger(__name__)


[docs] def _co2rr_adsorbates() -> dict[str, Atoms]: co2 = molecule("CO2") # C-O: HCOOH; C=0: HCOOH, O-H: HCOOH cooh_cis = Atoms( "CO2H", positions=[[0, 0, 0], [1.202, 0, 0], [0, 1.343, 0], [0.972, 1.343, 0]], ) # <OCO: HCOOH cooh_cis.set_angle(2, 0, 1, angle=124.9) # <COH: HCOOH cooh_cis.set_angle(0, 2, 3, angle=106.3) cooh_cis.info["special_centers"] = (0,) cooh = cooh_trans = Atoms( "CO2H", positions=[ [0, 0, 0], [1.202, 0, 0], [0, 1.343, 0], [-0.972, 1.343, 0], ], ) # <OCO: HCOOH cooh_trans.set_angle(2, 0, 1, angle=124.9) # <COH: HCOOH (flipped) cooh_trans.set_angle(0, 2, 3, angle=106.3) cooh_trans.info["special_centers"] = (0,) # C-O: HCOOH; C-H: HCOOH ocho = Atoms( "CO2H", positions=[[0, 0, 0], [1.343, 0, 0], [0, 1.343, 0], [-1.097, 0, 0]], ) # <OCO: symmetry ocho.set_angle(2, 0, 1, angle=120) # <COH: HCOOH ocho.set_angle(1, 0, 3, angle=120) ocho.info["special_centers"] = (0,) hcooh = molecule("HCOOH") hcooh.info["special_centers"] = (1,) co = Atoms("CO", positions=[[0, 0, 0], [1.128, 0, 0]]) # C-O: CH3OH; O-H: CH3OH coh = Atoms("COH", positions=[[0, 0, 0], [1.427, 0, 0], [1.427, 0.956, 0]]) # <COH: CH3OH coh.set_angle(2, 1, 0, angle=108.87) coh.info["special_centers"] = (0,) # C-H: CHO; C-O: CHO cho = Atoms("CHO", positions=[[0, 0, 0], [1.080, 0, 0], [0, 1.198, 0]]) # <HCO: CHO cho.set_angle(1, 0, 2, angle=119.5) cho.info["special_centers"] = (0,) c = Atoms("C", positions=[[0, 0, 0]]) # C-H: HCOOH; C-O: HCOOH; O-H: HCOOH choh = Atoms( "CHOH", positions=[ [0, 0, 0], [0, -1.097, 0], [1.343, 0, 0], [1.343, 0.972, 0], ], ) # <OCO: HCOOH choh.set_angle(2, 0, 1, angle=128.8) # <COH: HCOOH choh.set_angle(0, 2, 3, angle=106.3) choh.info["special_centers"] = (0,) # C-H: H2CO; C-O: H2CO ch2o = Atoms( "CH2O", positions=[[0, 0, 0], [0, -1.111, 0], [1.111, 0, 0], [0, 1.205, 0]], ) # <HCH: H2CO ch2o.set_angle(2, 0, 1, angle=116.133) # <HCO: H2CO ch2o.set_angle(1, 0, 3, angle=121.9) ch2o.info["special_centers"] = (0,) # C-H: CH3OH; C-O: CH3OH; O-H: CH3OH ch2oh = Atoms( "CH2OH", positions=[ [0, 0, 0], [-1.096, 0, 0], [0, -1.096, 0], [1.427, 0, 0], [1.427, 0.956, 0], ], ) # <HCH: H2CO ch2oh.set_angle(2, 0, 1, angle=116.133) # <HCO: symmetry ch2oh.set_angle(1, 0, 3, angle=121.9, indices=[3, 4]) # <HOC: CH3OH ch2oh.set_angle(0, 3, 4, angle=108.87) ch2oh.info["special_centers"] = (0,) # Positions from CH3O och3 = Atoms( "OCH3", positions=[ [0, 0, 0.8151], [0, 0, -0.5899], [0, 1.0360, -0.9938], [0.8972, -0.5180, -0.9938], [-0.8972, -0.5180, -0.9938], ], ) och3.info["special_centers"] = (0,) # C-H: H2CO ch2 = Atoms("CH2", positions=[[0, 0, 0], [0, -1.111, 0], [1.111, 0, 0]]) # <HCH: H2CO ch2.set_angle(1, 0, 2, angle=116.133) ch2.info["special_centers"] = (0,) # Positions from CH3O ch3 = Atoms( "CH3", positions=[ [0, 0, -0.5899], [0, 1.0360, -0.9938], [0.8972, -0.5180, -0.9938], [-0.8972, -0.5180, -0.9938], ], ) ch3.info["special_centers"] = (0,) return { "CO2": co2, "COOH_CIS": cooh_cis, "COOH_TRANS": cooh_trans, # The default COOH is trans "COOH": cooh, "OCHO": ocho, "HCOOH": hcooh, "CO": co, "COH": coh, "CHO": cho, "C": c, "CHOH": choh, "CH2O": ch2o, "CH2OH": ch2oh, "OCH3": och3, "CH2": ch2, "CH3": ch3, }
[docs] def _nrr_adsorbates() -> dict[str, Atoms]: # N-O: NO3 no3 = Atoms( "NO3", positions=[[0, 0, 0], [0, 1.238, 0], [-1.238, 0, 0], [1.238, 0, 0]], ) # <ONO: NO3 no3.set_angle(2, 0, 1, angle=120) no3.set_angle(1, 0, 3, angle=120) # From pubchem (cid=944) no3h = Atoms( "NO3H", positions=[ [-4.1600e-02, -1.3017e00, 1.0000e-04], [-1.1005e00, 6.3770e-01, 1.0000e-04], [1.1387e00, 5.6850e-01, 1.0000e-04], [3.4000e-03, 9.5600e-02, -4.0000e-04], [9.0950e-01, -1.5016e00, 2.0000e-04], ], ) no3h.info["special_centers"] = (3,) # N-O: HNO2 no2 = Atoms("NO2", positions=[[0, 0, 0], [0, 1.442, 0], [1.442, 0, 0]]) # <ONO: HNO2 no2.set_angle(1, 0, 2, angle=110.6) # N-O: HNO2; N=0: HNO2 no2h = Atoms( "NO2H", positions=[ [0, 0, 0], [-1.442, 0, 0], [0, 1.169, 0], [-1.442, -0.959, 0], ], ) # <ONO: HNO2 no2h.set_angle(1, 0, 2, angle=110.6) # <HON: HNO2 no2h.set_angle(0, 1, 3, angle=102.1) no2h.info["special_centers"] = (0,) # N=O: NO no = Atoms("NO", positions=[[0, 0, 0], [1.154, 0, 0]]) # N-O: HNO2 & NH2OH (average); O-H: HNO2 & NH2OH (average) noh = Atoms( "NOH", positions=[ [0, 0, 0], [0.5 * (1.442 + 1.453), 0, 0], [0.5 * (1.442 + 1.453), 0.5 * (0.959 + 0.962), 0], ], ) # <NOH: HNO2 & NH2OH (average) noh.set_angle(0, 1, 2, angle=0.5 * (102.1 + 101.37)) noh.info["special_centers"] = (0,) # N-H: NH2OH & HNO (average); N-O: NH2OH & HNO (average) nho = Atoms( "HNO", positions=[ [0, 0, 0], [0.5 * (1.016 + 1.090), 0, 0], [0.5 * (1.016 + 1.090), 0.5 * (1.453 + 1.209), 0], ], ) # <HNO: NH2OH & HNO (average) nho.set_angle(0, 1, 2, angle=0.5 * (107.01 + 108.047)) nho.info["special_centers"] = (2,) # N-H: NH2OH; N-O: NH2OH; O-H: NH2OH nhoh = Atoms( "HNOH", positions=[ [0, 0, 0], [1.016, 0, 0], [1.016, 1.453, 0], [1.016 + 0.962, 1.453, 0], ], ) # <HNO: NH2OH nhoh.set_angle(2, 1, 0, angle=107.1) # <HON: NH2OH nhoh.set_angle(1, 2, 3, angle=101.37) nhoh.info["special_centers"] = (1,) n = Atoms("N") # N-H: NH nh = Atoms("NH", positions=[[0, 0, 0], [1.036, 0, 0]]) # N-H: NH2 nh2 = ase.Atoms( "HNH", positions=[ [0, 0, 0], [1.024, 0, 0], [0, 1.024, 0], ], ) # <HNH: NH2 nh2.set_angle(0, 1, 2, angle=103.4) # N-H: NH2; N-O: NH2OH nh2o = ase.Atoms( "NH2O", positions=[ [0, 0, 0], [1.024, 0, 0], [0, 1.024, 0], [-0.8523, -0.8523, 0], ], ) nh2o.info["special_centers"] = ( 0, 3, ) # N-H: NH2OH nh2oh = ase.Atoms( "NH2OH", positions=[ [-0.0094, 0.704, 0], [0.5469, 1.0012, 0.7965], [0.5469, 1.0012, -0.7965], [-0.0094, -0.7490, 0], [-0.9525, -0.9386, 0], ], ) nh2oh.info["special_centers"] = ( 0, 3, ) # Positions from NH3 nh3 = Atoms( "NH3", positions=[ [0, 0, 0], [0, -0.9377, -0.3816], [0.812, 0.4689, -0.3816], [-0.812, 0.4689, -0.3816], ], ) n2 = molecule("N2") n2o = molecule("N2O") return { "NO3": no3, "NO3H": no3h, "NO2": no2, "NO2H": no2h, "NO": no, "NOH": noh, "NHO": nho, "NHOH": nhoh, "N": n, "NH": nh, "NH2": nh2, "NH2O": nh2o, "NH2OH": nh2oh, "NH3": nh3, "N2": n2, "N2O": n2o, }
[docs] def _orr_adsorbates() -> dict[str, Atoms]: return { "O": Atoms("O"), # O-H: OH- "OH": Atoms("OH", positions=[[0, 0, 0], [0.964, 0, 0]]), # O-H: HO2; O-O: HO2; <OOH: HO2 "OOH": Atoms( "OOH", positions=[ [0.0, 0.0, 0.0], [1.333, 0.0, 0.0], [1.5726, 0.9406, 0.0], ], info={"special_centers": (0,)}, ), "H2O": molecule("H2O"), }
#: adsorbates for the carbon dioxide reduction reaction #: Note that COOH is an alias for COOH_TRANS CO2RR_ADSORBATES = _co2rr_adsorbates() #: adsorbates for the urea oxidation and nitrate reduction reactions NRR_ADSORBATES = _nrr_adsorbates() #: adsorbates for the oxygen reduction and evolution reactions ORR_ADSORBATES = _orr_adsorbates() #: adsorbates for the hydrogen evolution reaction HER_ADSORBATES = { "H": Atoms("H"), "OH": ORR_ADSORBATES["OH"], "H2O": ORR_ADSORBATES["H2O"], } #: All internally defined adsorbates ALL_ADSORBATES: dict[str, Atoms] = {} ALL_ADSORBATES.update(CO2RR_ADSORBATES) ALL_ADSORBATES.update(NRR_ADSORBATES) ALL_ADSORBATES.update(ORR_ADSORBATES) ALL_ADSORBATES.update(HER_ADSORBATES) for name, adsorbate in ALL_ADSORBATES.items(): adsorbate.info["structure"] = name
[docs] def get_adsorbate(adsorbate: str) -> Atoms: """Retrieve the requested adsorbate from `ase` or the internal database. Args: adsorbate: The name of the adsorbate to retrieve. Returns: An :class:`~ase.Atoms` instance representing the requested adsorbate. Raises: NotImplementedError: The requested adsorbate is neither a molecule supported by ASE nor a defined adsorbate in :mod:`ccu.adsorption.adsorbates`. Note: The name of the adsorbate will be stored under the `"structure"` key of its `info` dictionary. .. seealso:: :func:`ase.build.molecule` """ try: atoms = molecule(adsorbate) atoms.info["structure"] = adsorbate return atoms except KeyError: pass name = adsorbate.upper() try: return ALL_ADSORBATES[name] except KeyError as error: msg = f"The {adsorbate} adsorbate is not supported yet." raise NotImplementedError(msg) from error